mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-26 11:44:37 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
RSA Key Pair Management Tool
|
||||
|
||||
Implements RSA key generation, serialization, signing, verification,
|
||||
encryption, and key rotation using the cryptography library.
|
||||
|
||||
Requirements:
|
||||
pip install cryptography
|
||||
|
||||
Usage:
|
||||
python process.py generate --size 4096 --output ./keys --passphrase "MyKeyPass"
|
||||
python process.py sign --key ./keys/private.pem --input document.pdf --passphrase "MyKeyPass"
|
||||
python process.py verify --key ./keys/public.pem --input document.pdf --signature document.pdf.sig
|
||||
python process.py info --key ./keys/public.pem
|
||||
python process.py rotate --keystore ./keys --passphrase "MyKeyPass"
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import hashlib
|
||||
import argparse
|
||||
import logging
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, padding, utils
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RECOMMENDED_KEY_SIZE = 4096
|
||||
PUBLIC_EXPONENT = 65537
|
||||
|
||||
|
||||
def generate_rsa_keypair(
|
||||
key_size: int = RECOMMENDED_KEY_SIZE,
|
||||
passphrase: Optional[str] = None,
|
||||
) -> Tuple[bytes, bytes, Dict]:
|
||||
"""
|
||||
Generate an RSA key pair.
|
||||
|
||||
Returns:
|
||||
Tuple of (private_key_pem, public_key_pem, metadata)
|
||||
"""
|
||||
if key_size < 2048:
|
||||
raise ValueError("Key size must be at least 2048 bits (3072+ recommended)")
|
||||
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=PUBLIC_EXPONENT,
|
||||
key_size=key_size,
|
||||
backend=default_backend(),
|
||||
)
|
||||
|
||||
if passphrase:
|
||||
encryption = serialization.BestAvailableEncryption(passphrase.encode())
|
||||
else:
|
||||
encryption = serialization.NoEncryption()
|
||||
|
||||
private_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=encryption,
|
||||
)
|
||||
|
||||
public_key = private_key.public_key()
|
||||
public_pem = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
|
||||
# Compute fingerprint (SHA-256 of DER-encoded public key)
|
||||
public_der = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
fingerprint = hashlib.sha256(public_der).hexdigest()
|
||||
|
||||
metadata = {
|
||||
"algorithm": "RSA",
|
||||
"key_size": key_size,
|
||||
"public_exponent": PUBLIC_EXPONENT,
|
||||
"fingerprint_sha256": fingerprint,
|
||||
"created_at": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"passphrase_protected": passphrase is not None,
|
||||
"format": "PKCS#8 PEM",
|
||||
}
|
||||
|
||||
return private_pem, public_pem, metadata
|
||||
|
||||
|
||||
def save_keypair(
|
||||
output_dir: str,
|
||||
private_pem: bytes,
|
||||
public_pem: bytes,
|
||||
metadata: Dict,
|
||||
version: int = 1,
|
||||
) -> Dict:
|
||||
"""Save key pair to files with metadata."""
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
prefix = f"v{version}_" if version > 1 else ""
|
||||
|
||||
private_path = output_path / f"{prefix}private.pem"
|
||||
public_path = output_path / f"{prefix}public.pem"
|
||||
meta_path = output_path / f"{prefix}key_metadata.json"
|
||||
|
||||
private_path.write_bytes(private_pem)
|
||||
public_path.write_bytes(public_pem)
|
||||
|
||||
metadata["version"] = version
|
||||
metadata["private_key_path"] = str(private_path)
|
||||
metadata["public_key_path"] = str(public_path)
|
||||
|
||||
meta_path.write_text(json.dumps(metadata, indent=2))
|
||||
|
||||
logger.info(f"Key pair saved to {output_dir} (version {version})")
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
def load_private_key(key_path: str, passphrase: Optional[str] = None):
|
||||
"""Load an RSA private key from PEM file."""
|
||||
key_data = Path(key_path).read_bytes()
|
||||
pwd = passphrase.encode() if passphrase else None
|
||||
return serialization.load_pem_private_key(key_data, password=pwd, backend=default_backend())
|
||||
|
||||
|
||||
def load_public_key(key_path: str):
|
||||
"""Load an RSA public key from PEM file."""
|
||||
key_data = Path(key_path).read_bytes()
|
||||
return serialization.load_pem_public_key(key_data, backend=default_backend())
|
||||
|
||||
|
||||
def sign_data(data: bytes, private_key) -> bytes:
|
||||
"""Sign data using RSA-PSS with SHA-256."""
|
||||
signature = private_key.sign(
|
||||
data,
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
salt_length=padding.PSS.MAX_LENGTH,
|
||||
),
|
||||
hashes.SHA256(),
|
||||
)
|
||||
return signature
|
||||
|
||||
|
||||
def verify_signature(data: bytes, signature: bytes, public_key) -> bool:
|
||||
"""Verify RSA-PSS signature."""
|
||||
try:
|
||||
public_key.verify(
|
||||
signature,
|
||||
data,
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
salt_length=padding.PSS.MAX_LENGTH,
|
||||
),
|
||||
hashes.SHA256(),
|
||||
)
|
||||
return True
|
||||
except InvalidSignature:
|
||||
return False
|
||||
|
||||
|
||||
def encrypt_data(plaintext: bytes, public_key) -> bytes:
|
||||
"""Encrypt data using RSA-OAEP."""
|
||||
max_size = (public_key.key_size // 8) - 2 * 32 - 2 # OAEP with SHA-256
|
||||
if len(plaintext) > max_size:
|
||||
raise ValueError(
|
||||
f"Plaintext too large for RSA-OAEP ({len(plaintext)} bytes, max {max_size}). "
|
||||
"Use envelope encryption for large data."
|
||||
)
|
||||
return public_key.encrypt(
|
||||
plaintext,
|
||||
padding.OAEP(
|
||||
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||
algorithm=hashes.SHA256(),
|
||||
label=None,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def decrypt_data(ciphertext: bytes, private_key) -> bytes:
|
||||
"""Decrypt RSA-OAEP encrypted data."""
|
||||
return private_key.decrypt(
|
||||
ciphertext,
|
||||
padding.OAEP(
|
||||
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||
algorithm=hashes.SHA256(),
|
||||
label=None,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_key_info(key_path: str, passphrase: Optional[str] = None) -> Dict:
|
||||
"""Get information about an RSA key."""
|
||||
key_data = Path(key_path).read_bytes()
|
||||
|
||||
try:
|
||||
pwd = passphrase.encode() if passphrase else None
|
||||
key = serialization.load_pem_private_key(key_data, password=pwd, backend=default_backend())
|
||||
key_type = "private"
|
||||
public_key = key.public_key()
|
||||
except (ValueError, TypeError):
|
||||
key = serialization.load_pem_public_key(key_data, backend=default_backend())
|
||||
key_type = "public"
|
||||
public_key = key
|
||||
|
||||
public_der = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
fingerprint = hashlib.sha256(public_der).hexdigest()
|
||||
|
||||
numbers = public_key.public_numbers()
|
||||
|
||||
info = {
|
||||
"key_type": key_type,
|
||||
"algorithm": "RSA",
|
||||
"key_size": public_key.key_size,
|
||||
"public_exponent": numbers.e,
|
||||
"fingerprint_sha256": fingerprint,
|
||||
"modulus_hex_prefix": hex(numbers.n)[:32] + "...",
|
||||
}
|
||||
|
||||
if public_key.key_size < 2048:
|
||||
info["warning"] = "Key size below 2048 bits is considered insecure"
|
||||
elif public_key.key_size < 3072:
|
||||
info["note"] = "Key size below 3072 bits; consider upgrading for post-2030 use"
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def rotate_keys(keystore_dir: str, passphrase: Optional[str] = None) -> Dict:
|
||||
"""Rotate RSA key pair, archiving the old one."""
|
||||
keystore = Path(keystore_dir)
|
||||
|
||||
# Find current version
|
||||
version = 1
|
||||
meta_files = sorted(keystore.glob("*key_metadata.json"))
|
||||
if meta_files:
|
||||
for mf in meta_files:
|
||||
meta = json.loads(mf.read_text())
|
||||
v = meta.get("version", 1)
|
||||
if v >= version:
|
||||
version = v + 1
|
||||
|
||||
# Generate new key pair
|
||||
private_pem, public_pem, metadata = generate_rsa_keypair(
|
||||
key_size=RECOMMENDED_KEY_SIZE, passphrase=passphrase
|
||||
)
|
||||
|
||||
result = save_keypair(keystore_dir, private_pem, public_pem, metadata, version=version)
|
||||
|
||||
# Update current key symlink info
|
||||
current_meta = {
|
||||
"current_version": version,
|
||||
"current_fingerprint": metadata["fingerprint_sha256"],
|
||||
"rotated_at": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"all_versions": list(range(1, version + 1)),
|
||||
}
|
||||
(keystore / "current.json").write_text(json.dumps(current_meta, indent=2))
|
||||
|
||||
logger.info(f"Key rotated to version {version}")
|
||||
return result
|
||||
|
||||
|
||||
def sign_file(key_path: str, input_path: str, passphrase: Optional[str] = None) -> str:
|
||||
"""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)
|
||||
logger.info(f"Signature saved to {sig_path}")
|
||||
return sig_path
|
||||
|
||||
|
||||
def verify_file(key_path: str, input_path: str, sig_path: str) -> bool:
|
||||
"""Verify a file's signature."""
|
||||
public_key = load_public_key(key_path)
|
||||
data = Path(input_path).read_bytes()
|
||||
signature = Path(sig_path).read_bytes()
|
||||
valid = verify_signature(data, signature, public_key)
|
||||
logger.info(f"Signature verification: {'VALID' if valid else 'INVALID'}")
|
||||
return valid
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="RSA Key Pair Management Tool")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
gen = subparsers.add_parser("generate", help="Generate RSA key pair")
|
||||
gen.add_argument("--size", type=int, default=4096, help="Key size in bits")
|
||||
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 a signature")
|
||||
ver.add_argument("--key", required=True, help="Public key path")
|
||||
ver.add_argument("--input", "-i", required=True, help="Original file")
|
||||
ver.add_argument("--signature", "-s", required=True, help="Signature file")
|
||||
|
||||
info = subparsers.add_parser("info", help="Show key information")
|
||||
info.add_argument("--key", required=True, help="Key file path")
|
||||
info.add_argument("--passphrase", "-p", help="Key passphrase")
|
||||
|
||||
rot = subparsers.add_parser("rotate", help="Rotate key pair")
|
||||
rot.add_argument("--keystore", required=True, help="Keystore directory")
|
||||
rot.add_argument("--passphrase", "-p", help="Passphrase for new key")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "generate":
|
||||
priv, pub, meta = generate_rsa_keypair(args.size, args.passphrase)
|
||||
result = save_keypair(args.output, priv, pub, meta)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "sign":
|
||||
sig_path = sign_file(args.key, args.input, args.passphrase)
|
||||
print(json.dumps({"signature_file": sig_path}))
|
||||
elif args.command == "verify":
|
||||
valid = verify_file(args.key, args.input, args.signature)
|
||||
print(json.dumps({"valid": valid}))
|
||||
if not valid:
|
||||
sys.exit(1)
|
||||
elif args.command == "info":
|
||||
result = get_key_info(args.key, args.passphrase)
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.command == "rotate":
|
||||
result = rotate_keys(args.keystore, args.passphrase)
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user