1

I have an application and a server that both use signed messages (using bouncycastle) to communicate. For a new application, I want to use JS and sign the message using jsrsasign and then verify it on the server side using bouncycastle again. The keys are generated in bouncycastle as well and then transferred to the JS application.

The signature is always invalid for the JS->Server communication, so I tried to sign and verify the message completely in JS and found that this also doesn't work. I am assuming that I am not using the correct configuration, but I fail to figure out the source of the problem.

I am using ECDSA with the prime256v1 curve.

My keys are generated like this:

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))

The generated keys look like this:

privateKey = MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg6xrOGWvvTTh5EfxhDco0xqppyFrSRbFaKUKbZutlynugCgYIKoZIzj0DAQehRANCAAQ6EhELaQWv3z7tBgJXYMGspw93Ni+LvvmY3f0MSBtu84ldBZi3boz+OV3hiiyO+mBx+jCg4s2TDF+nw0Vi3lS7

publicKey = BDoSEQtpBa/fPu0GAldgwaynD3c2L4u++Zjd/QxIG27ziV0FmLdujP45XeGKLI76YHH6MKDizZMMX6fDRWLeVLs=

For testing I take these keys and run this to see if they are valid on JS side:

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."
}

This fails for the keys taken from server, but when I generated keys with jsrsasign it works (.I need to make the server keys work, though).

I already tried to encode the keys to HEX instead of BASE64, but that didn't help. Since I am fairly new to the whole signing topic and at the end of my wits, any help is deeply appreciated.

bearbob
  • 46
  • 1
  • 4

1 Answers1

2

Keys can be specified in different formats and encodings. The posted private key has the PKCS#8 format, the public key is specified in uncompressed format. Both are Base64 encoded.
With regard to the import, a hex encoding would be more suitable here (as already done, in the Java/BouncyCastle code, or alternatively in the JavaScript code, e.g. here). Then the import with jsrsasign is feasible as follows:

// 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>

There are also other ways of importing. For instance, the private key could be PEM encoded (the line breaks after each 64 bytes are not required):

var privKeyPPCS8PEM = `-----BEGIN PRIVATE KEY-----
                       MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg6xrOGWvvTTh5EfxhDco0xqppyFrSRbFaKUKbZutlynugCgYIKoZIzj0DAQehRANCAAQ6EhELaQWv3z7tBgJXYMGspw93Ni+LvvmY3f0MSBtu84ldBZi3boz+OV3hiiyO+mBx+jCg4s2TDF+nw0Vi3lS7
                       -----END PRIVATE KEY-----`;
var privKey = KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(privKeyPPCS8PEM);

For more information about the different import options, see the documentation of jsrsasign, e.g. KEYUTIL.

One minor note: prime256v1 corresponds to a key size (order of the base point) of 256 bits (32 bytes). But with SHA512, you are using a digest that is twice as large (64 bytes). In this case according to NIST FIPS 186-4 the leftmost n bits of the hash are used, here.

Topaco
  • 40,594
  • 4
  • 35
  • 62