I'm trying to verify a signature generated with Google's cloud KMS, but I keep getting invalid responses.
Here's how I'm testing it:
const versionName = client.cryptoKeyVersionPath(
projectId,
locationId,
keyRingId,
keyId,
versionId
)
const [publicKey] = await client.getPublicKey({
name: versionName,
})
const valueToSign = 'hola, que tal'
const digest = crypto.createHash('sha256').update(valueToSign).digest()
const [signResponse] = await client.asymmetricSign({
name: versionName,
digest: {
sha256: digest,
},
})
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)
if (!valid) return console.log('INVALID SIGNATURE')
console.log('SIGNATURE IS VALID!')
// output: INVALID SIGNATURE
This code will always log 'INVALID SIGNATURE' unless I use the original message instead of its hash:
const valid = crypto.createVerify('sha256').update(valueToSign).verify(publicKey.pem, signResponse.signature) // true
But using a local private key, I'm able to sign messages and verify them using their hashes:
const valueToSign = 'hola, the tal'
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true
Why is it? Is there something different about kms signatures?
Based on this example from the crypto module documentation and your observations, I'd say that you might've misunderstood how client.asymmetricSign
works. Let's analyze what happens:
Your local private key code:
const valueToSign = 'hola, the tal'
// Create sha256 hash
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');
// Let signer sign sha256(hash)
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
// We now got sign(sha256(hash))
// Let verifier verify sha256(hash)
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true
We are verifying sign(sha256(hash))
using verify(sha256(hash))
.
Your KMS code:
const valueToSign = 'hola, que tal'
// Create sha256 hash
const digest = crypto.createHash('sha256').update(valueToSign).digest()
// Let KMS sign the hash
const [signResponse] = await client.asymmetricSign({
name: versionName,
digest: {
sha256: digest, // we already say "we hashed our data using sha256"
},
});
// We now got `sign(hash)`, NOT `sign(sha256(hash))` (where hash == digest)
// Let verifier verify sha256(hash)
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)
We are verifying sign(hash)
using verify(sha256(hash))
.
Basically, locally you are signing your hash and verifying the signed hash. With KMS you are signing your data and verifying the signed hash, which is actually your signed data, hence your 2nd attempt with .update(valueToSign)
works.
Solution? Hash your sha256 hash again before letting KMS sign it, since KMS expects the sha256 hash of the to-be-signed data, while the crypto
expects the to-be-signed data (which it'll hash itself given the algorithm you passed to createSign
).