JavaScript Subtle Crypto AES-CTR无填充问题:JS加密Python解密适配咨询
AES-CTR 模式无需填充,适配 Python 解密的解决方案
首先明确核心问题:AES-CTR 是流加密模式,本身不需要任何填充,密文长度必然和明文完全一致。Subtle Crypto 的实现符合密码学标准,你之前的误解是把需要填充的分组加密模式(比如 AES-CBC)的特性套用到了 CTR 模式上。
以下是两种适配方案,优先推荐方案1:
方案1:遵循 CTR 模式特性,修改 Python 解密逻辑
既然使用了 CTR 模式,就按它的标准实现解密,去掉 Python 端的 PKCS#7 填充移除步骤。
用 Python cryptography 库的示例代码如下(假设你已经从 JS 端拿到了 salt、iv 和密文的十六进制字符串):
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import binascii # 替换为实际从JS获取的参数 password = b"password" salt_hex = "JS生成的salt十六进制字符串" iv_hex = "JS生成的iv十六进制字符串" ciphertext_hex = "JS输出的密文十六进制字符串" # 转换为字节数据 salt = binascii.unhexlify(salt_hex) iv = binascii.unhexlify(iv_hex) ciphertext = binascii.unhexlify(ciphertext_hex) # 推导密钥,和JS端参数完全对齐 kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, # AES-256对应32字节密钥 salt=salt, iterations=10000, backend=default_backend() ) key = kdf.derive(password) # AES-CTR解密,无需处理填充 cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) decryptor = cipher.decryptor() plaintext = decryptor.update(ciphertext) + decryptor.finalize() print(plaintext.decode('utf-8')) # 输出: plain
注意:JS 端代码需要补充输出 salt 和 iv,解密必须依赖这两个参数,否则无法正确推导密钥。
方案2:手动添加 PKCS#7 填充(仅用于兼容原有逻辑)
如果因历史原因必须保留 PKCS#7 填充逻辑,可以在 JS 端加密前手动给明文加填充,Python 端解密后再移除填充。
JS 端修改代码,添加填充逻辑:
// PKCS#7 填充函数 function pkcs7Pad(buffer, blockSize = 16) { const paddingLength = blockSize - (buffer.length % blockSize); const padding = new Uint8Array(paddingLength).fill(paddingLength); return new Uint8Array([...buffer, ...padding]); } function bufferToHex(buffer) { return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, '0')).join (''); } window.crypto.subtle.importKey('raw', new TextEncoder().encode('password'), 'PBKDF2', false, ['deriveKey']).then(function(key) { var salt = window.crypto.getRandomValues(new Uint8Array(32)); console.log('salt:', bufferToHex(salt)); // 传递给Python window.crypto.subtle.deriveKey({ name: 'PBKDF2', salt: salt, iterations: 10000, hash: 'SHA-256' }, key, { name: 'AES-CTR', length: 256 }, false, ['encrypt']).then(function(ekey) { var iv = window.crypto.getRandomValues(new Uint8Array(16)); console.log('iv:', bufferToHex(iv)); // 传递给Python const plaintext = new TextEncoder().encode('plain'); const paddedPlaintext = pkcs7Pad(plaintext); // 手动添加填充 window.crypto.subtle.encrypt({ name: 'AES-CTR', counter: iv, length: 64 }, ekey, paddedPlaintext).then(function(ciphertext) { console.log('ciphertext:', bufferToHex(ciphertext)); // 此时密文长度为16字节 }); }); });
Python 端解密后移除填充:
在方案1的代码基础上,添加去填充函数:
def pkcs7_unpad(data): padding_length = data[-1] return data[:-padding_length] # 解密后执行去填充 plaintext = decryptor.update(ciphertext) + decryptor.finalize() unpadded_plaintext = pkcs7_unpad(plaintext) print(unpadded_plaintext.decode('utf-8'))
重要提醒
CTR 模式下添加填充是完全冗余的操作,不仅增加了数据体积,还没有任何安全收益,因此优先推荐方案1。
内容的提问来源于stack exchange,提问作者chrixm
相关产品推荐
相关产品推荐

