Skip to main content
 首页 » 编程设计

python之使用 PyCrypto 解密 AES 和 HMAC

2025年05月04日14lyhabc

在解密 AES 密文时遇到一些麻烦。

在这个特定场景中,我在客户端使用 Crypto-JS 加密数据,然后在 python 服务器上使用 PyCrypto 将其解密。

加密.js:

  var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'; 
  var data = 'mytext'; 
 
  var masterKey = CryptoJS.SHA256(password).toString(); 
 
  // Derive keys for AES and HMAC 
  var length = masterKey.toString().length / 2 
  var encryptionKey = masterKey.substr(0, length); 
  var hmacKey = masterKey.substr(length); 
 
  var iv = CryptoJS.lib.WordArray.random(64/8); 
 
  var encrypted = CryptoJS.AES.encrypt( 
    data, 
    encryptionKey, 
    { 
      iv: iv, 
      mode: CryptoJS.mode.CFB 
    } 
  ); 
 
  var concat = iv + encrypted; 
 
  // Calculate HMAC using iv and cipher text 
  var hash = CryptoJS.HmacSHA256(concat, hmacKey); 
 
  // Put it all together 
  var registrationKey = iv + encrypted + hash; 
 
  // Encode in Base64 
  var basemessage = btoa(registrationKey); 

解密.py:

class AESCipher: 
    def __init__(self, key): 
        key_hash = SHA256.new(key).hexdigest() 
        # Derive keys 
        encryption_key = key_hash[:len(key_hash)/2] 
        self.key = encryption_key             
        self.hmac_key = key_hash[len(key_hash)/2:] 
 
 
    def verify_hmac(self, input_cipher, hmac_key): 
        # Calculate hash using inputted key 
        new_hash = HMAC.new(hmac_key, digestmod=SHA256) 
        new_hash.update(input_cipher) 
        digest = new_hash.hexdigest() 
 
        # Calculate hash using derived key from local password 
        local_hash = HMAC.new(self.hmac_key, digestmod=SHA256) 
        local_hash.update(input_cipher) 
        local_digest = local_hash.hexdigest() 
 
        return True if digest == local_digest else False 
 
 
    def decrypt(self, enc): 
        enc = base64.b64decode(enc) 
        iv = enc[:16] 
        hmac = enc[60:] 
        cipher_text = enc[16:60] 
 
        # Verify HMAC using concatenation of iv + cipher like in js 
        verified_hmac = self.verify_hmac((iv+cipher_text), self.hmac_key) 
 
        if verified_hmac: 
            cipher = AES.new(self.key, AES.MODE_CFB, iv) 
            return cipher.decrypt(cipher_text) 
 
 
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP' 
 
input = 'long base64 registrationKey...' 
 
cipher = AESCipher(password) 
decrypted = cipher.decrypt(input) 

我成功地重新计算了 HMAC,但是当我尝试然后解密密码时,我得到的结果似乎是用 � 加密的。

我收到有关密文输入长度的错误,但当我切换到 CFB 模式时修复了它,所以我认为这不是填充问题。

请您参考如下方法:

你的代码有很多问题。

客户端(JavaScript):

  • AES 的 block 大小为 128 位,而 CFB 模式需要完整 block 用于 IV。使用

    var iv = CryptoJS.lib.WordArray.random(128/8); 
    
  • iv 和hash 变量是 WordArray 对象,但 encrypted 不是。当您通过连接它们 (+) 强制将它们转换为字符串时,iv 和 hash 是十六进制编码的,但 是加密的 采用 OpenSSL 兼容格式和 Base64 编码格式。您需要访问 ciphertext 属性以获取加密的 WordArray:

    var concat = iv + encrypted.ciphertext; 
    

    和

    var registrationKey = iv + encrypted.ciphertext + hash; 
    
  • registrationKey 是十六进制编码的。无需再用 Base64 对其进行编码并使其更加膨胀:

    var basemessage = registrationKey; 
    

    如果要将十六进制编码的 registrationKey 转换为 base64 编码,请使用:

    var basemessage = CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64); 
    
  • concat 是 IV 和密文的十六进制编码字符串,因为您通过“添加”(+) iv 和加密。 HmacSHA256() 函数采用 WordArray 对象或字符串。当你传入一个字符串时,它会假定数据是 UTF-8 编码的,并尝试将其解码为 UTF-8。您需要自己将数据解析为 WordArray:

    var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey); 
    
  • CryptoJS.AES.encrypt() 和 CryptoJS.HmacSHA256() 期望 key 是 WordArray 对象或字符串。和以前一样,如果 key 以字符串形式提供,则假定为 UTF-8 编码,而此处不是这种情况。您最好自己将字符串解析为 WordArrays:

    var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length)); 
    var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length)); 
    

