mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-28 12:19:41 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: implementing-digital-signatures-with-ed25519
|
||||
description: Ed25519 is a high-performance digital signature algorithm using the Edwards curve Curve25519. It provides 128-bit security with 64-byte signatures and 32-byte keys, offering significant advantages ove
|
||||
domain: cybersecurity
|
||||
subdomain: cryptography
|
||||
tags: [cryptography, digital-signatures, ed25519, authentication, integrity]
|
||||
version: "1.0"
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
# Implementing Digital Signatures with Ed25519
|
||||
|
||||
## Overview
|
||||
|
||||
Ed25519 is a high-performance digital signature algorithm using the Edwards curve Curve25519. It provides 128-bit security with 64-byte signatures and 32-byte keys, offering significant advantages over RSA and ECDSA including deterministic signatures (no random nonce needed), resistance to side-channel attacks, and fast verification. This skill covers implementing Ed25519 for document signing, code signing, and API authentication.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Generate Ed25519 key pairs for signing
|
||||
- Sign messages and files with Ed25519
|
||||
- Verify signatures against public keys
|
||||
- Implement multi-signature verification
|
||||
- Build a simple code signing system
|
||||
- Compare Ed25519 performance with RSA and ECDSA
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Ed25519 vs RSA vs ECDSA
|
||||
|
||||
| Property | Ed25519 | RSA-3072 | ECDSA P-256 |
|
||||
|----------|---------|----------|-------------|
|
||||
| Security | 128-bit | 128-bit | 128-bit |
|
||||
| Public key size | 32 bytes | 384 bytes | 64 bytes |
|
||||
| Signature size | 64 bytes | 384 bytes | 64 bytes |
|
||||
| Key generation | ~50 us | ~100 ms | ~1 ms |
|
||||
| Sign | ~70 us | ~5 ms | ~200 us |
|
||||
| Verify | ~200 us | ~200 us | ~500 us |
|
||||
| Deterministic | Yes | No (PSS) | No (unless RFC 6979) |
|
||||
|
||||
### Key Properties
|
||||
|
||||
- **Deterministic**: Same message + key always produces same signature
|
||||
- **Collision-resistant**: No separate hash function needed
|
||||
- **Side-channel resistant**: Constant-time implementation
|
||||
- **Small keys**: 32 bytes each (public and private)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Ed25519 does not support key recovery from signatures
|
||||
- Verify the full message, not a hash (Ed25519 hashes internally)
|
||||
- Public keys must be validated before use (check for low-order points)
|
||||
- Private keys should be stored encrypted at rest
|
||||
- Ed25519 is not yet approved for all NIST use cases (Ed448 is preferred for federal)
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- [ ] Key pair generation produces valid Ed25519 keys
|
||||
- [ ] Signature verification succeeds for valid message
|
||||
- [ ] Signature verification fails for tampered message
|
||||
- [ ] Signature verification fails for wrong public key
|
||||
- [ ] Deterministic: same input produces same signature
|
||||
- [ ] File signing and verification works correctly
|
||||
- [ ] Performance meets or exceeds RSA-3072
|
||||
@@ -0,0 +1,34 @@
|
||||
# Ed25519 Digital Signatures Template
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```python
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
|
||||
# Generate
|
||||
private_key = Ed25519PrivateKey.generate()
|
||||
public_key = private_key.public_key()
|
||||
|
||||
# Sign
|
||||
signature = private_key.sign(b"message data")
|
||||
|
||||
# Verify
|
||||
public_key.verify(signature, b"message data") # raises InvalidSignature on failure
|
||||
```
|
||||
|
||||
## Key Formats
|
||||
|
||||
| Format | Private Key Size | Public Key Size | Signature Size |
|
||||
|--------|-----------------|-----------------|----------------|
|
||||
| Raw | 32 bytes | 32 bytes | 64 bytes |
|
||||
| PEM (PKCS#8) | ~119 bytes | ~90 bytes | N/A |
|
||||
| SSH | ~83 bytes | ~51 bytes | ~83 bytes |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- API request authentication (sign request body)
|
||||
- Software/code signing
|
||||
- Document signing
|
||||
- Git commit signing (ssh-ed25519)
|
||||
- JWT signing (EdDSA algorithm)
|
||||
- Certificate signing (X.509 with Ed25519)
|
||||
@@ -0,0 +1,36 @@
|
||||
# Standards and References - Digital Signatures with Ed25519
|
||||
|
||||
## Primary Standards
|
||||
|
||||
### RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
|
||||
- **URL**: https://www.rfc-editor.org/rfc/rfc8032
|
||||
- **Description**: Defines Ed25519 and Ed448 signature algorithms
|
||||
|
||||
### RFC 8709 - Ed25519 and Ed448 Public Key Algorithms for SSH
|
||||
- **URL**: https://www.rfc-editor.org/rfc/rfc8709
|
||||
- **Description**: SSH key format for Ed25519
|
||||
|
||||
### NIST FIPS 186-5 - Digital Signature Standard
|
||||
- **URL**: https://csrc.nist.gov/publications/detail/fips/186/5/final
|
||||
- **Description**: Includes EdDSA as approved signature algorithm
|
||||
|
||||
### RFC 7748 - Elliptic Curves for Security
|
||||
- **URL**: https://www.rfc-editor.org/rfc/rfc7748
|
||||
- **Description**: Defines Curve25519 and Curve448
|
||||
|
||||
## Python Libraries
|
||||
|
||||
### cryptography (pyca/cryptography)
|
||||
- **Ed25519**: `cryptography.hazmat.primitives.asymmetric.ed25519`
|
||||
- **Docs**: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/
|
||||
|
||||
### PyNaCl (libsodium)
|
||||
- **URL**: https://pynacl.readthedocs.io/
|
||||
- **Ed25519**: `nacl.signing`
|
||||
- **Docs**: https://pynacl.readthedocs.io/en/latest/signing/
|
||||
|
||||
## Related
|
||||
|
||||
### Daniel J. Bernstein et al. - High-speed high-security signatures
|
||||
- **URL**: https://ed25519.cr.yp.to/
|
||||
- **Description**: Original Ed25519 paper and reference implementation
|
||||
@@ -0,0 +1,66 @@
|
||||
# Workflows - Digital Signatures with Ed25519
|
||||
|
||||
## Workflow 1: Key Generation and Storage
|
||||
|
||||
```
|
||||
[Generate Ed25519 Key Pair]
|
||||
(32-byte private seed -> 32-byte public key)
|
||||
|
|
||||
[Serialize Private Key (PKCS#8 PEM)]
|
||||
[Serialize Public Key (SubjectPublicKeyInfo PEM)]
|
||||
|
|
||||
[Encrypt Private Key with Passphrase]
|
||||
|
|
||||
[Store with Metadata]
|
||||
(key_id, fingerprint, creation_date)
|
||||
```
|
||||
|
||||
## Workflow 2: Sign Document
|
||||
|
||||
```
|
||||
[Document to Sign]
|
||||
|
|
||||
[Load Private Key (decrypt passphrase)]
|
||||
|
|
||||
[Ed25519 Sign]
|
||||
(deterministic: SHA-512 internal hash)
|
||||
|
|
||||
[Output: 64-byte Signature]
|
||||
|
|
||||
[Create Signature File]
|
||||
(signature + public key reference + metadata)
|
||||
```
|
||||
|
||||
## Workflow 3: Verify Signature
|
||||
|
||||
```
|
||||
[Document + Signature + Public Key]
|
||||
|
|
||||
[Load Public Key]
|
||||
|
|
||||
[Ed25519 Verify]
|
||||
|
|
||||
[Valid?]
|
||||
YES -> Accept document as authentic
|
||||
NO -> Reject (tampering detected)
|
||||
```
|
||||
|
||||
## Workflow 4: Code Signing System
|
||||
|
||||
```
|
||||
[Build Artifact] (binary, package, container)
|
||||
|
|
||||
[Hash Artifact] (SHA-256)
|
||||
|
|
||||
[Create Signing Manifest]
|
||||
(artifact_name, hash, timestamp, signer_id)
|
||||
|
|
||||
[Sign Manifest with Ed25519]
|
||||
|
|
||||
[Distribute: Artifact + Manifest + Signature + Public Key]
|
||||
|
|
||||
[Recipient Verifies]:
|
||||
1. Verify signature on manifest
|
||||
2. Hash artifact and compare to manifest
|
||||
3. Check signer identity against trust store
|
||||
```
|
||||
@@ -0,0 +1,319 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Ed25519 Digital Signature Tool
|
||||
|
||||
Implements Ed25519 key generation, signing, verification, and a
|
||||
simple code signing system.
|
||||
|
||||
Requirements:
|
||||
pip install cryptography
|
||||
|
||||
Usage:
|
||||
python process.py generate --output ./keys
|
||||
python process.py sign --key ./keys/private.pem --input document.pdf
|
||||
python process.py verify --key ./keys/public.pem --input document.pdf --signature document.pdf.sig
|
||||
python process.py code-sign --key ./keys/private.pem --artifact ./build/app.zip
|
||||
python process.py benchmark
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import hashlib
|
||||
import argparse
|
||||
import logging
|
||||
import datetime
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_ed25519_keypair(
|
||||
output_dir: str, passphrase: Optional[str] = None
|
||||
) -> Dict:
|
||||
"""Generate an Ed25519 key pair."""
|
||||
private_key = Ed25519PrivateKey.generate()
|
||||
public_key = private_key.public_key()
|
||||
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if passphrase:
|
||||
enc = serialization.BestAvailableEncryption(passphrase.encode())
|
||||
else:
|
||||
enc = serialization.NoEncryption()
|
||||
|
||||
private_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=enc,
|
||||
)
|
||||
(output_path / "private.pem").write_bytes(private_pem)
|
||||
|
||||
public_pem = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
(output_path / "public.pem").write_bytes(public_pem)
|
||||
|
||||
# Compute fingerprint
|
||||
public_raw = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.Raw,
|
||||
format=serialization.PublicFormat.Raw,
|
||||
)
|
||||
fingerprint = hashlib.sha256(public_raw).hexdigest()
|
||||
|
||||
metadata = {
|
||||
"algorithm": "Ed25519",
|
||||
"public_key_hex": public_raw.hex(),
|
||||
"fingerprint_sha256": fingerprint,
|
||||
"created_at": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"private_key_path": str(output_path / "private.pem"),
|
||||
"public_key_path": str(output_path / "public.pem"),
|
||||
}
|
||||
(output_path / "key_metadata.json").write_text(json.dumps(metadata, indent=2))
|
||||
|
||||
logger.info(f"Ed25519 key pair generated in {output_dir}")
|
||||
logger.info(f"Fingerprint: {fingerprint}")
|
||||
return metadata
|
||||
|
||||
|
||||
def load_private_key(path: str, passphrase: Optional[str] = None) -> Ed25519PrivateKey:
|
||||
"""Load Ed25519 private key from PEM file."""
|
||||
data = Path(path).read_bytes()
|
||||
pwd = passphrase.encode() if passphrase else None
|
||||
key = serialization.load_pem_private_key(data, password=pwd)
|
||||
if not isinstance(key, Ed25519PrivateKey):
|
||||
raise TypeError("Key is not Ed25519")
|
||||
return key
|
||||
|
||||
|
||||
def load_public_key(path: str) -> Ed25519PublicKey:
|
||||
"""Load Ed25519 public key from PEM file."""
|
||||
data = Path(path).read_bytes()
|
||||
key = serialization.load_pem_public_key(data)
|
||||
if not isinstance(key, Ed25519PublicKey):
|
||||
raise TypeError("Key is not Ed25519")
|
||||
return key
|
||||
|
||||
|
||||
def sign_data(data: bytes, private_key: Ed25519PrivateKey) -> bytes:
|
||||
"""Sign data with Ed25519."""
|
||||
return private_key.sign(data)
|
||||
|
||||
|
||||
def verify_data(data: bytes, signature: bytes, public_key: Ed25519PublicKey) -> bool:
|
||||
"""Verify Ed25519 signature."""
|
||||
try:
|
||||
public_key.verify(signature, data)
|
||||
return True
|
||||
except InvalidSignature:
|
||||
return False
|
||||
|
||||
|
||||
def sign_file(key_path: str, input_path: str, passphrase: Optional[str] = None) -> Dict:
|
||||
"""Sign a file and save the signature."""
|
||||
private_key = load_private_key(key_path, passphrase)
|
||||
data = Path(input_path).read_bytes()
|
||||
|
||||
signature = sign_data(data, private_key)
|
||||
sig_path = input_path + ".sig"
|
||||
Path(sig_path).write_bytes(signature)
|
||||
|
||||
# Also save base64 signature for text-friendly contexts
|
||||
sig_b64_path = input_path + ".sig.b64"
|
||||
Path(sig_b64_path).write_text(base64.b64encode(signature).decode())
|
||||
|
||||
file_hash = hashlib.sha256(data).hexdigest()
|
||||
|
||||
logger.info(f"Signed {input_path} ({len(data)} bytes)")
|
||||
return {
|
||||
"file": input_path,
|
||||
"signature_file": sig_path,
|
||||
"signature_b64_file": sig_b64_path,
|
||||
"signature_hex": signature.hex(),
|
||||
"file_sha256": file_hash,
|
||||
"algorithm": "Ed25519",
|
||||
}
|
||||
|
||||
|
||||
def verify_file(key_path: str, input_path: str, sig_path: str) -> Dict:
|
||||
"""Verify a file's Ed25519 signature."""
|
||||
public_key = load_public_key(key_path)
|
||||
data = Path(input_path).read_bytes()
|
||||
signature = Path(sig_path).read_bytes()
|
||||
|
||||
# Handle base64 encoded signatures
|
||||
if len(signature) != 64:
|
||||
try:
|
||||
signature = base64.b64decode(signature)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
valid = verify_data(data, signature, public_key)
|
||||
logger.info(f"Verification: {'VALID' if valid else 'INVALID'}")
|
||||
|
||||
return {
|
||||
"file": input_path,
|
||||
"valid": valid,
|
||||
"file_sha256": hashlib.sha256(data).hexdigest(),
|
||||
"algorithm": "Ed25519",
|
||||
}
|
||||
|
||||
|
||||
def code_sign(key_path: str, artifact_path: str, passphrase: Optional[str] = None) -> Dict:
|
||||
"""Create a code signing manifest for an artifact."""
|
||||
private_key = load_private_key(key_path, passphrase)
|
||||
data = Path(artifact_path).read_bytes()
|
||||
|
||||
public_raw = private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.Raw,
|
||||
format=serialization.PublicFormat.Raw,
|
||||
)
|
||||
|
||||
manifest = {
|
||||
"artifact": Path(artifact_path).name,
|
||||
"size": len(data),
|
||||
"sha256": hashlib.sha256(data).hexdigest(),
|
||||
"sha512": hashlib.sha512(data).hexdigest(),
|
||||
"signer_public_key": public_raw.hex(),
|
||||
"signer_fingerprint": hashlib.sha256(public_raw).hexdigest(),
|
||||
"signed_at": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"algorithm": "Ed25519",
|
||||
}
|
||||
|
||||
manifest_json = json.dumps(manifest, indent=2, sort_keys=True).encode()
|
||||
signature = sign_data(manifest_json, private_key)
|
||||
|
||||
signed_manifest = {
|
||||
**manifest,
|
||||
"signature": base64.b64encode(signature).decode(),
|
||||
}
|
||||
|
||||
manifest_path = artifact_path + ".manifest.json"
|
||||
Path(manifest_path).write_text(json.dumps(signed_manifest, indent=2))
|
||||
|
||||
logger.info(f"Code signed: {artifact_path}")
|
||||
return signed_manifest
|
||||
|
||||
|
||||
def verify_code_signature(manifest_path: str, artifact_path: str) -> Dict:
|
||||
"""Verify a code signing manifest."""
|
||||
signed_manifest = json.loads(Path(manifest_path).read_text())
|
||||
signature = base64.b64decode(signed_manifest["signature"])
|
||||
|
||||
public_raw = bytes.fromhex(signed_manifest["signer_public_key"])
|
||||
public_key = Ed25519PublicKey.from_public_bytes(public_raw)
|
||||
|
||||
manifest_copy = {k: v for k, v in signed_manifest.items() if k != "signature"}
|
||||
manifest_json = json.dumps(manifest_copy, indent=2, sort_keys=True).encode()
|
||||
|
||||
sig_valid = verify_data(manifest_json, signature, public_key)
|
||||
|
||||
data = Path(artifact_path).read_bytes()
|
||||
hash_valid = hashlib.sha256(data).hexdigest() == signed_manifest["sha256"]
|
||||
|
||||
return {
|
||||
"artifact": artifact_path,
|
||||
"signature_valid": sig_valid,
|
||||
"hash_valid": hash_valid,
|
||||
"overall_valid": sig_valid and hash_valid,
|
||||
"signer_fingerprint": signed_manifest["signer_fingerprint"],
|
||||
}
|
||||
|
||||
|
||||
def benchmark():
|
||||
"""Benchmark Ed25519 operations."""
|
||||
print("=== Ed25519 Benchmark ===\n")
|
||||
|
||||
# Key generation
|
||||
count = 1000
|
||||
start = time.time()
|
||||
for _ in range(count):
|
||||
Ed25519PrivateKey.generate()
|
||||
elapsed = time.time() - start
|
||||
print(f"Key generation: {count / elapsed:.0f} keys/s ({elapsed / count * 1e6:.1f} us/key)")
|
||||
|
||||
# Signing
|
||||
key = Ed25519PrivateKey.generate()
|
||||
message = b"Benchmark message for Ed25519 signing performance test." * 10
|
||||
count = 5000
|
||||
start = time.time()
|
||||
for _ in range(count):
|
||||
key.sign(message)
|
||||
elapsed = time.time() - start
|
||||
print(f"Signing: {count / elapsed:.0f} sigs/s ({elapsed / count * 1e6:.1f} us/sig)")
|
||||
|
||||
# Verification
|
||||
public_key = key.public_key()
|
||||
signature = key.sign(message)
|
||||
count = 2000
|
||||
start = time.time()
|
||||
for _ in range(count):
|
||||
public_key.verify(signature, message)
|
||||
elapsed = time.time() - start
|
||||
print(f"Verification: {count / elapsed:.0f} verifs/s ({elapsed / count * 1e6:.1f} us/verify)")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Ed25519 Digital Signature Tool")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
gen = subparsers.add_parser("generate", help="Generate Ed25519 key pair")
|
||||
gen.add_argument("--output", "-o", default="./keys", help="Output directory")
|
||||
gen.add_argument("--passphrase", "-p", help="Passphrase for private key")
|
||||
|
||||
sig = subparsers.add_parser("sign", help="Sign a file")
|
||||
sig.add_argument("--key", required=True, help="Private key path")
|
||||
sig.add_argument("--input", "-i", required=True, help="File to sign")
|
||||
sig.add_argument("--passphrase", "-p", help="Key passphrase")
|
||||
|
||||
ver = subparsers.add_parser("verify", help="Verify signature")
|
||||
ver.add_argument("--key", required=True, help="Public key path")
|
||||
ver.add_argument("--input", "-i", required=True, help="File to verify")
|
||||
ver.add_argument("--signature", "-s", required=True, help="Signature file")
|
||||
|
||||
cs = subparsers.add_parser("code-sign", help="Code sign an artifact")
|
||||
cs.add_argument("--key", required=True, help="Private key path")
|
||||
cs.add_argument("--artifact", required=True, help="Artifact to sign")
|
||||
cs.add_argument("--passphrase", "-p", help="Key passphrase")
|
||||
|
||||
csv = subparsers.add_parser("code-verify", help="Verify code signature")
|
||||
csv.add_argument("--manifest", required=True, help="Manifest file path")
|
||||
csv.add_argument("--artifact", required=True, help="Artifact file path")
|
||||
|
||||
subparsers.add_parser("benchmark", help="Benchmark Ed25519 performance")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "generate":
|
||||
result = generate_ed25519_keypair(args.output, args.passphrase)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "sign":
|
||||
result = sign_file(args.key, args.input, args.passphrase)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "verify":
|
||||
result = verify_file(args.key, args.input, args.signature)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "code-sign":
|
||||
result = code_sign(args.key, args.artifact, args.passphrase)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "code-verify":
|
||||
result = verify_code_signature(args.manifest, args.artifact)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "benchmark":
|
||||
benchmark()
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user