使用 Node.js/crypto 生成 ECDSA 签名

问题描述 投票:0回答:2

我有代码使用

jsrsasign
和 JWK 格式的密钥生成 ECDSA 签名的串联 (r-s) 签名:

const sig = new Signature({ alg: 'SHA256withECDSA' });
sig.init(KEYUTIL.getKey(key));
sig.updateHex(dataBuffer.toString('hex'));
const asn1hexSig = sig.sign();
const concatSig = ECDSA.asn1SigToConcatSig(asn1hexSig);
return new Buffer(concatSig, 'hex');

似乎有效。我也有使用

SubtleCrypto
来实现同样的事情的代码:

importEcdsaKey(key, 'sign') // importKey JWK -> raw
.then((privateKey) => subtle.sign(
    { name: 'ECDSA', hash: {name: 'SHA-256'} },
    privateKey,
    dataBuffer
))

它们都返回 128 字节缓冲区;他们交叉验证(即我可以用

jsrsasign
验证
SubtleCrypto
签名,反之亦然)。然而,当我在 Node.js
Sign
模块中使用
crypto
类时,我似乎得到了完全不同的东西。

key = require('jwk-to-pem')(key, {'private': true});
const sign = require('crypto').createSign('sha256');
sign.update(dataBuffer);
return sign.sign(key);

这里我得到一个可变长度的缓冲区,大约70字节;它不会与

jsrsa
进行交叉验证(这可以避免抱怨 r-s 签名长度无效)。

如何使用 Node

jsrsasign
获得由
SubtleCrypto
crypto
生成的 r-s 签名?

node.js cryptography ecdsa
2个回答
20
投票

答案是 Node

crypto
模块生成 ASN.1/DER 签名,而其他 API,如
jsrsasign
SubtleCrypto
则生成“串联”签名。在这两种情况下,签名都是
(r, s)
的串联。不同之处在于 ASN.1 使用最少的字节数以及一些有效负载长度数据来实现这一点;而 P1363 格式使用两个 32 位十六进制编码整数,如有必要,请对它们进行零填充。

以下解决方案假设“规范”格式是

SubtleCrypto
使用的串联样式。

const asn1 = require('asn1.js');
const BN = require('bn.js');
const crypto = require('crypto');

const EcdsaDerSig = asn1.define('ECPrivateKey', function() {
    return this.seq().obj(
        this.key('r').int(),
        this.key('s').int()
    );
});

function asn1SigSigToConcatSig(asn1SigBuffer) {
    const rsSig = EcdsaDerSig.decode(asn1SigBuffer, 'der');
    return Buffer.concat([
        rsSig.r.toArrayLike(Buffer, 'be', 32),
        rsSig.s.toArrayLike(Buffer, 'be', 32)
    ]);
}

function concatSigToAsn1SigSig(concatSigBuffer) {
    const r = new BN(concatSigBuffer.slice(0, 32).toString('hex'), 16, 'be');
    const s = new BN(concatSigBuffer.slice(32).toString('hex'), 16, 'be');
    return EcdsaDerSig.encode({r, s}, 'der');
}

function ecdsaSign(hashBuffer, key) {
    const sign = crypto.createSign('sha256');
    sign.update(asBuffer(hashBuffer));
    const asn1SigBuffer = sign.sign(key, 'buffer');
    return asn1SigSigToConcatSig(asn1SigBuffer);
}

function ecdsaVerify(data, signature, key) {
    const verify = crypto.createVerify('SHA256');
    verify.update(data);
    const asn1sig = concatSigToAsn1Sig(signature);
    return verify.verify(key, new Buffer(asn1sig, 'hex'));
}

弄清楚了,感谢


0
投票

现在可以使用节点

crypto
包直接生成
ieee-p1363
格式的签名

标志

  const payload: string = "hello";

  const signature = crypto
    .createSign("SHA256")
    .update(payload)
    .sign({
      key: privateKey,
      dsaEncoding: 'ieee-p1363',
    })
    .toString('base64');

验证

  const verified = crypto
    .createVerify("SHA256")
    .update(payload)
    .verify(
      {
        key: publicKey,
        dsaEncoding: 'ieee-p1363',
      },
      signature,
      'base64',
    );

在生产中使用的示例可以在 simple-jwt-auth 包中找到

© www.soinside.com 2019 - 2024. All rights reserved.