|
| 1 | +""" |
| 2 | +Cryptographic utility module using PBKDF2 key derivation and stream cipher encryption. |
| 3 | +Zero external dependencies, built using standard library hashlib & secrets. |
| 4 | +""" |
| 5 | + |
| 6 | +import hashlib |
| 7 | +import secrets |
| 8 | +from typing import Tuple |
| 9 | + |
| 10 | + |
| 11 | +def generate_salt(length: int = 16) -> bytes: |
| 12 | + """Generate cryptographically secure random salt.""" |
| 13 | + return secrets.token_bytes(length) |
| 14 | + |
| 15 | + |
| 16 | +def derive_key(master_password: str, salt: bytes, iterations: int = 100_000) -> bytes: |
| 17 | + """Derive 256-bit encryption key from master password using PBKDF2 HMAC SHA-256.""" |
| 18 | + return hashlib.pbkdf2_hmac('sha256', master_password.encode('utf-8'), salt, iterations, dklen=32) |
| 19 | + |
| 20 | + |
| 21 | +def hash_master_password(master_password: str, salt: bytes) -> bytes: |
| 22 | + """Hash master password for storage & verification.""" |
| 23 | + return derive_key(master_password, salt, iterations=100_000) |
| 24 | + |
| 25 | + |
| 26 | +def encrypt_password(plaintext: str, key: bytes) -> Tuple[bytes, bytes]: |
| 27 | + """ |
| 28 | + Encrypt plaintext password using derived key and unique nonce via SHA-256 keystream. |
| 29 | + Returns (nonce, ciphertext). |
| 30 | + """ |
| 31 | + nonce = secrets.token_bytes(16) |
| 32 | + plaintext_bytes = plaintext.encode('utf-8') |
| 33 | + keystream = bytearray() |
| 34 | + |
| 35 | + # Generate keystream block by block |
| 36 | + block_num = 0 |
| 37 | + while len(keystream) < len(plaintext_bytes): |
| 38 | + block = hashlib.sha256(key + nonce + block_num.to_bytes(4, 'big')).digest() |
| 39 | + keystream.extend(block) |
| 40 | + block_num += 1 |
| 41 | + |
| 42 | + ciphertext = bytes(p ^ k for p, k in zip(plaintext_bytes, keystream[:len(plaintext_bytes)])) |
| 43 | + return nonce, ciphertext |
| 44 | + |
| 45 | + |
| 46 | +def decrypt_password(nonce: bytes, ciphertext: bytes, key: bytes) -> str: |
| 47 | + """Decrypt ciphertext back to plaintext string.""" |
| 48 | + keystream = bytearray() |
| 49 | + block_num = 0 |
| 50 | + |
| 51 | + while len(keystream) < len(ciphertext): |
| 52 | + block = hashlib.sha256(key + nonce + block_num.to_bytes(4, 'big')).digest() |
| 53 | + keystream.extend(block) |
| 54 | + block_num += 1 |
| 55 | + |
| 56 | + plaintext_bytes = bytes(c ^ k for c, k in zip(ciphertext, keystream[:len(ciphertext)])) |
| 57 | + return plaintext_bytes.decode('utf-8') |
| 58 | + |
| 59 | + |
| 60 | +def evaluate_password_strength(password: str) -> Tuple[str, int]: |
| 61 | + """ |
| 62 | + Analyze password strength on scale 0-100 and return strength level string. |
| 63 | + """ |
| 64 | + score = 0 |
| 65 | + if len(password) >= 8: |
| 66 | + score += 20 |
| 67 | + if len(password) >= 12: |
| 68 | + score += 20 |
| 69 | + if any(c.isupper() for c in password): |
| 70 | + score += 15 |
| 71 | + if any(c.islower() for c in password): |
| 72 | + score += 15 |
| 73 | + if any(c.isdigit() for c in password): |
| 74 | + score += 15 |
| 75 | + if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password): |
| 76 | + score += 15 |
| 77 | + |
| 78 | + if score < 40: |
| 79 | + return "Weak 🔴", score |
| 80 | + elif score < 75: |
| 81 | + return "Medium 🟡", score |
| 82 | + else: |
| 83 | + return "Strong 🟢", score |
0 commit comments