将前端 JavaScript 代码与 AES 加密集成时出现 ValueError:数据必须在 CBC 模式下填充到 16 字节边界

问题描述 投票:0回答:1

我目前正在使用 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 中实现加密/解密的人能否就如何修改我的前端代码以实现与后端的兼容性提供一些指导或建议?任何见解、更正或替代方法都会非常有帮助。

javascript python-3.x encryption aes cryptojs
1个回答
0
投票

这比你想象的要容易得多。

Python 代码执行符合 OpenSSL 的加密/解密:

  • 密钥派生是
    EVP_BytesToKey()
    兼容的。它使用 MD5 作为摘要,迭代计数为 1 和 8 字节盐。
  • 应用 CBC 模式下的加密/解密 AES-256 和 PKCS#7 填充。
  • 结果以Base64编码的OpenSSL格式返回。 OpenSSL 格式由 ASCII 编码
    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()

© www.soinside.com 2019 - 2024. All rights reserved.