我目前正在使用 Python 在后端实现 AES 加密,但在确保前端和后端之间的兼容性方面遇到了一些问题。我需要帮助来集成前端 JavaScript 代码以使用它。
我的后端Python代码:
class Crypt():
def pad(self, data):
BLOCK_SIZE = 16
length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
return data + (chr(length)*length)
def unpad(self, data):
return data[:-(data[-1] if type(data[-1]) == int else ord(data[-1]))]
def bytes_to_key(self, data, salt, output=48):
assert len(salt) == 8, len(salt)
data += salt
key = sha256(data).digest()
final_key = key
while len(final_key) < output:
key = sha256(key + data).digest()
final_key += key
return final_key[:output]
def bytes_to_key_md5(self, data, salt, output=48):
assert len(salt) == 8, len(salt)
data += salt
key = md5(data).digest()
final_key = key
while len(final_key) < output:
key = md5(key + data).digest()
final_key += key
return final_key[:output]
def encrypt(self, message):
passphrase = "<secret passpharse value>".encode()
salt = Random.new().read(8)
key_iv = self.bytes_to_key_md5(passphrase, salt, 32+16)
key = key_iv[:32]
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
return base64.b64encode(b"Salted__" + salt + aes.encrypt(self.pad(message).encode()))
def decrypt(self, encrypted):
passphrase ="<secret passpharse value>".encode()
encrypted = base64.b64decode(encrypted)
assert encrypted[0:8] == b"Salted__"
salt = encrypted[8:16]
key_iv = self.bytes_to_key_md5(passphrase, salt, 32+16)
key = key_iv[:32]
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
return self.unpad(aes.decrypt(encrypted[16:])).decode().strip('"')
def base64_decoding(self, encoded):
base64decode = base64.b64decode(encoded)
return base64decode.decode()
crypt = Crypt()
test = "secret message to be send over network"
encrypted_message = crypt.encrypt(test)
print("Encryp msg:", encrypted_message)
decrypted_message = crypt.decrypt(encrypted_message)
print("Decryp:", decrypted_message)
这是我迄今为止在前端使用 React 和 CryptoJS 所做的尝试:
import React from "react";
import CryptoJS from 'crypto-js';
const DecryptEncrypt = () => {
function bytesToKey(passphrase, salt, output = 48) {
if (salt.length !== 8) {
throw new Error('Salt must be 8 characters long.');
}
let data = CryptoJS.enc.Latin1.parse(passphrase + salt);
let key = CryptoJS.SHA256(data).toString(CryptoJS.enc.Latin1);
let finalKey = key;
while (finalKey.length < output) {
data = CryptoJS.enc.Latin1.parse(key + passphrase + salt);
key = CryptoJS.SHA256(data).toString(CryptoJS.enc.Latin1);
finalKey += key;
}
return finalKey.slice(0, output);
}
const decryptData = (encryptedData, key) => {
const decodedEncryptedData = atob(encryptedData);
const salt = CryptoJS.enc.Hex.parse(decodedEncryptedData.substring(8, 16));
const ciphertext = CryptoJS.enc.Hex.parse(decodedEncryptedData.substring(16));
const keyIv = bytesToKey(key, salt.toString(), 32 + 16);
const keyBytes = CryptoJS.enc.Hex.parse(keyIv.substring(0, 32));
const iv = CryptoJS.enc.Hex.parse(keyIv.substring(32));
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: ciphertext },
keyBytes,
{ iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
return decrypted.toString(CryptoJS.enc.Utf8);
};
const encryptData = (data, key) => {
const salt = CryptoJS.lib.WordArray.random(8); // Generate random salt
const keyIv = bytesToKey(key, salt.toString(), 32 + 16);
const keyBytes = CryptoJS.enc.Hex.parse(keyIv.substring(0, 32));
const iv = CryptoJS.enc.Hex.parse(keyIv.substring(32));
const encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
const ciphertext = encrypted.ciphertext.toString(CryptoJS.enc.Hex);
const saltedCiphertext = "Salted__" + salt.toString(CryptoJS.enc.Hex) + ciphertext;
return btoa(saltedCiphertext);
};
const dataToEncrypt = 'Data to be sent over network';
const encryptionKey = "<secret passpharse value>";
const encryptedData = encryptData(dataToEncrypt, encryptionKey);
console.log("Encrypted data:", encryptedData);
const decryptedData = decryptData(encryptedData, encryptionKey);
console.log("Decrypted data:", decryptedData);
return (<>
Check
</>);
}
export default DecryptEncrypt;
我在确保前端和后端之间的兼容性方面遇到了一些问题。具体来说,我正在努力正确导出密钥和 IV,并以与后端实现相匹配的方式加密/解密数据。当我尝试将加密文本发送到后端时出现如下错误,解密时会抛出以下错误,
packages\Crypto\Cipher\_mode_cbc.py", line 246, in decrypt
raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
ValueError: Data must be padded to 16 byte boundary in CBC mode
我对在全栈应用程序中实现 AES 有点陌生,所以学习和尝试但仍然遇到这个问题。遇到类似问题或在 JavaScript 中实现加密/解密的人能否就如何修改我的前端代码以实现与后端的兼容性提供一些指导或建议?任何见解、更正或替代方法都会非常有帮助。
这比你想象的要容易得多。
Python 代码执行符合 OpenSSL 的加密/解密:
EVP_BytesToKey()
兼容的。它使用 MD5 作为摘要,迭代计数为 1 和 8 字节盐。Salted__
的串联组成,后跟 8 字节的盐,最后是实际的密文。CryptoJS 与 OpenSSL 兼容(参见此处),并且默认支持上述加密/解密。您所需要做的就是将密码作为字符串传递。
请注意,当在 Python 代码中使用
bytes_to_key()
而不是 bytes_to_key_md5()
时,必须在 CryptoJS 端显式指定 SHA256 作为摘要,因为 MD5 是默认值。
CryptoJS 示例代码:
// MD5 sample
var ciphertextFromPython = "U2FsdGVkX18lJwVCQIbRWqiIycIZg4LRZFHq+ORvygkE/umH1Il3m/yzgu3n9jVQhUikwXeURBW9yAjMawTk3A==";
var passphrase = "<secret passpharse value>";
var decrypted = CryptoJS.AES.decrypt(ciphertextFromPython, passphrase);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
var plaintext = "secret message to be send over network";
var passphrase = "<secret passpharse value>";
var ciphertextForPython = CryptoJS.AES.encrypt(plaintext, passphrase);
console.log(ciphertextForPython.toString()); // e.g. U2FsdGVkX18/aYM99XaqbT/GjFDAuNlGBMd2Wd7Vuum120DkmeItS7tJndPLbxDyNzEUBF28AOG5pOwLGvpSSA==
// SHA-256 sample
CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();
var ciphertextFromPython = "U2FsdGVkX189ft5ncnmOK/rJIB2fkdrfdWQCbf6DgbXkWMXw7yjX2oRXbDgZTIt4LibWBPamalnKCZl3l1VnWQ==";
var passphrase = "<secret passpharse value>";
var decrypted = CryptoJS.AES.decrypt(ciphertextFromPython, passphrase);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
var plaintext = "secret message to be send over network";
var passphrase = "<secret passpharse value>";
var ciphertextForPython = CryptoJS.AES.encrypt(plaintext, passphrase);
console.log(ciphertextForPython.toString()); // e.g. U2FsdGVkX188W7G1Xis9KZogKpVCvCVbDQHc1AIul+CSTjS8m+zdc4pPQ9jlunIP4jbTD49q82GV9ic/4HVNNA==
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
在上面的CryptoJS代码中,
ciphertextFromPython
是使用发布的Python代码生成的,ciphertextForPython
可以使用发布的Python代码解密。
第一个示例应用 MD5 作为摘要(默认)并对应于密钥派生函数
bytes_to_key_md5()
,第二个示例应用 SHA256(明确指定)并对应于 bytes_to_key()
。