0

I have a big problem about encryption with AES-GCM.

Between using dotnet and Node.js, the encryption results are slightly different.

Here is the code in Node.js from JOSE using Crypto Library :

    var cipher = crypto.createCipheriv("id-aes256-GCM", key, iv);
    cipher.setAutoPadding(false);
    cipher.setAAD(aad);
    var ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    return {
        cipher: ciphertext,
        tag: cipher.getAuthTag()
    }

The same with C# and BouncyCastle library :

        var iv = Convert.FromBase64String("3Wey/ctxwAnK7ZRv");          

        var cipher = new GcmBlockCipher(new AesFastEngine());
        var parameters = new AeadParameters(new KeyParameter(key), MacBitSize, iv, aad);
        cipher.Init(true, parameters);

        //Generate Cipher Text With Auth Tag
        var cipherText = new byte[cipher.GetOutputSize(secretMessage.Length)];
        var len = cipher.ProcessBytes(secretMessage, 0, secretMessage.Length, cipherText, 0);
        cipher.DoFinal(cipherText, len);

        var tag = cipher.GetMac();

The authentication tag is completely different, but the ciphertext is nearly the same except 4 bytes that are different.

That is very strange, and I don't understand why this is different for the same cipher because every parameter is the same (iv, key, aad).

Does anyone know what is wrong?

Thanks in advance!

EDIT:

for example I get this with NodeJS : WCgMFNfaGsoHynha+Nl+DYT7MiPiU3mbnnEMMMgbHjepkFObJEM8QT7UoTRt74iwmSsDI6IPqgpCpJwYtg/pre5nwbbZqwLh43QlmOm2xZhbeIXq1xOSapNIG2oAjiSJsij9scMjwwrQrYhBujiMbJqy4ppwyD8w5EiNaUGw3ZzUSTxe3ZSl6Mr8kHnsc3L1NVc+qpRyry1V3N+A2pNuE7VzI3Q3QclD2tAAVXtmF2GfXyY70y9+sdib/cXiqJh8Z8q7aZVTAWCLjWjvBn98N0p8KNY35NvKRmzJSu8tZ20ZBA==

And with dotnet BouncyCastle or other same library : WCgMFNfaGsoHynha+Nl+DYT7MiPiU3mbnnEMMMgbHjepkFObJEM8QT7UoTRt74iwmSsDI6IPqgpCpJwYtg/pre5nwbbZqwLh43QlmOm2xZhbeIXq1xOSapNIG2oAjiSJsij9scMjwwrQrYhBujiMbJqy4ppwyD8w5EiNaUGw3ZzUSTxe3ZSl6Mr8kHnsc3L1NVc+qpRyry1V3N+A2pNuE7VzI3Q3QclD2tAAVXtmF2GfXyY70y94sdib/cXiqJh8Z8q7aeVTAWCLjWjvBn98N0x8KNY35N3KRmzJSu8tZ20Z

The source text is a encoded JSON string from object. Maybe it's serialization of the JSON.

Other things, the ciphertext generated in dotnet is not decryptable in nodeJS, so it's probably a cross platform problem ?

EDIT2:

Here is the code to decrypt in NodeJS :

var jose = require('jose') ;

secret = 'rioa62N/Kvx78wUu6icgvDthco+Ro086WFqT4h4QMj4=';

// retrieve the header
var header = new Buffer('eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY3R5IjoiSldUIn0=', 'base64') ; // same as in dotnet
// Retreive the intialisation vector
var iv = new Buffer('3Wey/ctxwAnK7ZRv', 'base64'); // same as in dotnet
// Retrieve the cipher text (contains the JWS)
var ciphertext = new Buffer('R2HdWvfZuyNyf9VxZ/FNAkxZQpS9eSTHCnucA32+fB/+Yn1Qnm/HTjHXUsYiO84xv+v7BfiR8Myubb7lWjlCl43TFIB4TIiMPeEI7xLkRzRORnqOfFCktlirwv5Tjzj/vknpaTB1ZLKgIiNxOhZhuPr8S0dLVRm7Sy6tbNPKbUJgBAueg8Q28x3XGonDaznf8SwMVbnqRWD2FSYhoA4aH04iKXyvD9RMECl+/eAzSMpvJAvBCFaASe6A11VF/Jt3nMuOIqMsa93hnS8Y84Idpa9k2glMytS7tD/q2s2olaCs','base64') ;
// Retrieve the authentication tag
var authTag = new Buffer('TMrUu7Q/6trNqJWgrEKwYg==','base64') ;

