|
1 | | -from base64 import b64decode, urlsafe_b64encode |
| 1 | +from base64 import b64decode, b64encode, urlsafe_b64encode |
2 | 2 | from hashlib import sha256 |
3 | 3 | from typing import Optional |
4 | 4 |
|
5 | 5 | from cryptography.fernet import Fernet, InvalidToken |
6 | 6 | from cryptography.hazmat.backends import default_backend # type: ignore |
7 | | -from cryptography.hazmat.primitives import hashes, padding # type: ignore |
8 | | -from cryptography.hazmat.primitives.ciphers import ( # type: ignore |
9 | | - Cipher, |
10 | | - algorithms, |
11 | | - modes, |
12 | | -) |
| 7 | +from cryptography.hazmat.primitives import hashes # type: ignore |
| 8 | +from cryptography.hazmat.primitives.ciphers.aead import AESGCM # type: ignore |
13 | 9 | from cryptography.hazmat.primitives.kdf.pbkdf2 import ( |
14 | 10 | PBKDF2HMAC, # type: ignore |
15 | 11 | ) |
@@ -76,39 +72,29 @@ def generate_salt() -> str: |
76 | 72 |
|
77 | 73 |
|
78 | 74 | def generate_encrypted_file_name(cache_key: str, encryption_key: str) -> str: |
79 | | - """Generate encrypted file name from cache key using AES encryption. |
| 75 | + """Generate encrypted file name from cache key using AES-GCM encryption. |
| 76 | +
|
| 77 | + This implementation matches the Java EncryptionService to ensure compatibility. |
80 | 78 |
|
81 | 79 | Args: |
82 | 80 | cache_key (str): The cache key to encrypt |
83 | 81 | encryption_key (str): The encryption key |
84 | 82 |
|
85 | 83 | Returns: |
86 | | - str: Base64URL encoded AES encrypted filename ending in .txt |
| 84 | + str: Base64 encoded AES-GCM encrypted filename |
87 | 85 | """ |
88 | | - # Derive a 256-bit key from the encryption_key using PBKDF2 |
89 | | - kdf = PBKDF2HMAC( |
90 | | - algorithm=hashes.SHA256(), |
91 | | - salt=b"firebolt_cache_salt", # Fixed salt for deterministic key derivation |
92 | | - length=32, # 256 bits |
93 | | - iterations=10000, |
94 | | - backend=default_backend(), |
95 | | - ) |
96 | | - aes_key = kdf.derive(encryption_key.encode("utf-8")) |
97 | | - |
98 | | - # Pad the cache_key to be a multiple of 16 bytes (AES block size) |
99 | | - padder = padding.PKCS7(128).padder() |
100 | | - padded_data = padder.update(cache_key.encode("utf-8")) |
101 | | - padded_data += padder.finalize() |
102 | | - |
103 | | - # Use a fixed IV for deterministic encryption |
104 | | - # (same input always produces same output) |
105 | | - # This is acceptable for cache file names where we need deterministic results |
106 | | - iv = sha256(cache_key.encode("utf-8")).digest()[:16] |
107 | | - |
108 | | - # Encrypt the padded cache_key |
109 | | - cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend()) |
110 | | - encryptor = cipher.encryptor() |
111 | | - encrypted_data = encryptor.update(padded_data) + encryptor.finalize() |
112 | | - |
113 | | - # Base64URL encode the encrypted data and add .txt extension |
114 | | - return urlsafe_b64encode(encrypted_data).decode("ascii").rstrip("=") + ".txt" |
| 86 | + # Derive AES key using SHA-256 |
| 87 | + key_hash = sha256(encryption_key.encode("utf-8")).digest() |
| 88 | + aes_key = key_hash[:32] # Use first 32 bytes for AES-256 |
| 89 | + |
| 90 | + # Generate deterministic nonce |
| 91 | + nonce_input = (encryption_key + encryption_key).encode("utf-8") |
| 92 | + nonce_hash = sha256(nonce_input).digest() |
| 93 | + nonce = nonce_hash[:12] # AES-GCM nonce should be 12 bytes |
| 94 | + |
| 95 | + # Encrypt using AES-GCM |
| 96 | + aesgcm = AESGCM(aes_key) |
| 97 | + encrypted_data = aesgcm.encrypt(nonce, cache_key.encode("utf-8"), None) |
| 98 | + |
| 99 | + # Base64 encode |
| 100 | + return b64encode(encrypted_data).decode("ascii") |
0 commit comments