Генерация подписи ECDSA с помощью Node.js/crypto
У меня есть код, который генерирует каскадную (rs) подпись для подписи ECDSA, используя jsrsasign
и ключ в формате JWK:
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
и наоборот). Тем не менее, когда я использую Sign
класс в Node.js crypto
модуль, мне кажется, что-то совсем другое.
key = require('jwk-to-pem')(key, {'private': true});
const sign = require('crypto').createSign('sha256');
sign.update(dataBuffer);
return sign.sign(key);
Здесь я получаю буфер переменной длины, примерно 70 байт; он не сверяется с jsrsa
(который подает жалобу на недопустимую длину подписи rs).
Как я могу получить подпись RS, сгенерированную jsrsasign
а также SubtleCrypto
, используя Node crypto
?
1 ответ
Ответ оказывается, что узел 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'));
}
Разобрался благодаря