mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-14 15:04:56 +03:00
320 lines
11 KiB
Python
320 lines
11 KiB
Python
#!/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()
|