Aes-Gcm

dotnet 和 Node.js 中 AES-GCM 加密的區別

  • February 23, 2019

我有一個關於使用 AES-GCM 加密的大問題。

在使用 dotnet 和 Node.js 之間,加密結果略有不同。

這是來自 JOSE 的 Node.js 中使用 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()
   }

C# 和 BouncyCastle 庫也是如此:

       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();

認證標籤完全不同,但密文除了4個字節不同外幾乎相同。

這很奇怪,我不明白為什麼對於同一個密碼來說這是不同的,因為每個參數都是相同的(iv、key、aad)。

有誰知道出了什麼問題?

提前致謝!

編輯:

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

源文本是來自對象的編碼 JSON 字元串。也許它是 JSON 的序列化。

其他的東西,dotnet中生成的密文在nodeJS中是不可解密的,所以可能是跨平台問題?

編輯2:

這是在 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) ;

這是在 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

這是我嘗試執行上述程式碼時 NodeJS 崩潰的結果:

> 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)

輸入消息存在編碼問題。這是對密文部分差異的唯一合理解釋。GCM 在下面使用 CTR,因此如果 IV 或密鑰錯誤,則所有密文都會更改。錯誤字節在明文中的位置與在密文中的位置完全相同。

當然身份驗證標籤將完全 $ ^{*1} $ 如果即使是純文字消息的一點不同,也會有所不同。


$ \space^{*1} $ 好吧,身份驗證標籤的每一位都有可能發生變化 $ 1\over2 $ 當然。

引用自:https://crypto.stackexchange.com/questions/56112