nodejs crypto node.js cryptography ecdsa webcryptoapi

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.