// get the decryptor
var aes = new jose.jwa('A256GCM') ;
// Compute the aad (base 64 representation of the header)
var aad = new Buffer('eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY3R5IjoiSldUIn0=', 'base64') ; // same as in dotnet

// The key must be a buffer
var key = new Buffer(secret, 'base64') ;

// Decryption
var plain = aes.decrypt ( ciphertext, authTag, aad, iv, key )

// Display the jwt
console.log ( 'Part 1: ' + header ) ;
console.log ( 'Part 2: ') ;
console.log ( 'Part 3: ' + iv ) ;
console.log ( 'Part 4 :' + plain ) ;
console.log ( 'Part 5: ' + authTag) ;

Here is the code to encrypt in dotnet :

        var sharedKey = Convert.FromBase64String("rioa62N/Kvx78wUu6icgvDthco+Ro086WFqT4h4QMj4=");

        byte[] key = Convert.FromBase64String("+bGRaL6EPzUCHu0GiNxthgvD3hJN/glKJRQ7B66LJLo=");

        var payload = new
        {
            iss = "XXX",
            exp = 1519828214,
            //exp = DateTimeOffset.Now.ToUnixTimeSeconds() + 90,  // Expiration time is up to 90s, lets play on safe side
            sub = "XXX",
            secret = Convert.ToBase64String(key)
        };


        string jwt = JwtUtils.Encode(payload, sharedKey, JwtHashAlgorithm.HS256); // HMACSHA256 enconding

        var jweHeader = new { alg = "dir", enc = "A256GCM", cty = "JWT" };
        var serializedJweHeader = JsonConvert.SerializeObject(jweHeader, Formatting.None);
        var serializedJweHeaderBytes = Encoding.UTF8.GetBytes(serializedJweHeader);

        var nonce = Convert.FromBase64String("3Wey/ctxwAnK7ZRv");
        var secretMessage = Encoding.UTF8.GetBytes(jwt);

        var cipher = new GcmBlockCipher(new AesFastEngine());
        var parameters = new AeadParameters(new KeyParameter(sharedKey), 128, nonce, serializedJweHeaderBytes);
        cipher.Init(true, parameters);

        //Generate Cipher Text With Auth Tag
        var cipherText = new byte[cipher.GetOutputSize(secretMessage.Length)];
        var len = cipher.ProcessBytes(secretMessage, 0, secretMessage.Length, cipherText, 0);
        cipher.DoFinal(cipherText, len); // Convert.ToBase64String(cipherText.Take(225).ToArray()) to have the base64 représentation to set in NodeJS for the decryption, it appears that cipherText has tag so that's why I take only 225 first

        var tag = cipher.GetMac(); // Convert.ToBase64String(tag) to have the base64 representation

Here is the result crash of NodeJS whn I try to run the above code :

> crypto.js:183
  var ret = this._handle.final();
                         ^

Error: Unsupported state or unable to authenticate data
    at Decipheriv.final (crypto.js:183:26)
    at Object.decrypt (C:\Program Files\nodejs\node_modules\jose\lib\jwa\content
.js:140:72)
    at Object.<anonymous> (C:\Program Files\nodejs\decrypt_jwe_hard.js:35:17)
    at Module._compile (module.js:643:30)
    at Object.Module._extensions..js (module.js:654:10)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Function.Module.runMain (module.js:684:10)
    at startup (bootstrap_node.js:187:16)
Maarten Bodewes
  • 96,351
  • 14
  • 169
  • 323
user56610
  • 1
  • 1
  • 2

1 Answers1

1

There is an encoding issue for the input message. That is the only reasonable explanation for a partial difference in ciphertext. GCM uses CTR underneath, so if the IV or key is wrong then all the ciphertext changes. The wrong bytes are exactly at the same location in the plaintext as they are in the ciphertext.

Of course the authentication tag will be completely$^{*1}$ different if even a single bit of plaintext message is different.


$\space^{*1}$ Well, each bit of the authentication tag changes with a chance of $1\over2$ of course.

Maarten Bodewes
  • 96,351
  • 14
  • 169
  • 323