npx skills add mukul975/Anthropic-Cybersecurity-SkillsOverview
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.
When to Use
- When deploying or configuring implementing digital signatures with ed25519 capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- Familiarity with cryptography concepts and tools
- Access to a test or lab environment for safe execution
- Python 3.8+ with required dependencies installed
- Appropriate authorization for any testing activities
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
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.7 KB
API Reference: Ed25519 Digital Signature Agent
Dependencies
| Library | Version | Purpose |
|---|---|---|
| cryptography | >=41.0 | Ed25519 key generation, signing, verification |
CLI Usage
# Generate keypair
python scripts/agent.py --generate-keys --output-dir /keys/
# Sign a file
python scripts/agent.py --sign release.tar.gz --private-key /keys/ed25519_private.pem
# Verify files
python scripts/agent.py --verify release.tar.gz --public-key /keys/ed25519_public.pemFunctions
generate_keypair(output_dir, key_name) -> dict
Ed25519PrivateKey.generate(), serializes with private_bytes(PEM, PKCS8, NoEncryption) and public_bytes(PEM, SubjectPublicKeyInfo).
sign_message(private_key_path, message) -> dict
Loads key via load_pem_private_key(), calls key.sign(message). Returns base64 and hex signature.
sign_file(private_key_path, file_path) -> dict
Signs file contents, writes .ed25519.sig JSON containing signature, hash, timestamp.
verify_message(public_key_path, message, signature_b64) -> dict
Calls key.verify(signature, message). Catches InvalidSignature.
verify_file(public_key_path, file_path, sig_path) -> dict
Verifies file against .ed25519.sig JSON, checks hash match.
cryptography API
| Method | Purpose |
|---|---|
Ed25519PrivateKey.generate() |
Generate 32-byte private key |
private_key.sign(data) |
Create 64-byte signature |
public_key.verify(signature, data) |
Verify signature |
load_pem_private_key(data, password) |
Load PEM key |
Output Schema
{
"verifications": [{"file": "release.tar.gz", "valid": true}],
"valid": 3, "invalid": 0
}standards.md1.3 KB
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
workflows.md1.3 KB
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 storeScripts 2
agent.py5.7 KB
#!/usr/bin/env python3
"""Ed25519 digital signature agent using the cryptography library."""
import argparse
import base64
import hashlib
import json
import logging
import os
import sys
from datetime import datetime
from typing import List
try:
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey)
from cryptography.hazmat.primitives import serialization
from cryptography.exceptions import InvalidSignature
except ImportError:
sys.exit("cryptography required: pip install cryptography")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def generate_keypair(output_dir: str, key_name: str = "ed25519") -> dict:
"""Generate Ed25519 keypair and save to PEM files."""
private_key = Ed25519PrivateKey.generate()
priv_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption())
pub_pem = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
priv_path = os.path.join(output_dir, f"{key_name}_private.pem")
pub_path = os.path.join(output_dir, f"{key_name}_public.pem")
with open(priv_path, "wb") as f:
f.write(priv_pem)
with open(pub_path, "wb") as f:
f.write(pub_pem)
logger.info("Keypair saved: %s, %s", priv_path, pub_path)
return {"private_key_path": priv_path, "public_key_path": pub_path}
def load_private_key(path: str) -> Ed25519PrivateKey:
"""Load Ed25519 private key from PEM file."""
with open(path, "rb") as f:
return serialization.load_pem_private_key(f.read(), password=None)
def load_public_key(path: str) -> Ed25519PublicKey:
"""Load Ed25519 public key from PEM file."""
with open(path, "rb") as f:
return serialization.load_pem_public_key(f.read())
def sign_message(private_key_path: str, message: bytes) -> dict:
"""Sign a message with Ed25519 private key."""
key = load_private_key(private_key_path)
signature = key.sign(message)
return {
"signature_b64": base64.b64encode(signature).decode(),
"signature_hex": signature.hex(),
"message_hash": hashlib.sha256(message).hexdigest(),
"signature_bytes": len(signature),
}
def sign_file(private_key_path: str, file_path: str) -> dict:
"""Sign a file with Ed25519 and write signature to .sig file."""
with open(file_path, "rb") as f:
data = f.read()
result = sign_message(private_key_path, data)
sig_path = file_path + ".ed25519.sig"
with open(sig_path, "w") as f:
json.dump({"signature": result["signature_b64"],
"file_hash": result["message_hash"],
"algorithm": "Ed25519",
"signed_at": datetime.utcnow().isoformat()}, f, indent=2)
result["signature_file"] = sig_path
return result
def verify_message(public_key_path: str, message: bytes, signature_b64: str) -> dict:
"""Verify an Ed25519 signature on a message."""
key = load_public_key(public_key_path)
signature = base64.b64decode(signature_b64)
try:
key.verify(signature, message)
return {"valid": True, "algorithm": "Ed25519"}
except InvalidSignature:
return {"valid": False, "error": "Signature verification failed"}
def verify_file(public_key_path: str, file_path: str, sig_path: str) -> dict:
"""Verify a file's Ed25519 signature."""
with open(file_path, "rb") as f:
data = f.read()
with open(sig_path) as f:
sig_data = json.load(f)
result = verify_message(public_key_path, data, sig_data["signature"])
result["file"] = file_path
result["file_hash"] = hashlib.sha256(data).hexdigest()
result["hash_matches"] = result["file_hash"] == sig_data.get("file_hash", "")
return result
def batch_verify(public_key_path: str, files: List[str]) -> List[dict]:
"""Verify signatures for multiple files."""
results = []
for file_path in files:
sig_path = file_path + ".ed25519.sig"
if os.path.isfile(sig_path):
results.append(verify_file(public_key_path, file_path, sig_path))
else:
results.append({"file": file_path, "valid": False, "error": "No signature file"})
return results
def main():
parser = argparse.ArgumentParser(description="Ed25519 Digital Signature Agent")
parser.add_argument("--generate-keys", action="store_true")
parser.add_argument("--sign", help="File to sign")
parser.add_argument("--verify", nargs="+", help="Files to verify")
parser.add_argument("--private-key", help="Private key PEM path")
parser.add_argument("--public-key", help="Public key PEM path")
parser.add_argument("--output-dir", default=".")
parser.add_argument("--output", default="signature_report.json")
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
if args.generate_keys:
result = generate_keypair(args.output_dir)
print(json.dumps(result, indent=2))
elif args.sign and args.private_key:
result = sign_file(args.private_key, args.sign)
print(json.dumps(result, indent=2))
elif args.verify and args.public_key:
results = batch_verify(args.public_key, args.verify)
report = {"verifications": results,
"valid": sum(1 for r in results if r.get("valid")),
"invalid": sum(1 for r in results if not r.get("valid"))}
out_path = os.path.join(args.output_dir, args.output)
with open(out_path, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py10.9 KB
#!/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()