0

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.

Community
  • 1
  • 1
andrew
  • 77
  • 10
  • If you're using only symmetric encryption you need the exact same key at the server and the client. If you send the encryption key from the server to the client or the other way around you need to encrypt your symmetric encryption key. The easiest way to do this would be to use TLS. If you use TLS, then the data as well as key are encrypted, so you don't need to encrypt it yourself. This doesn't provide any security, just a little bit of obfuscation. You should read: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/ – Artjom B. Apr 07 '17 at 18:30
  • Yep, we're just trying to obfuscate: so-called "DRM". It's absurd. The "strategy" is to obfuscate the decryption within the js, and make it "difficult" for the user to inspect the traffic. Then we show them the decrypted result in plain sight in the browser. Of course anyone who would go that far can find a dozen other ways to get the content. We combine this with other strategies to build a beautiful multi-layered cake of absurdity. I'd still like to solve this problem. – andrew Apr 07 '17 at 18:45

1 Answers1

2

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 
)
Community
  • 1
  • 1
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • Artjom's input looks very promising, but I am still getting a DOMException client side, but no details in the error. The BouncyCastle code which I'm using was using a 128 bit nonce. I've tried changing it to 96, with the same results. Will keep trying. – andrew Apr 10 '17 at 16:52
  • turns out that then input value for the data parameter should include the last 16 bits: encryptedArrayBuffer.slice(12). I'll edit the answer – andrew Apr 12 '17 at 18:25