dotnet 和 Node.js 中 AES-GCM 加密的區別
我有一個關於使用 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 $ 當然。