nodejs - Las firmas ECDSA entre Node.js y WebCrypto parecen ser incompatibles?
crypto javascript (1)
Estoy usando el siguiente ejemplo para firmar + verificar en Node.js: https://github.com/nodejs/node-v0.x-archive/issues/6904 . La verificación tiene éxito en Node.js pero falla en WebCrypto. Del mismo modo, un mensaje firmado usando WebCrypto no se puede verificar en Node.js.
Aquí está el código que utilicé para verificar una firma producida desde el script Node.js usando WebCrypto - https://jsfiddle.net/aj49e8sj/ . Probado en Chrome 54.0.2840.27 y Firefox 48.0.2
// From https://github.com/nodejs/node-v0.x-archive/issues/6904
var keys = {
priv: ''-----BEGIN EC PRIVATE KEY-----/n'' +
''MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49/n'' +
''AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2/n'' +
''pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==/n'' +
''-----END EC PRIVATE KEY-----/n'',
pub: ''-----BEGIN PUBLIC KEY-----/n'' +
''MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNh/n'' +
''B8i3mXyIMq704m2m52FdfKZ2pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==/n'' +
''-----END PUBLIC KEY-----/n''
};
var message = (new TextEncoder(''UTF-8'')).encode(''hello'');
// Algorithm used in Node.js script is ecdsa-with-SHA1, key generated with prime256v1
var algorithm = {
name: ''ECDSA'',
namedCurve: ''P-256'',
hash: {
name: ''SHA-1''
}
};
// Signature from obtained via above Node.js script
var sig64 = ''MEUCIQDkAtiomagyHFi7dNfxMrzx/U0Gk/ZhmwCqaL3TimvlswIgPgeDqgZNqfR5/FZZASYsczUAhGSXjuycLhWnvk20qKc='';
// Decode base64 string into ArrayBuffer
var b64Decode = (str) => Uint8Array.from(atob(str), x => x.charCodeAt(0));
// Get base64 string from public key
const key64 = keys.pub.split(''/n'')
.filter(x => x.length > 0 && !x.startsWith(''-----''))
.join('''');
// Convert to buffers
var sig = b64Decode(sig64);
var keySpki = b64Decode(key64);
// Import and verify
// Want ''Verification result: true'' but will get ''false''
var importKey = crypto.subtle.importKey(''spki'', keySpki, algorithm, true, [''verify''])
.then(key => crypto.subtle.verify(algorithm, key, sig, message))
.then(result => console.log(''Verification result: '' + result));
Pregunta relacionada con un problema similar con SHA-256 en lugar de SHA-1: generación de la firma ECDSA con Node.js / crypto
Cosas que he comprobado:
- Decodifiqué las claves Node.js y verifiqué que tienen el mismo OID que las claves generadas a través de WebCrypto. Esto me dice que estoy usando las curvas correctas.
- SHA-1 se identifica explícitamente como el hash para usar en ambas ubicaciones.
- ECDSA se identifica explícitamente en Node.js y WebCrypto.
¿Cómo puedo verificar con éxito la firma recibida de Node.js y viceversa - verificar una firma en Node.js producido a partir de WebCrypto? ¿O las implementaciones del estándar son sutilmente diferentes de tal forma que las hace incompatibles?
Editar:
- Firma WebCrypto (64 bytes): uTaUWTfF + AjN3aPj0b5Z2d1HybUEpV / phv / P9RtfKaGXtcYnbgfO43IRg46rznG3 / WnWwJ2sV6mPOEnEPR0vWw ==
- Firma de Node.js (71 bytes): MEUCIQDkAtiomagyHFi7dNfxMrzx / U0Gk / ZhmwCqaL3TimvlswIgPgeDqgZNqfR5 / FZZASYsczUAhGSXjuycLhWnvk20qKc =
La firma verificada de Node.js está codificada DER y la firma WebCrypto no.
Al no haber usado ninguna de estas bibliotecas, no puedo decirlo con certeza, pero una posibilidad es que no usen el mismo tipo de codificación para la firma. Para DSA / ECDSA hay dos formatos principales, IEEE P1363 (utilizado por Windows) y DER (utilizado por OpenSSL).
El formato "Windows" debe tener un tamaño preestablecido (determinado por Q para DSA y P para ECDSA (Windows no es compatible con Char-2, pero si lo hiciera probablemente sería M para Char-2 ECDSA)). Entonces, tanto r
como s
se rellenan a la izquierda con 0
hasta que alcanzan esa longitud.
En el ejemplo demasiado pequeño para ser legal de r = 0x305
y s = 0x810522
con sizeof (Q) siendo 3 bytes:
// r
000305
// s
810522
Para el formato "OpenSSL" está codificado bajo las reglas de DER como SEQUENCE (INTEGER (r), INTEGER (s)), que se parece a
// SEQUENCE
30
// (length of payload)
0A
// INTEGER(r)
02
// (length of payload)
02
// note the leading 0x00 is omitted
0305
// INTEGER(s)
02
// (length of payload)
04
// Since INTEGER is a signed type, but this represented a positive number,
// a 0x00 has to be inserted to keep the sign bit clear.
00810522
o, de forma compacta
- Windows:
000305810522
- OpenSSL:
300A02020305020400810522
El formato "Windows" siempre es uniforme, siempre con la misma longitud. El formato "OpenSSL" suele ser de unos 6 bytes más grande, pero puede ganar o perder un byte en el medio; por lo que a veces es par, a veces extraño.
La decodificación Base64 de su valor sig64
muestra que está utilizando la codificación DER. Generar un par de firmas con WebCrypto; si alguno no comienza con 0x30
entonces tiene el problema IEEE / DER.