diff --git a/src/js/crypto/aes-cbc.js b/src/js/crypto/aes-cbc.js index 9904a80..47c3683 100644 --- a/src/js/crypto/aes-cbc.js +++ b/src/js/crypto/aes-cbc.js @@ -1,55 +1,81 @@ /** - * A Wrapper for Crypto.js's AES-CBC encryption + * A Wrapper for Forge's AES-CBC encryption with HMAC-SHA-256 an integrify check */ app.crypto.AesCBC = function() { 'use strict'; - var mode = CryptoJS.mode.CBC; // use CBC mode for Crypto.js - var padding = CryptoJS.pad.Pkcs7; // use Pkcs7/Pkcs5 padding for Crypto.js - /** * Encrypt a String using AES-CBC-Pkcs7 using the provided keysize (e.g. 128, 256) + * and create an HMAC-SHA-265 for integrity check * @param plaintext [String] The input string in UTF8 * @param key [String] The base64 encoded key * @param iv [String] The base64 encoded IV * @return [String] The base64 encoded ciphertext */ this.encrypt = function(plaintext, key, iv) { - // parse base64 input to crypto.js WordArrays - var keyWords = CryptoJS.enc.Base64.parse(key); - var ivWords = CryptoJS.enc.Base64.parse(iv); - var plaintextWords = CryptoJS.enc.Utf8.parse(plaintext); + // parse base64 input to utf8 + var keyUtf8 = forge.util.decode64(key); + var ivUtf8 = forge.util.decode64(iv); - var encrypted = CryptoJS.AES.encrypt(plaintextWords, keyWords, { - iv: ivWords, - mode: mode, - padding: padding - }); - var ctBase64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext); + // encrypt + var cipher = forge.aes.createEncryptionCipher(keyUtf8); + cipher.start(ivUtf8); + cipher.update(forge.util.createBuffer(plaintext)); + cipher.finish(); + var ctUtf8 = cipher.output.getBytes(); - return ctBase64; + // get hmac + return { + hmac: this.getHmac(ctUtf8, keyUtf8, ivUtf8), + ciphertext: forge.util.encode64(ctUtf8) + }; }; /** * Decrypt a String using AES-CBC-Pkcs7 using the provided keysize (e.g. 128, 256) + * and does an HMAC-SHA-265 integrity check * @param ciphertext [String] The base64 encoded ciphertext * @param key [String] The base64 encoded key * @param iv [String] The base64 encoded IV + * @param iv [String] The base64 encoded HMAC * @return [String] The decrypted plaintext in UTF8 */ - this.decrypt = function(ciphertext, key, iv) { - // parse base64 input to crypto.js WordArrays - var keyWords = CryptoJS.enc.Base64.parse(key); - var ivWords = CryptoJS.enc.Base64.parse(iv); + this.decrypt = function(ciphertext, key, iv, hmac) { + // parse base64 input to utf8 + var ctUtf8 = forge.util.decode64(ciphertext); + var keyUtf8 = forge.util.decode64(key); + var ivUtf8 = forge.util.decode64(iv); - var decrypted = CryptoJS.AES.decrypt(ciphertext, keyWords, { - iv: ivWords, - mode: mode, - padding: padding - }); - var pt = decrypted.toString(CryptoJS.enc.Utf8); + // check hmac + var checkedHmac = this.getHmac(ctUtf8, keyUtf8, ivUtf8); + if (hmac !== checkedHmac) { + throw new Error('The integrity check via HMAC failed!'); + } - return pt; + var cipher = forge.aes.createDecryptionCipher(keyUtf8); + cipher.start(ivUtf8); + cipher.update(forge.util.createBuffer(ctUtf8)); + cipher.finish(); + + return cipher.output.getBytes(); + }; + + /** + * Generate a base64 encoded HMAC using SHA-265 + * @param input [String] The input string in UTF8 + * @param key [String] The UTF8 encoded key + * @param iv [String] The UTF8 encoded IV + * @return [String] The base64 encoded hmac + */ + this.getHmac = function(input, key, iv) { + var hmac = forge.hmac.create(); + hmac.start('sha256', key); + if (iv) { + hmac.update(iv); + } + hmac.update(input); + + return forge.util.encode64(hmac.digest().getBytes()); }; }; \ No newline at end of file diff --git a/test/unit/aes-test.js b/test/unit/aes-test.js index cb2a0b8..fde5391 100644 --- a/test/unit/aes-test.js +++ b/test/unit/aes-test.js @@ -12,7 +12,7 @@ test("Init", 1, function() { aes_test.test_message = new TestData().generateBigString(1000); }); -test("CBC mode", 4, function() { +test("CBC mode with HMAC-SHA-256", 4, function() { var aes = new app.crypto.AesCBC(); var plaintext = aes_test.test_message; @@ -21,10 +21,10 @@ test("CBC mode", 4, function() { ok(key, 'Key: ' + key); equal(CryptoJS.enc.Base64.parse(key).sigBytes * 8, aes_test.keySize, 'Keysize ' + aes_test.keySize); - var ciphertext = aes.encrypt(plaintext, key, iv); - ok(ciphertext, 'Ciphertext lenght: ' + ciphertext.length); + var ct = aes.encrypt(plaintext, key, iv); + ok(ct.ciphertext, 'Ciphertext lenght: ' + ct.ciphertext.length); - var decrypted = aes.decrypt(ciphertext, key, iv); + var decrypted = aes.decrypt(ct.ciphertext, key, iv, ct.hmac); equal(decrypted, plaintext, 'Decryption correct' + decrypted); }); @@ -40,18 +40,4 @@ test("CCM mode", 2, function() { var decrypted = aes.decrypt(ciphertext, key, iv); equal(decrypted, plaintext, 'Decryption correct: ' + decrypted); -}); - -// test("GCM mode", 2, function() { -// var aes = new app.crypto.AesGCM(); -// -// var plaintext = aes_test.test_message; -// var key = aes_test.util.random(aes_test.keySize); -// var iv = aes_test.util.random(104); -// -// var ciphertext = aes.encrypt(plaintext, key, iv); -// ok(ciphertext, 'Ciphertext length: ' + ciphertext.length); -// -// var decrypted = aes.decrypt(ciphertext, key, iv); -// equal(decrypted, plaintext, 'Decryption correct: ' + decrypted); -// }); \ No newline at end of file +}); \ No newline at end of file