int*_*why 1 python encryption hmac pycrypto cryptojs
在获取AES密码文本以解密时遇到一些麻烦.
在这个特定的场景中,我使用Crypto-JS在客户端加密数据,并使用PyCrypto在python服务器上解密.
encrypt.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);
Run Code Online (Sandbox Code Playgroud)
decrypt.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)
Run Code Online (Sandbox Code Playgroud)
我成功地重新计算了HMAC,但是当我尝试然后解密密码时,我得到了一些似乎用结果中的 加密的东西.
我收到了关于密文输入长度的错误,但是当我切换到修复它的CFB模式时,我认为这不是填充问题.
您的代码存在许多问题.
AES的块大小为128位,CFB模式需要IV的完整块.使用
var iv = CryptoJS.lib.WordArray.random(128/8);
Run Code Online (Sandbox Code Playgroud)在iv和hash变量是WordArray对象,但encrypted并非如此.当你强制它们通过连接它们(+)转换为字符串,iv并且hash是十六进制编码的,但是encrypted格式化为OpenSSL兼容格式和Base64编码.您需要访问该ciphertext属性以获取加密的WordArray:
var concat = iv + encrypted.ciphertext;
Run Code Online (Sandbox Code Playgroud)
和
var registrationKey = iv + encrypted.ciphertext + hash;
Run Code Online (Sandbox Code Playgroud)registrationKey是十六进制编码的.无需再使用Base64对其进行编码并使其更加膨胀:
var basemessage = registrationKey;
Run Code Online (Sandbox Code Playgroud)
如果要将十六进制编码转换registrationKey为base64编码,请使用:
var basemessage = CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64);
Run Code Online (Sandbox Code Playgroud)concat是IV和密文的十六进制编码字符串,因为你通过"添加"(+)iv和强制字符串化encrypted.该HmacSHA256()函数采用WordArray对象或字符串.当你传入一个字符串时,它将假设数据是UTF-8编码并尝试将其解码为UTF-8.您需要自己将数据解析为WordArray:
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
Run Code Online (Sandbox Code Playgroud)在CryptoJS.AES.encrypt()和CryptoJS.HmacSHA256()期望的关键无论是作为一个WordArray对象或字符串.和以前一样,如果密钥是作为字符串提供的,则假定UTF-8编码,这不是这里的情况.您最好自己将字符串解析为WordArrays:
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
Run Code Online (Sandbox Code Playgroud)你没有验证任何内容verify_hmac().您使用相同的密钥两次散列相同的数据.你需要做的是散列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
Run Code Online (Sandbox Code Playgroud)
后来在decrypt():
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
Run Code Online (Sandbox Code Playgroud)您需要正确切掉MAC.硬编码的60是一个坏主意.由于您使用的是SHA-256,因此MAC长度为32个字节,因此您可以这样做
hmac = enc[-32:]
cipher_text = enc[16:-32]
Run Code Online (Sandbox Code Playgroud)CFB模式实际上是一组类似的模式.实际模式由段大小决定.CryptoJS仅支持128位的段.因此,您需要告诉pycrypto使用与CryptoJS中相同的模式:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
Run Code Online (Sandbox Code Playgroud)
如果要使用段大小为8位的CFB模式(默认为pycrypto),可以在我的项目中使用CryptoJS中的CFB的修改版本: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));
Run Code Online (Sandbox Code Playgroud)
完整服务器代码:
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
Run Code Online (Sandbox Code Playgroud)