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

如何在Web Crypto API中将签名与公钥(ArrayBuffer)转为字符串存储

问题

我正在使用Web Crypto API进行数据签名,目前已获得ArrayBuffer类型的公钥和签名,需要将它们分别存储到对应文件中:

  • 公钥以字符串格式存入"public_key"文件
  • 签名以字符串格式存入"signature"文件

我是该领域新手,不清楚便于Java后端等其他语言/工具处理的合适文件格式。公钥采用SPKI格式的PEM字符串似乎可行,但不知道签名的处理方法,已编写如下代码:

const generateKeyPair = async function() {
    return crypto.subtle.generateKey(
        {
            name: "ECDSA",
            namedCurve: "P-384",
        },
        true,
        ["sign", "verify"]
    );
};

const sign = async function(privateKey: CryptoKey, buffer: BufferSource) {
    return crypto.subtle.sign({
      name: privateKey.algorithm.name,
      hash: 'SHA-512'
    }, privateKey, buffer);
};

const exportKeyAsSPKI = async function(cryptoKey: CryptoKey) {
    return crypto.subtle.exportKey("spki", cryptoKey)
};

const spkiToPEM = function(buffer: ArrayBuffer) {
    const asString = String.fromCharCode.apply(null, new Uint8Array(buffer));
    const asBase64 = btoa(asString);
    
    return `-----BEGIN PUBLIC KEY-----\n${asBase64}\n-----END PUBLIC KEY-----`;
};

(async () => {
    // Generate keys 
    
    const { privateKey, publicKey } = await generateKeyPair();

    // Sign data

    const data = { foo: 'bar' };
    const stringifiedData = JSON.stringify(data);
    const buffer = new TextEncoder().encode(stringifiedData);
    
    const signature = await sign(privateKey, buffer);

    // Convert public key to string to store into file

    const publicKeyAsSPKIFormat = await exportKeyAsSPKI(publicKey);
    const pemEncodedKey = spkiToPEM(publicKeyAsSPKIFormat);

    // Convert signature to string to store into file

    // ... TODO ...

    // DONE

    console.log("Public key:");
    console.log(pemEncodedKey);

    console.log("Signature:");
    console.log("todo...");
})();

请问签名应该如何转换为可存储的字符串?


解决方案

1. 基础转换:二进制签名转Base64字符串

Web Crypto返回的签名是ArrayBuffer二进制数据,要转成可存储的字符串,最通用的方式是Base64编码,几乎所有语言(包括Java)都能直接解码处理。

你可以用和公钥转换类似的逻辑实现:

const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
  const uint8Array = new Uint8Array(buffer);
  const asString = String.fromCharCode.apply(null, uint8Array);
  return btoa(asString);
};

在代码中替换TODO部分:

// Convert signature to string to store into file
const signatureBase64 = arrayBufferToBase64(signature);

2. 兼容Java后端的关键:签名格式转换

注意:Web Crypto的ECDSA签名返回的是IEEE P1363格式(直接拼接r和s的二进制,P-384曲线下每个部分48字节,总长度96字节),而Java默认的Signature类验证的是ASN.1 DER格式的签名(带有ASN.1结构的编码)。如果直接传递Base64的P1363格式签名给Java,会验证失败。

需要把P1363格式转成DER格式,再转Base64,实现函数如下:

const ecdsaP1363ToDer = (signature: ArrayBuffer) => {
  const uint8 = new Uint8Array(signature);
  const curveByteLength = uint8.length / 2; // P-384是48字节,总长度96
  let r = uint8.slice(0, curveByteLength);
  let s = uint8.slice(curveByteLength);

  // 移除r和s开头的0字节(ASN.1要求整数是最小长度)
  while (r[0] === 0) r = r.slice(1);
  while (s[0] === 0) s = s.slice(1);

  // 如果最高位是1,加一个0字节避免被解析为负数
  if (r[0] >= 0x80) r = new Uint8Array([0, ...r]);
  if (s[0] >= 0x80) s = new Uint8Array([0, ...s]);

  // 构建ASN.1 DER结构:SEQUENCE(INTEGER(r), INTEGER(s))
  const derHeader = new Uint8Array([
    0x30, // SEQUENCE标签
    2 + r.length + 2 + s.length, // 总长度
    0x02, // INTEGER标签
    r.length, // r的长度
    ...r,
    0x02, // INTEGER标签
    s.length, // s的长度
    ...s
  ]);

  return derHeader.buffer;
};

然后在代码中使用:

// Convert signature to DER format first, then Base64
const derSignature = ecdsaP1363ToDer(signature);
const signatureBase64 = arrayBufferToBase64(derSignature);

这样得到的Base64字符串,Java后端可以直接用Base64.getDecoder().decode()解码,再通过Signature.verify()方法验证。

3. 可选:给签名加PEM包装(非必须)

如果想和公钥的PEM格式统一,也可以给Base64的签名加上PEM头部尾部:

const signatureToPEM = (signatureBase64: string) => {
  return `-----BEGIN ECDSA SIGNATURE-----\n${signatureBase64}\n-----END ECDSA SIGNATURE-----`;
};

不过多数场景下,直接存储Base64字符串就足够了,Java处理起来也更简便。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:34:57