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

如何实现Python与TypeScript的AES加解密结果一致?

问题描述

我使用以下TypeScript代码实现AES加解密:

const AES = require('crypto-js/aes');
const Utf8 = require('crypto-js/enc-utf8');

export const encrypt = (text, passphrase) => {
  return AES.encrypt(text, passphrase).toString();
};

export const decrypt = (ciphertext, passphrase) => {
  const bytes = AES.decrypt(ciphertext, passphrase);
  const originalText = bytes.toString(Utf8);
  return originalText;
};

需要在Python环境中解密TypeScript生成的密文,但当前基于cryptography库编写的代码无法得到正确结果:

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from os import urandom

def derive_key_iv(password: str, salt: bytes, key_length=32, iv_length=16):
    # Key derivation function
    backend = default_backend()
    kdf = Scrypt(
        salt=salt,
        length=key_length + iv_length,
        n=2**14,
        r=8,
        p=1,
        backend=backend
    )
    key_iv = kdf.derive(password.encode())
    return key_iv[:key_length], key_iv[key_length:key_length+iv_length]

def encrypt(message: str, password: str):
    salt = urandom(16)  # Generate a random salt
    key, iv = derive_key_iv(password, salt)
    backend = default_backend()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(algorithms.AES.block_size).padder()
    padded_data = padder.update(message.encode()) + padder.finalize()
    encrypted = encryptor.update(padded_data) + encryptor.finalize()
    return salt + encrypted  # prepend salt for use in decryption

def decrypt(token: bytes, password: str):
    salt = token[:16]  # Extract the salt from the beginning
    encrypted_message = token[16:]
    key, iv = derive_key_iv(password, salt)
    backend = default_backend()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
    decryptor = cipher.decryptor()
    padded_plaintext = decryptor.update(encrypted_message) + decryptor.finalize()
    unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
    plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
    return plaintext.decode()

已知CryptoJS的AES加密逻辑为:从密码派生32字节AES-256密钥和16字节初始化向量(IV),采用CBC模式、PKCS7填充,但调整代码后仍未解决问题,请问如何修改Python代码以正确解密?


解决方案

你的Python代码和CryptoJS的实现存在三处核心不匹配,修正后即可正确解密:

关键差异点

  1. 密钥派生算法:CryptoJS默认使用PBKDF2而非Scrypt,且固定参数为1000次迭代、SHA-256哈希、8字节salt
  2. 密文格式:CryptoJS输出的是Base64编码的OpenSSL兼容格式(前缀为Salted__ + 8字节salt + 密文),而非直接的字节拼接
  3. 编码一致性:密码和明文需严格使用UTF-8编码,与TypeScript代码中的Utf8处理匹配

修正后的解密代码

import base64
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.primitives import padding
from cryptography.hazmat.backends import default_backend

def decrypt_cryptojs_aes(ciphertext_b64: str, password: str):
    # 解码Base64密文并拆分格式组件
    ciphertext_bytes = base64.b64decode(ciphertext_b64)
    # 验证CryptoJS密文的OpenSSL格式前缀
    if ciphertext_bytes[:8] != b"Salted__":
        raise ValueError("无效的CryptoJS密文格式")
    # 提取8字节salt(CryptoJS固定使用8字节salt)
    salt = ciphertext_bytes[8:16]
    # 提取实际加密数据
    encrypted_data = ciphertext_bytes[16:]

    # 用PBKDF2派生密钥和IV,严格匹配CryptoJS参数
    backend = default_backend()
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=48,  # 32字节AES-256密钥 + 16字节IV
        salt=salt,
        iterations=1000,  # CryptoJS默认迭代次数
        backend=backend
    )
    key_iv = kdf.derive(password.encode('utf-8'))
    key = key_iv[:32]
    iv = key_iv[32:]

    # CBC模式解密
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
    decryptor = cipher.decryptor()
    padded_plaintext = decryptor.update(encrypted_data) + decryptor.finalize()

    # PKCS7去填充
    unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
    plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()

    return plaintext.decode('utf-8')

使用示例

假设TypeScript加密得到的密文为U2FsdGVkX1+M8eK7zL2bN5cR9tY3fH6jK1lO0pQ2sU4dF8gH0jK2lN4mB6vC8xZ0aS2dF4gH6jK8,密码为my-secret-passphrase,调用方式如下:

ciphertext = "U2FsdGVkX1+M8eK7zL2bN5cR9tY3fH6jK1lO0pQ2sU4dF8gH0jK2lN4mB6vC8xZ0aS2dF4gH6jK8"
password = "my-secret-passphrase"
print(decrypt_cryptojs_aes(ciphertext, password))

内容的提问来源于stack exchange,提问作者Kartik Watwani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 06:57:07