服务器(Python):

  • 您没有在 verify_hmac() 中验证任何内容。您使用相同的 key 对相同的数据进行两次哈希处理。您需要做的是对 IV+密文进行哈希处理,并将结果与​​您从完整密文中切出的哈希值(称为标签或 HMAC 标签)进行比较。

    def verify_hmac(self, input_cipher, mac): 
        # Calculate hash using derived key from local password 
        local_hash = HMAC.new(self.hmac_key, digestmod=SHA256) 
        local_hash.update(input_cipher) 
        local_digest = local_hash.digest() 
     
        return mac == local_digest 
    

    然后在 decrypt() 中:

    verified_hmac = self.verify_hmac((iv+cipher_text), hmac) 
    
  • 您需要正确分割 MAC。硬编码的 60 不是一个好主意。由于您使用的是 SHA-256,因此 MAC 的长度为 32 字节,因此您可以这样做

    hmac = enc[-32:] 
    cipher_text = enc[16:-32] 
    
  • CFB模式实际上是一组相似的模式。实际模式由段大小决定。 CryptoJS 只支持 128 位的段。所以你需要告诉 pycrypto 使用与 CryptoJS 中相同的模式:

    cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128) 
    

    如果您想使用段大小为 8 位的 CFB 模式(默认为 pycrypto),您可以在我的项目的 CryptoJS 中使用修改后的 CFB 版本:Extension for CryptoJS

完整的客户端代码:

var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'; 
var data = 'mytext'; 
 
var masterKey = CryptoJS.SHA256(password).toString(); 
var length = masterKey.length / 2 
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length)); 
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length)); 
 
var iv = CryptoJS.lib.WordArray.random(128/8); 
 
var encrypted = CryptoJS.AES.encrypt( 
    data, 
    encryptionKey, 
    { 
      iv: iv, 
      mode: CryptoJS.mode.CFB 
    } 
); 
 
var concat = iv + encrypted.ciphertext;  
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey); 
var registrationKey = iv + encrypted.ciphertext + hash; 
console.log(CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64)); 

完整服务器代码:

from Crypto.Cipher import AES 
from Crypto.Hash import HMAC, SHA256 
import base64 
import binascii 
 
class AESCipher: 
    def __init__(self, key): 
        key_hash = SHA256.new(key).hexdigest() 
        self.hmac_key = binascii.unhexlify(key_hash[len(key_hash)/2:]) 
        self.key = binascii.unhexlify(key_hash[:len(key_hash)/2]) 
 
    def verify_hmac(self, input_cipher, mac): 
        local_hash = HMAC.new(self.hmac_key, digestmod=SHA256) 
        local_hash.update(input_cipher) 
        local_digest = local_hash.digest() 
 
        return SHA256.new(mac).digest() == SHA256.new(local_digest).digest() # more or less constant-time comparison 
 
    def decrypt(self, enc): 
        enc = base64.b64decode(enc) 
        iv = enc[:16] 
        hmac = enc[-32:] 
        cipher_text = enc[16:-32] 
 
        verified_hmac = self.verify_hmac((iv+cipher_text), hmac) 
 
        if verified_hmac: 
            cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128) 
            return cipher.decrypt(cipher_text) 
        else: 
            return 'Bad Verify' 
 
 
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP' 
 
input = "btu0CCFbvdYV4B/j7hezAra6Q6u6KB8n5QcyA32JFLU8QRd+jLGW0GxMQsTqxaNaNkcU2I9r1ls4QUPUpaLPQg==" 
 
obj = AESCipher(password) 
decryption = obj.decrypt(input) 
 
print 'Decrypted message:', decryption