Python 3 Fernet使用轻微修改的密钥可成功解密的技术疑问
以下是测试用的Python代码:
def encrypt_message(message, key) -> bytes: """Encrypts a message using Fernet.""" f = Fernet(key) encrypted_message = f.encrypt(message.encode()) return encrypted_message def decrypt_message(encrypted_message, key) -> str: """Decrypts a message using Fernet.""" f = Fernet(key) decrypted_message = f.decrypt(encrypted_message).decode() return decrypted_message message_ = "This is a secret message!" encrypted = encrypt_message(message_, b"HeYt_XX4wRLvnDP9fC9-7hlMgasQ270tpWSrjKy0jRE=") # 每次加密的密文会变化,这是正常现象 print("Encrypted:", encrypted) decrypted = decrypt_message(encrypted, b"HeYt_XX4wRLvnDP9fC9-7hlMgasQ270tpWSrjKy0jRF=") print("Decrypted:", decrypted)
运行后发现一个奇怪的现象:调用decrypt_message时,把解密密钥末尾的E替换成F/G/H,解密依然能成功;但替换成I时解密失败。按对称加密的常识,加解密必须用完全相同的密钥,对此感到困惑,寻求解释。
测试输出结果:
Encrypted: b'gAAAAABmcc7eNiwRP4lYl7gM0mthNdlvYsPUs_nttJMchmZ_mfJNMS4D2BC3QZHZZiZ7vrPhPwvBqK2DDOXSuB5wQkk9t-tU0PYefGvNy2IoDZNGNQMcdc0='
Decrypted: This is a secret message!
Process finished with exit code 0
你遇到的这个现象,核心是Fernet密钥的Base64编码特性在起作用:
Fernet密钥的本质:Fernet要求密钥是32字节的二进制数据,我们传入的字符串是这个二进制密钥经过URL-safe Base64编码后的结果。32字节等于256位,Base64每4个字符对应3字节(24位),256位的二进制数据需要43个Base64字符(42个字符对应252位,最后1个字符补充剩下的4位),再加上1个
=填充符,凑成44个字符的标准格式。末尾字符的解码逻辑:你修改的末尾字符(E/F/G/H/I)属于Base64编码的最后一个有效字符,它只需要提供4位数据就能补全256位,剩下的2位会被填充符
=忽略。看Base64的字符编码规则(URL-safe版本和标准一致,仅替换+为-、/为_):- E对应二进制
000100,取前4位是0001 - F对应
000101,前4位0001 - G对应
000110,前4位0001 - H对应
000111,前4位0001 - I对应
001000,前4位0010
所以当你把E换成F/G/H时,解码得到的32字节二进制密钥和原密钥完全一致——因为我们只取了这些字符的前4位,后2位被填充忽略了。而换成I时,前4位变成了
0010,解码后的二进制密钥和原密钥产生了差异,导致Fernet的HMAC签名验证失败,解密就无法成功了。- E对应二进制
对称加密的规则没有被打破:你以为用了不同的密钥,但实际上Fernet真正使用的二进制密钥是一样的,这才是解密成功的原因。一旦二进制密钥真的不同(比如换成I的情况),对称加密“加解密密钥必须完全一致”的规则就生效了,解密必然失败。
内容的提问来源于stack exchange,提问作者OGM

