You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 10:52:51