这个用于解密 AES-CCM 加密字符串的 JS 函数的 Python 等价物是什么?

What is the Python equivalent of this JS function to decrypt an AES-CCM-encrypted string?

我想在 Python 3.

中解密 AES 加密字符串(CCM 模式)

以下使用 sjcl 库的 JavaScript 代码工作正常:

const sjcl = require('sjcl');

const key = "ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640";
const keyArray = sjcl.codec.hex.toBits(key);
const iv = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(key.substr(0,16))); 
const params = {
    "iv": iv,
    "v": 1,
    "iter": 1000,
    "ks": 256,
    "ts": 128,
    "mode": "ccm",
    "adata": "",
    "cipher": "aes",
    "salt": "",
};

function encrypt(data) {
    const ct = JSON.parse(sjcl.encrypt(keyArray, data, params)).ct;
    return sjcl.codec.hex.fromBits(sjcl.codec.base64.toBits(ct));
}

function decrypt(data) {
    const ct = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(data));
    const paramsWithCt = JSON.stringify({ ...params, ...{ "ct": ct } });
    return sjcl.decrypt(keyArray, paramsWithCt);
}

let ct = encrypt("my secret string");
console.log("Cipher Text: " + ct);

let plain = decrypt(ct);
console.log("Plain Text: " + plain);

输出:

$ npm i sjcl
$ node index.js
Cipher Text: fa90bcdedbfe7ba89b69216e352a90fa57a63871fc4da7e69ab7f897f427f8e3
Plain Text: my secret string

我可以使用哪个库在 Python 中执行相同的操作?

我尝试使用 pycryptodome library,但它接受一组不同的参数:

sjcl 对 4 字节字数组进行操作。使用 sjcl.codec.hex.toBits() 十六进制编码的密钥被转换成这样的数组。密钥的前 8 个字节(16 个十六进制数字)用作随机数。
密钥大小、标签大小、算法和模式由 params 对象确定。 params 对象还包含用于密钥派生的参数,例如itersalt 等),但这些在这里被忽略,因为键是作为数组而不是字符串传递的。
Nonce 和密文在 params 对象中通过 Base64 编码传递。

密文是实际密文和标签按顺序拼接而成,也必须按此格式传递给解密
sjcl 处理连接的密文和标签,而 PyCryptodome 分别处理两者。除此之外,Python 中的加密和解密很简单 PyCryptodome:

from Crypto.Cipher import AES

data = b'my secret string'
key = bytes.fromhex('ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640')
nonce = bytes.fromhex('ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640')[:8]

# Encryption 
cipher = AES.new(key, AES.MODE_CCM, nonce)
ciphertext, tag = cipher.encrypt_and_digest(data)

ciphertextTagHex = ciphertext.hex() + tag.hex()
print(ciphertextTagHex) # fa90bcdedbfe7ba89b69216e352a90fa57a63871fc4da7e69ab7f897f427f8e3

# Decryption
ciphertextTag = bytes.fromhex(ciphertextTagHex)
ciphertext = ciphertextTag[:-16]
tag = ciphertextTag[-16:]

cipher = AES.new(key, AES.MODE_CCM, nonce)
try:
    decrypted = cipher.decrypt_and_verify(ciphertext, tag)
    print(decrypted.decode('utf-8')) # my secret string
except ValueError:
    print('Decryption failed')

请注意,从密钥中导出随机数是不安全的。对于 CCM,s 尤其如此。例如RFC4309, p. 3, last section:

AES CCM employs counter mode for encryption. As with any stream cipher, reuse of the same IV value with the same key is catastrophic.

相反,应为每次加密随机生成随机数。 nonce 不是秘密的,通常在字节级别与密文连接,通常为 nonce|ciphertext|tag.