I want to encrypt data using C# and decrypt it using JS.
This table suggests that AES-GCM is the way to go with WebCryptoApi https://diafygi.github.io/webcrypto-examples/.
I am successfully using BouncyCastle https://codereview.stackexchange.com/questions/14892/simplified-secure-encryption-of-a-string to encrypt (and decrypt) in .NET.
var message = "This is the test message";
var key = AESGCM.NewKey();
Console.Out.WriteLine("KEY:" + Convert.ToBase64String(key));
>> KEY:5tgX6AOHot1T9SrImyILIendQXwfdjfOSRAVfMs0ed4=
string encrypted = AESGCM.SimpleEncrypt(message, key);
Console.Out.WriteLine("ENCRYPTED:" + encrypted);
>> ENCRYPTED:Ct0/VbOVsyp/LMxaaFqKKw91+ts+8uzDdHLrTG1XVjPNL7KiBGYB4kfdNGl+xj4fYqdb4JXgdTk=
var decrypted = AESGCM.SimpleDecrypt(encrypted, key);
Console.Out.WriteLine("DECRYPTED:" + decrypted);
>> DECRYPTED:This is the test message
But, I can't figure out how to decrypt this client side. There's a great list of WebCryptoApi examples including AES-GCM at https://github.com/diafygi/webcrypto-examples#aes-cbc---decrypt.
First step (which seems to working) is to import the key, which I have as a base-64 encoded string:
var keyString = "+6yDdIiJJl8Lqt60VOHuP25p4yNxz0CRMoE/WKA+Mqo=";
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array( len );
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
var key = _base64ToArrayBuffer(keyString )
var cryptoKey; // we'll get this out in the promise below
window.crypto.subtle.importKey(
"raw",
key,
{ //this is the algorithm options
name: "AES-GCM",
},
true, // whether the key is extractable
["encrypt", "decrypt"] // usages
)
.then(function(key){
//returns the symmetric key
console.log(key);
cryptoKey = key;
})
.catch(function(err){
console.error(err);
});
The final step should be to decrypt the encoded message, which is also a base-64 encoded string
var encryptedString = "adHb4UhM93uWyRIV6L1SrYFbxEpIbj3sQW8VwJDP7v+XoxGi6fjmucEEItP1kQWxisZp3qhoAhQ=";
var encryptedArrayBuffer = _base64ToArrayBuffer(encryptedString)
window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: new ArrayBuffer(12), //The initialization vector you used to encrypt
//additionalData: ArrayBuffer, //The addtionalData you used to encrypt (if any)
// tagLength: 128, //The tagLength you used to encrypt (if any)
},
cryptoKey, //from above
encryptedArrayBuffer //ArrayBuffer of the data
)
.then(function(decrypted){
//returns an ArrayBuffer containing the decrypted data
console.log(new Uint8Array(decrypted));
})
.catch(function(err){
debugger; console.error(err);
});
Unfortunately, this is thowing a DomError.
I have no idea what I am supposed to use for "iv" in the decrypt method. I've tried null, ArrayBuffer(0), ArrayBuffer(12). This is pretty much where my understanding ends.
If you look into the implementation of AESGCM
, you should see that the nonce (called IV) is part of the ciphertext. Its size is set to 16 bytes (NonceBitSize = 128
). You would need to read that many bytes from the beginning of the ciphertext in JavaScript and use the remaining bytes as the actual ciphertext to be decrypted.
GCM is only defined for a nonce of 96 bit, so you might need to change it to NonceBitSize = 96
and read the first 12 bytes.
Based on this answer, you will need to slice the last 16 bytes of the ciphertext (MacBitSize = 128
) for the authentication tag.
Example with a 96 bit nonce:
window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: encryptedArrayBuffer.slice(0, 12), //The initialization vector you used to encrypt
//additionalData: ArrayBuffer, //The addtionalData you used to encrypt (if any)
// tagLength: 128, //The tagLength you used to encrypt (if any)
tag: encryptedArrayBuffer.slice(-16), // authentication tag
},
cryptoKey, //from above
encryptedArrayBuffer.slice(12, -16) //ArrayBuffer of the data
// alternatively: encryptedArrayBuffer.slice(12) // in some cases leave the authentication tag in place
)