Tengo una aplicación y un servidor que usan mensajes firmados (usando bouncycastle) para comunicarse. Para una nueva aplicación, quiero usar JS y firmar el mensaje usando jsrsasign y luego verificarlo en el lado del servidor usando bouncycastle nuevamente. Las claves también se generan en bouncycastle y luego se transfieren a la aplicación JS.
La firma siempre es inválida para la comunicación JS->Server, así que traté de firmar y verificar el mensaje completamente en JS y descubrí que esto tampoco funciona. Asumo que no estoy usando la configuración correcta, pero no logro descifrar el origen del problema.
Estoy usando ECDSA con la curva prime256v1.
Mis claves se generan así:
val generator = KeyPairGenerator.getInstance("ECDSA") val spec = ECNamedCurveTable.getParameterSpec("prime256v1") generator.initialize(spec, SecureRandom()) val keypair = generator.generateKeyPair() val privateKey = Base64.toBase64String(keypair.private.encoded) // X962 val publicKey = Base64.toBase64String(Arrays.copyOfRange(keypair.public.encoded, 26, keypair.public.encoded.size))Las claves generadas se ven así:
privateKey = MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg6xrOGWvvTTh5EfxhDco0xqppyFrSRbFaKUKbZutlynugCgYIKoZIzj0DAQehRANCAAQ6EhELaQWv3z7tBgJXYMGspw93Ni+LvvmY3f0MSBtu84ldBZi3boz+OV3hiiyO+mBx+jCg4s2TDF+nw0Vi3lS7 publicKey = BDoSEQtpBa/fPu0GAldgwaynD3c2L4u++Zjd/QxIG27ziV0FmLdujP45XeGKLI76YHH6MKDizZMMX6fDRWLeVLs=Para probar, tomo estas claves y ejecuto esto para ver si son válidas en el lado de JS:
const message = "Testing"; // Sign const sig = new KJUR.crypto.Signature({ alg: "SHA512withECDSA" }); sig.init({ d: keyPair.private, curve: "prime256v1" }); sig.updateString(message); const signature = sig.sign(); // Verify let sigVerification = new r.Signature({ alg: "SHA512withECDSA" }); sigVerification.init({ xy: keyPair.public, curve: "prime256v1" }); sigVerification.updateString(message); let isValid = sigVerification.verify(signature); if(isValid) { console.log("Signature is valid, keys are matching"); } else { throw "The signature is invalid. Please make sure the keys match." }Esto falla para las claves tomadas del servidor, pero cuando generé claves con jsrsasign funciona (aunque necesito hacer que las claves del servidor funcionen).
Ya intenté codificar las claves en HEX en lugar de BASE64, pero eso no ayudó. Dado que soy bastante nuevo en todo el tema de la firma y al final de mis ingenios, cualquier ayuda es muy apreciada.
Las claves se pueden especificar en diferentes formatos y codificaciones. La clave privada publicada tiene el formato PKCS#8, la clave pública se especifica en formato sin comprimir. Ambos están codificados en Base64.
Con respecto a la importación, una codificación hexadecimal sería más adecuada aquí (como ya se ha hecho, en el código Java/BouncyCastle, o alternativamente en el código JavaScript, por ejemplo aquí ). Entonces la importación con jsrsasign es factible de la siguiente manera:
// Import as hex encoded PKCS#8 key var privKeyPKCS8Hex = '308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420eb1ace196bef4d387911fc610dca34c6aa69c85ad245b15a29429b66eb65ca7ba00a06082a8648ce3d030107a144034200043a12110b6905afdf3eed06025760c1aca70f77362f8bbef998ddfd0c481b6ef3895d0598b76e8cfe395de18a2c8efa6071fa30a0e2cd930c5fa7c34562de54bb'; var privKey = KEYUTIL.getKeyFromPlainPrivatePKCS8Hex(privKeyPKCS8Hex); var signature = new KJUR.crypto.Signature({"alg": "SHA512withECDSA"}); signature.init(privKey); signature.updateString("The quick brown fox jumps over the lazy dog"); var signatureHex = signature.sign(); document.getElementById("signatureHex").innerHTML = signatureHex; var publicKeyUncompressed = '043a12110b6905afdf3eed06025760c1aca70f77362f8bbef998ddfd0c481b6ef3895d0598b76e8cfe395de18a2c8efa6071fa30a0e2cd930c5fa7c34562de54bb'; var pubKey = new KJUR.crypto.ECDSA({'pub': publicKeyUncompressed, 'curve': 'prime256v1'}); var signature = new KJUR.crypto.Signature({"alg": "SHA512withECDSA"}); signature.init(pubKey); signature.updateString("The quick brown fox jumps over the lazy dog"); var verified = signature.verify(signatureHex); document.getElementById("verified").innerHTML = verified; <script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script> <p style="font-family:'Courier New', monospace;" id="signatureHex"></p> <p style="font-family:'Courier New', monospace;" id="verified"></p>También hay otras formas de importar. Por ejemplo, la clave privada podría codificarse con PEM (no se requieren saltos de línea después de cada 64 bytes):
var privKeyPPCS8PEM = `-----BEGIN PRIVATE KEY----- MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg6xrOGWvvTTh5EfxhDco0xqppyFrSRbFaKUKbZutlynugCgYIKoZIzj0DAQehRANCAAQ6EhELaQWv3z7tBgJXYMGspw93Ni+LvvmY3f0MSBtu84ldBZi3boz+OV3hiiyO+mBx+jCg4s2TDF+nw0Vi3lS7 -----END PRIVATE KEY-----`; var privKey = KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(privKeyPPCS8PEM); Para obtener más información sobre las diferentes opciones de importación, consulte la documentación de jsrsasign , por ejemplo,KEYUTIL .
Una nota menor: prime256v1 corresponde a un tamaño de clave (orden del punto base) de 256 bits (32 bytes). Pero con SHA512, está utilizando un resumen que es el doble de grande (64 bytes). En este caso, según NIST FIPS 186-4, se utilizan los n bits más a la izquierda del hash, aquí .