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

如何在JavaScript中解密无MAC的ChaCha20-Poly1305加密字符串?

问题与解决方案

问题描述

在Python中,可通过PyCryptodome库不使用MAC(标签)实现ChaCha20-Poly1305的加解密,代码示例如下:

from Crypto.Cipher import ChaCha20_Poly1305

key = '20d821e770a6d3e4fc171fd3a437c7841d58463cb1bc7f7cce6b4225ae1dd900' # 随机生成的密钥
nonce = '18c02beda4f8b22aa782444a' # 随机生成的12字节nonce

def decode(ciphertext):
    cipher = ChaCha20_Poly1305.new(key=bytes.fromhex(key), nonce=bytes.fromhex(nonce))
    plaintext = cipher.decrypt(bytes.fromhex(ciphertext))
    return plaintext.decode('utf-8')

def encode(plaintext):
    cipher = ChaCha20_Poly1305.new(key=bytes.fromhex(key), nonce=bytes.fromhex(nonce))
    ciphertext = cipher.encrypt(plaintext.encode('utf-8'))
    return ciphertext.hex()

encrypted = encode('abcdefg123')
print(encrypted) # 输出: ab6cf9f9e0cf73833194
print(decode(encrypted)) # 输出: abcdefg123

但在React的JavaScript环境中,尝试libsodium-wrappers、js-chacha20等库时,均要求提供MAC才能解密。已知:

  • 待解密字符串已通过上述方式加密,无法重新生成
  • 直接用纯ChaCha20算法解密失败,因为同密钥和nonce下,纯ChaCha20的加密输出与上述方式不同

注:此操作仅用于教育用途,已知无MAC的方式安全性更低。

问题根源

PyCryptodome中ChaCha20_Poly1305的encrypt/decrypt方法在不处理MAC标签时,实际遵循RFC7539中ChaCha20-Poly1305的流加密规则:

  • 使用12字节长度的nonce
  • ChaCha20流的计数器从1开始(而非纯ChaCha20默认的0)

普通纯ChaCha20库通常默认使用8字节nonce + 计数器从0开始,这导致加密输出不一致,无法直接解密。

解决方案

在JS中手动模拟该逻辑,以下是两种可行实现:

方式1:使用libsodium-wrappers实现

libsodium支持自定义ChaCha20的计数器起始值,可直接匹配PyCryptodome的行为:

import sodium from 'libsodium-wrappers';

// 初始化libsodium
await sodium.ready();

const KEY = sodium.from_hex('20d821e770a6d3e4fc171fd3a437c7841d58463cb1bc7f7cce6b4225ae1dd900');
const NONCE = sodium.from_hex('18c02beda4f8b22aa782444a'); // 12字节nonce

// 加密:模拟PyCryptodome的无MAC加密逻辑
function encode(plaintext) {
    const plaintextBytes = sodium.from_string(plaintext);
    // 使用crypto_stream_chacha20_xor_ic指定计数器起始值为1
    const ciphertextBytes = sodium.crypto_stream_chacha20_xor_ic(plaintextBytes, null, NONCE, 1, KEY);
    return sodium.to_hex(ciphertextBytes);
}

// 解密:与加密逻辑完全一致
function decode(ciphertextHex) {
    const ciphertextBytes = sodium.from_hex(ciphertextHex);
    const plaintextBytes = sodium.crypto_stream_chacha20_xor_ic(ciphertextBytes, null, NONCE, 1, KEY);
    return sodium.to_string(plaintextBytes);
}

// 测试验证
const encrypted = encode('abcdefg123');
console.log(encrypted); // 输出: ab6cf9f9e0cf73833194
console.log(decode(encrypted)); // 输出: abcdefg123

方式2:使用@stablelib/chacha20实现

若不想依赖libsodium,可使用@stablelib/chacha20库,它支持自定义nonce长度和计数器起始值:

import { ChaCha20 } from '@stablelib/chacha20';
import { fromHex, toHex, fromString, toString } from '@stablelib/encoding';

const KEY = fromHex('20d821e770a6d3e4fc171fd3a437c7841d58463cb1bc7f7cce6b4225ae1dd900');
const NONCE = fromHex('18c02beda4f8b22aa782444a'); // 12字节nonce

function encode(plaintext) {
    const plaintextBytes = fromString(plaintext);
    // 初始化ChaCha20时指定计数器从1开始
    const cipher = new ChaCha20(KEY, NONCE, 1);
    const ciphertextBytes = cipher.secretXOR(plaintextBytes);
    return toHex(ciphertextBytes);
}

function decode(ciphertextHex) {
    const ciphertextBytes = fromHex(ciphertextHex);
    const cipher = new ChaCha20(KEY, NONCE, 1);
    const plaintextBytes = cipher.secretXOR(ciphertextBytes);
    return toString(plaintextBytes);
}

// 测试验证
const encrypted = encode('abcdefg123');
console.log(encrypted); // 输出: ab6cf9f9e0cf73833194
console.log(decode(encrypted)); // 输出: abcdefg123

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 20:25:53