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

使用window.crypto.subtle加密,C#解密时认证标签不匹配问题求助

JS端window.crypto.subtle加密,C#解密时认证标签不匹配问题

我想用window.crypto.subtle实现加密,在C#中完成解密。目前JS端的加解密功能正常,但C#端计算出的认证标签和输入不匹配。我不确定是否可以用任意12字节作为盐,也不清楚是否必须派生密码。

JS代码

export async function deriveKey(password, salt) {
  const buffer = utf8Encoder.encode(password);
  const key = await crypto.subtle.importKey(
    'raw',
    buffer,
    { name: 'PBKDF2' },
    false,
    ['deriveKey'],
  );

  const privateKey = crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      hash: { name: 'SHA-256' },
      iterations,
      salt,
    },
    key,
    {
      name: 'AES-GCM',
      length: 256,
    },
    false,
    ['encrypt', 'decrypt'],
  );

  return privateKey;
}
const buff_to_base64 = (buff) => btoa(String.fromCharCode.apply(null, buff));
const base64_to_buf = (b64) => Uint8Array.from(atob(b64), (c) => c.charCodeAt(null));

export async function encrypt(key, data) {
  const salt = crypto.getRandomValues(new Uint8Array(12));
  const iv = crypto.getRandomValues(new Uint8Array(12));

  console.log('encrypt');
  console.log('iv', iv);
  console.log('salt', salt);

  const buffer = new TextEncoder().encode(data);

  const privatekey = await deriveKey(key, salt);

  const encrypted = await crypto.subtle.encrypt(
    {
      name: 'AES-GCM',
      iv,
      tagLength: 128,
    },
    privatekey,
    buffer,
  );

  const bytes = new Uint8Array(encrypted);
  console.log('concat');

  const buff = new Uint8Array(iv.byteLength + encrypted.byteLength + salt.byteLength);
  buff.set(iv, 0);
  buff.set(salt, iv.byteLength);
  buff.set(bytes, iv.byteLength + salt.byteLength);

  console.log('iv', iv);
  console.log('salt', salt);
  console.log('buff', buff);

  const base64Buff = buff_to_base64(buff);
  console.log(base64Buff);
  return base64Buff;
}

export async function decrypt(key, data) {
  console.log('decryption');
  console.log('buff', base64_to_buf(data));

  const d = base64_to_buf(data);
  const iv = d.slice(0, 12);
  const salt = d.slice(12, 24);
  const ec = d.slice(24);

  console.log('iv', iv);
  console.log('salt', salt);
  console.log(ec);

  const decrypted = await window.crypto.subtle.decrypt(
    {
      name: 'AES-GCM',
      iv,
      tagLength: 128,
    },
    await deriveKey(key, salt),
    ec,
  );

  return new TextDecoder().decode(new Uint8Array(decrypted));
}

C#原代码

Span<byte> encryptedData = Convert.FromBase64String(enc).AsSpan();
Span<byte> nonce = encryptedData[..12];
Span<byte> salt = encryptedData.Slice(12, 12);
Span<byte> data = encryptedData.Slice(12 + 12, encryptedData.Length - 16 - 12 - 12);
Span<byte> tag = encryptedData[^16..];

Span<byte> result = new byte[data.Length];

using Rfc2898DeriveBytes pbkdf2 = new(Encoding.UTF8.GetBytes(password), salt.ToArray(), 1000, HashAlgorithmName.SHA256);
using AesGcm aes = new(pbkdf2.GetBytes(16));

aes.Decrypt(nonce, data, tag, result);

问题分析与修复

1. 密钥长度不匹配

JS中通过PBKDF2派生的是256位AES密钥(length: 256),但C#中pbkdf2.GetBytes(16)仅生成128位密钥,两者长度不一致导致解密失败。需要改为生成32字节(256位)密钥。

2. 密文与标签拆分错误

JS中encrypted是AES-GCM加密后的完整结果,包含密文+16字节认证标签。原C#代码的切片逻辑错误,正确的拆分方式是:

  • 从索引24开始的全部内容是「密文+标签」
  • 密文:取前总长度-16字节
  • 标签:取最后16字节

3. 迭代次数一致性

确保JS中iterations变量的值与C#中的1000完全一致,否则派生的密钥会不同。

关于盐的疑问

12字节的盐是完全合规的,PBKDF2要求盐至少8字节,随机生成的12字节盐符合安全标准,无需担心。


修复后的C#代码

Span<byte> encryptedData = Convert.FromBase64String(enc).AsSpan();
Span<byte> nonce = encryptedData[..12]; // AES-GCM的IV对应nonce
Span<byte> salt = encryptedData.Slice(12, 12);
Span<byte> ciphertextWithTag = encryptedData.Slice(24);
Span<byte> ciphertext = ciphertextWithTag[..^16];
Span<byte> tag = ciphertextWithTag[^16..];

Span<byte> result = new byte[ciphertext.Length];

// 派生256位AES密钥
using Rfc2898DeriveBytes pbkdf2 = new(Encoding.UTF8.GetBytes(password), salt.ToArray(), 1000, HashAlgorithmName.SHA256);
using AesGcm aes = new(pbkdf2.GetBytes(32)); // 32字节=256位

aes.Decrypt(nonce, ciphertext, tag, result);

额外注意事项

  • 确认JS中的utf8Encoder是new TextEncoder(),与C#的Encoding.UTF8编码一致。
  • 如果JS中iterations不是1000,需要同步修改C#中的迭代次数参数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:55:27