为何相同盐值与加密流程下密码加密输出不一致?
问题:相同盐值与加密流程下输出结果不一致的原因及修复
问题场景
使用相同盐值对同一密码执行加密操作,理论上输出应一致,但实际结果不同,代码如下:
import os from cryptography.fernet import Fernet from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import base64 def encrypt_password(password, salt=None): # Create a password hash if not salt: salt = os.urandom(64) print("...") token = base64.b64encode(salt).decode('utf-8') password = password.encode() kdf = PBKDF2HMAC( algorithm=hashes.SHA256, iterations=100000, length=32, salt=salt, backend=default_backend() ) key = base64.urlsafe_b64encode(kdf.derive(password)) # Encrypt the password f = Fernet(key) encrypted_password = f.encrypt(password) return encrypted_password, salt x, salt2 = encrypt_password("Hello") print(x) l, salt1 = encrypt_password("Hello", salt2) print(l) print(salt1 == salt2)
已确认盐值未发生变化,怀疑是库的不一致性问题。
原因分析
- Fernet加密的安全特性:Fernet每次执行
encrypt()时,会自动生成随机初始化向量(IV)并嵌入加密结果中。即使密钥、明文完全相同,不同的IV会导致加密输出不同,但解密时可通过结果携带的IV还原明文。这是刻意设计的安全机制,避免相同明文加密后出现重复密文,防止被暴力破解。 - 登录逻辑的错误设计:当前代码用加密而非哈希存储密码,不符合登录系统安全规范——加密的密码可被解密还原,而密码应使用不可逆的哈希算法处理,避免泄露后被还原。
修复方案
方案1:正确实现登录密码的哈希验证(推荐)
登录系统应存储密码的哈希值,验证时用相同盐值重新计算输入密码的哈希,再与存储值对比:
import os from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import base64 def hash_password(password, salt=None): password = password.encode() if not salt: salt = os.urandom(64) kdf = PBKDF2HMAC( algorithm=hashes.SHA256, iterations=100000, length=32, salt=salt, backend=default_backend() ) # 直接派生不可逆的哈希值 password_hash = base64.b64encode(kdf.derive(password)) return password_hash, salt # 注册/存储密码时 stored_hash, stored_salt = hash_password("Hello") # 登录验证时 input_hash, _ = hash_password("Hello", stored_salt) print(input_hash == stored_hash) # 输出True,验证通过
方案2:强制Fernet使用固定IV(不推荐,存在安全风险)
如果仅为测试需要让加密结果相同,可手动构造Fernet底层加密器并指定固定IV,但这会破坏Fernet的安全性(相同明文生成相同密文,易被破解):
import os from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes import base64 def encrypt_with_fixed_iv(password, salt=None, fixed_iv=b'\x00'*16): password = password.encode() if not salt: salt = os.urandom(64) kdf = PBKDF2HMAC( algorithm=hashes.SHA256, iterations=100000, length=32, salt=salt, backend=default_backend() ) key = kdf.derive(password) # 用固定IV构造AES-CBC加密器(Fernet底层实现) cipher = Cipher(algorithms.AES(key), modes.CBC(fixed_iv), backend=default_backend()) encryptor = cipher.encryptor() # 填充明文至AES块大小的倍数 pad_length = 16 - (len(password) % 16) padded_password = password + bytes([pad_length])*pad_length ciphertext = encryptor.update(padded_password) + encryptor.finalize() # 按Fernet格式打包结果 encrypted = base64.urlsafe_b64encode(b'\x80' + fixed_iv + ciphertext) return encrypted, salt x, salt2 = encrypt_with_fixed_iv("Hello") print(x) l, salt1 = encrypt_with_fixed_iv("Hello", salt2) print(l) # 输出与x完全相同 print(salt1 == salt2) # 输出True
内容的提问来源于stack exchange,提问作者Jordy Dongen
相关产品推荐
相关产品推荐

