Initial commit - 611 cybersecurity skills across all subdomains

This commit is contained in:
mukul975
2026-02-25 10:47:44 +01:00
commit 22a7ab1462
1765 changed files with 280648 additions and 0 deletions
@@ -0,0 +1,69 @@
---
name: implementing-rsa-key-pair-management
description: RSA (Rivest-Shamir-Adleman) is the most widely deployed asymmetric cryptographic algorithm, used for digital signatures, key exchange, and encryption. This skill covers generating, storing, rotating,
domain: cybersecurity
subdomain: cryptography
tags: [cryptography, rsa, key-management, pki, asymmetric-encryption]
version: "1.0"
author: mahipal
license: MIT
---
# Implementing RSA Key Pair Management
## Overview
RSA (Rivest-Shamir-Adleman) is the most widely deployed asymmetric cryptographic algorithm, used for digital signatures, key exchange, and encryption. This skill covers generating, storing, rotating, and managing RSA key pairs following NIST SP 800-57 key management guidelines, including key serialization formats (PEM, DER, PKCS#8), passphrase protection, and key strength validation.
## Objectives
- Generate RSA key pairs with appropriate key sizes (2048, 3072, 4096 bits)
- Serialize keys in PEM and DER formats with PKCS#8
- Protect private keys with strong passphrase encryption
- Implement key rotation with versioning
- Extract public key components and fingerprints
- Validate key strength and detect weak keys
- Sign and verify data using RSA-PSS
## Key Concepts
### RSA Key Sizes and Security Strength
| Key Size (bits) | Security Strength (bits) | Recommended Until |
|-----------------|-------------------------|-------------------|
| 2048 | 112 | 2030 |
| 3072 | 128 | Beyond 2030 |
| 4096 | ~140 | Beyond 2030 |
### RSA Padding Schemes
| Scheme | Use Case | Standard |
|--------|----------|----------|
| OAEP | Encryption | PKCS#1 v2.2 (RFC 8017) |
| PSS | Signatures | PKCS#1 v2.2 (RFC 8017) |
| PKCS#1 v1.5 | Legacy only | Deprecated for new systems |
### Key Storage Formats
- **PEM**: Base64-encoded with headers, human-readable
- **DER**: Binary ASN.1 encoding, compact
- **PKCS#8**: Standard for private key encapsulation
- **PKCS#12/PFX**: Bundled key + certificate, password-protected
## Security Considerations
- Minimum 3072-bit keys for new deployments (NIST recommendation)
- Always protect private keys with AES-256-CBC passphrase encryption
- Use RSA-PSS for signatures (not PKCS#1 v1.5)
- Use RSA-OAEP for encryption (not PKCS#1 v1.5)
- Store private keys with restrictive file permissions (0600)
- Implement key rotation at least annually
## Validation Criteria
- [ ] Key generation produces valid RSA key pair
- [ ] Public key can be extracted from private key
- [ ] Private key is protected with passphrase
- [ ] RSA-PSS signature verification succeeds
- [ ] Tampered signature verification fails
- [ ] Key fingerprint is computed correctly
- [ ] Key rotation maintains old key access for verification
@@ -0,0 +1,56 @@
# RSA Key Pair Management Template
## Key Generation Checklist
- [ ] Select key size (minimum 3072 bits for new deployments)
- [ ] Generate key pair using secure random number generator
- [ ] Protect private key with strong passphrase (AES-256)
- [ ] Compute and record key fingerprint (SHA-256)
- [ ] Set restrictive file permissions on private key
- [ ] Store public key in accessible location
- [ ] Document key metadata (size, algorithm, creation date)
## Key Metadata Template
```json
{
"key_id": "rsa-prod-001",
"algorithm": "RSA",
"key_size": 4096,
"public_exponent": 65537,
"fingerprint_sha256": "<hex-digest>",
"created_at": "2024-01-01T00:00:00Z",
"expires_at": "2025-01-01T00:00:00Z",
"usage": ["sign", "verify"],
"owner": "security-team",
"version": 1
}
```
## Key Rotation Schedule
| Environment | Rotation Frequency | Grace Period |
|------------|-------------------|--------------|
| Production | 12 months | 30 days |
| Staging | 6 months | 14 days |
| Development| 3 months | 7 days |
## Quick Reference
```python
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
# Generate
key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
# Sign (RSA-PSS)
signature = key.sign(data, padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())
# Verify
key.public_key().verify(signature, data, padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())
```
@@ -0,0 +1,41 @@
# Standards and References - RSA Key Pair Management
## Primary Standards
### NIST FIPS 186-5 - Digital Signature Standard (DSS)
- **URL**: https://csrc.nist.gov/publications/detail/fips/186/5/final
- **Description**: Specifies RSA digital signature generation and verification
- **RSA minimum**: 2048-bit keys
### RFC 8017 - PKCS #1: RSA Cryptography Specifications Version 2.2
- **URL**: https://www.rfc-editor.org/rfc/rfc8017
- **Description**: Defines RSA key formats, OAEP encryption, and PSS signatures
- **Key operations**: RSAEP, RSADP, RSASP1, RSAVP1
### RFC 5958 - Asymmetric Key Packages (PKCS#8 v2)
- **URL**: https://www.rfc-editor.org/rfc/rfc5958
- **Description**: Private key information syntax for storage
### RFC 7468 - Textual Encodings of PKIX, PKCS, and CMS Structures
- **URL**: https://www.rfc-editor.org/rfc/rfc7468
- **Description**: PEM encoding format specification
### NIST SP 800-57 Part 1 Rev. 5 - Key Management
- **URL**: https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final
- **Description**: Key length recommendations and lifecycle management
- **RSA 2048**: Acceptable through 2030
- **RSA 3072+**: Recommended for beyond 2030
### NIST SP 800-131A Rev. 2 - Transitioning Cryptographic Algorithms
- **URL**: https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
- **Description**: Transition guidance for algorithm selection
- **PKCS#1 v1.5 signatures**: Legacy use only
- **RSA-PSS**: Recommended for all new applications
## Python Library References
### cryptography (pyca/cryptography)
- **RSA Key Generation**: `cryptography.hazmat.primitives.asymmetric.rsa`
- **Serialization**: `cryptography.hazmat.primitives.serialization`
- **Signatures**: `cryptography.hazmat.primitives.asymmetric.padding`
- **Documentation**: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
@@ -0,0 +1,81 @@
# Workflows - RSA Key Pair Management
## Workflow 1: Key Pair Generation
```
[Select Key Size] (3072 or 4096 bits)
|
[Generate RSA Key Pair]
(public_exponent=65537)
|
[Serialize Private Key]
(PEM/PKCS#8 with AES-256-CBC passphrase)
|
[Extract and Serialize Public Key]
(PEM/SubjectPublicKeyInfo)
|
[Compute Key Fingerprint]
(SHA-256 of DER-encoded public key)
|
[Store Keys with Metadata]
(key_id, creation_date, algorithm, size)
```
## Workflow 2: Digital Signature (RSA-PSS)
```
[Document/Data to Sign]
|
[Hash Data] (SHA-256)
|
[Load Private Key] (decrypt with passphrase)
|
[RSA-PSS Sign]
(padding=PSS, mgf=MGF1(SHA256), salt_length=PSS.MAX_LENGTH)
|
[Output Signature] (DER or Base64)
```
## Workflow 3: Signature Verification
```
[Document + Signature + Public Key]
|
[Load Public Key]
|
[RSA-PSS Verify]
(same padding parameters as signing)
|
[Valid?]
YES --> Accept
NO --> Reject (data or signature tampered)
```
## Workflow 4: Key Rotation
```
[Current Key Pair (version N)]
|
[Generate New Key Pair (version N+1)]
|
[Update Active Key Reference]
|
[Archive Old Key Pair]
(mark as "decrypt/verify only")
|
[After Grace Period: Destroy Old Private Key]
(keep public key for verification)
```
## Workflow 5: RSA Encryption (OAEP)
```
[Plaintext] (max size depends on key and padding)
|
[Load Recipient's Public Key]
|
[RSA-OAEP Encrypt]
(padding=OAEP, mgf=MGF1(SHA256), algorithm=SHA256)
|
[Ciphertext]
```
@@ -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()