Implementing web cryptography API
Hello. I am a web dev newbie and this is my first blog. I was initially reluctant to start blogging but recently at work, I came across a task in which I had to sign and verify messages being sent in a PUT/POST HTTP request to increase the security of the web application. When I started researching the implementation of the same, I noticed that there aren't many examples of WebCryptoAPI on the web that I could find. So, I would like to share my implementation of signing data on the client side(Javascript) and verifying the data on the server side(Java) using WebCryptoAPI.
First, we generate a keypair using SubtleCrypto interface of the WebCrypto API by passing the algorithm to be used, whether the key is extractable or not (i.e can be used in exportKey method) and the key usage combination of "sign" and "verify".
const keyPair = await window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256"
},
true,
["sign", "verify"]
)
Here, We are generating a CryptoKeyPair with ECDSA algorithm and P-256 elliptic curve. Since we will be exporting the public key, extractable is set to true and the keyUsages are "sign" and "verify".
Note: generateKey method returns a Promise. You can either wait for it to complete using the good old Async-Await or attach a callback to resolve/reject the promise.
Next, We export the public key in jwk format using the exportKey() method of SubtleCrypto interface.
const publicKey = await window.crypto.subtle.exportKey(
"jwk",
keyPair.publicKey
)
Convert the publicKey to a JSON string and pass it in the headers.
For signing the data being sent in PUT/POST requests, intercept the request, convert the data/message to ArrayBuffer and sign with the private key generated.
const signature = await window.crypto.subtle.sign(
{
name: "ECDSA",
hash: { name: "SHA-256" },
},
privateKey,
requestDataBuffer
)
Here, the SHA256 hashing algorithm generates an almost unique 256-bit (32-byte) signature.
The generated signature is an ArrayBuffer. So, to send the signature to the server side, you'd have to encode it to a Base64 string and send it in the headers.
On the server side, add the dependency of jose4j and parse the public key string and convert it back to the public key:
ECKey ecKey = ECKey.parse(publicKeyString);
ECPublicKey ecPublicKey = (ECPublicKey) ecKey.toPublicKey();
Decode the signature, convert it to byte array and verify the data/message:
Signature signature = Signature.getInstance("SHA256withECDSAinP1363Format");
signature.initVerify(ecPublicKey)
signature.update(requestDataString.getBytes());
boolean isVerified = signature.verify(signatureBytes);
if(isVerified == false)
{
//Signature is not verified
}
else
{
//signature is verified
}
Note: Javascript hash is in P1363Format. Create the instance of Signature accordingly.
Data/message is now signed and verified.
Thanks for reading. :)