From 6b81995dd5c739baad3772eb2717cd86be7cba11 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 00:05:43 +0100 Subject: [PATCH 1/9] remove aes args from global scope --- src/crypto/cipher/aes.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/crypto/cipher/aes.js b/src/crypto/cipher/aes.js index 2a13e946..3892aa08 100644 --- a/src/crypto/cipher/aes.js +++ b/src/crypto/cipher/aes.js @@ -442,9 +442,11 @@ function keyExpansion(key) { } } } - this.rounds = rounds; - this.rk = keySched; - return this; + + return { + rounds: rounds, + rk: keySched + }; } function AESencrypt(block, ctx) { From 8eada2a1a82ca85e53bdf8c2f5b7ac00d2653edf Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 00:09:49 +0100 Subject: [PATCH 2/9] fix aes unit test --- test/crypto/cipher/aes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/crypto/cipher/aes.js b/test/crypto/cipher/aes.js index 2befa313..9acc36ea 100644 --- a/test/crypto/cipher/aes.js +++ b/test/crypto/cipher/aes.js @@ -88,7 +88,7 @@ describe('AES Rijndael cipher test with test vectors from ecb_tbl.txt', function var res = test_aes(testvectors256[i][1],testvectors256[i][0],testvectors256[i][2]); expect(res, 'block ' + util.hexidump(testvectors256[i][1]) + ' and key ' + util.hexidump(testvectors256[i][0]) + - ' should be ' + util.hexidump(testvectors256[i][2])); + ' should be ' + util.hexidump(testvectors256[i][2])).to.be.true; } done(); }); From 47d86825e29b45b40bda335fd9f80f631e07b015 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 09:55:58 +0100 Subject: [PATCH 3/9] cleanup for loops, use strings += instead of array.push/join, use strict mode --- src/crypto/cfb.js | 94 +++++++++++++++++++++++++--------------- src/crypto/cipher/aes.js | 2 + 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index c003bf54..3bb9e1c6 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -21,6 +21,8 @@ * @module crypto/cfb */ +'use strict'; + var util = require('../util.js'), cipher = require('./cipher'); @@ -51,10 +53,12 @@ module.exports = { prefixrandom = prefixrandom + prefixrandom.charAt(block_size - 2) + prefixrandom.charAt(block_size - 1); util.print_debug("prefixrandom:" + util.hexstrdump(prefixrandom)); - var ciphertext = ""; + var ciphertext = ''; var i, n; // 1. The feedback register (FR) is set to the IV, which is all zeros. - for (i = 0; i < block_size; i++) FR[i] = 0; + for (i = 0; i < block_size; i++) { + FR[i] = 0; + } // 2. FR is encrypted to produce FRE (FR Encrypted). This is the // encryption of an all-zero value. @@ -62,10 +66,14 @@ module.exports = { // 3. FRE is xored with the first BS octets of random data prefixed to // the plaintext to produce C[1] through C[BS], the first BS octets // of ciphertext. - for (i = 0; i < block_size; i++) ciphertext += String.fromCharCode(FRE[i] ^ prefixrandom.charCodeAt(i)); + for (i = 0; i < block_size; i++) { + ciphertext += String.fromCharCode(FRE[i] ^ prefixrandom.charCodeAt(i)); + } // 4. FR is loaded with C[1] through C[BS]. - for (i = 0; i < block_size; i++) FR[i] = ciphertext.charCodeAt(i); + for (i = 0; i < block_size; i++) { + FR[i] = ciphertext.charCodeAt(i); + } // 5. FR is encrypted to produce FRE, the encryption of the first BS // octets of ciphertext. @@ -79,9 +87,13 @@ module.exports = { if (resync) { // 7. (The resync step) FR is loaded with C3-C10. - for (i = 0; i < block_size; i++) FR[i] = ciphertext.charCodeAt(i + 2); + for (i = 0; i < block_size; i++) { + FR[i] = ciphertext.charCodeAt(i + 2); + } } else { - for (i = 0; i < block_size; i++) FR[i] = ciphertext.charCodeAt(i); + for (i = 0; i < block_size; i++) { + FR[i] = ciphertext.charCodeAt(i); + } } // 8. FR is encrypted to produce FRE. FRE = cipherfn.encrypt(FR, key); @@ -90,11 +102,14 @@ module.exports = { // 9. FRE is xored with the first 8 octets of the given plaintext, now // that we have finished encrypting the 10 octets of prefixed data. // This produces C11-C18, the next 8 octets of ciphertext. - for (i = 0; i < block_size; i++) + for (i = 0; i < block_size; i++) { ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt(i)); + } for (n = block_size + 2; n < plaintext.length; n += block_size) { // 10. FR is loaded with C11-C18 - for (i = 0; i < block_size; i++) FR[i] = ciphertext.charCodeAt(n + i); + for (i = 0; i < block_size; i++) { + FR[i] = ciphertext.charCodeAt(n + i); + } // 11. FR is encrypted to produce FRE. FRE = cipherfn.encrypt(FR); @@ -102,8 +117,9 @@ module.exports = { // 12. FRE is xored with the next 8 octets of plaintext, to produce the // next 8 octets of ciphertext. These are loaded into FR and the // process is repeated until the plaintext is used up. - for (i = 0; i < block_size; i++) ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt((n - 2) + - i)); + for (i = 0; i < block_size; i++) { + ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt((n - 2) + i)); + } } } else { plaintext = " " + plaintext; @@ -163,7 +179,9 @@ module.exports = { // initialisation vector - for (i = 0; i < block_size; i++) iblock[i] = 0; + for (i = 0; i < block_size; i++) { + iblock[i] = 0; + } iblock = cipherfn.encrypt(iblock); for (i = 0; i < block_size; i++) { @@ -199,10 +217,12 @@ module.exports = { var iblock = new Array(block_size); var ablock = new Array(block_size); var i, n = ''; - var text = []; + var text = ''; // initialisation vector - for (i = 0; i < block_size; i++) iblock[i] = 0; + for (i = 0; i < block_size; i++) { + iblock[i] = 0; + } iblock = cipherfn.encrypt(iblock, key); for (i = 0; i < block_size; i++) { @@ -226,33 +246,34 @@ module.exports = { */ if (resync) { - for (i = 0; i < block_size; i++) iblock[i] = ciphertext.charCodeAt(i + 2); + for (i = 0; i < block_size; i++) { + iblock[i] = ciphertext.charCodeAt(i + 2); + } for (n = block_size + 2; n < ciphertext.length; n += block_size) { ablock = cipherfn.encrypt(iblock); for (i = 0; i < block_size && i + n < ciphertext.length; i++) { iblock[i] = ciphertext.charCodeAt(n + i); - text.push(String.fromCharCode(ablock[i] ^ iblock[i])); + text += String.fromCharCode(ablock[i] ^ iblock[i]); } } } else { - for (i = 0; i < block_size; i++) iblock[i] = ciphertext.charCodeAt(i); + for (i = 0; i < block_size; i++) { + iblock[i] = ciphertext.charCodeAt(i); + } for (n = block_size; n < ciphertext.length; n += block_size) { ablock = cipherfn.encrypt(iblock); for (i = 0; i < block_size && i + n < ciphertext.length; i++) { iblock[i] = ciphertext.charCodeAt(n + i); - text.push(String.fromCharCode(ablock[i] ^ iblock[i])); + text += String.fromCharCode(ablock[i] ^ iblock[i]); } } } n = resync ? 0 : 2; - text = text.join(''); - text = text.substring(n, ciphertext.length - block_size - 2 + n); - return text; }, @@ -261,47 +282,50 @@ module.exports = { cipherfn = new cipher[cipherfn](key); var block_size = cipherfn.blockSize; - var blocki = ""; - var blockc = ""; + var blocki = ''; + var blockc = ''; var pos = 0; - var cyphertext = []; - var tempBlock = []; + var cyphertext = ''; + var tempBlock = ''; blockc = iv.substring(0, block_size); while (plaintext.length > block_size * pos) { var encblock = cipherfn.encrypt(util.str2bin(blockc)); blocki = plaintext.substring((pos * block_size), (pos * block_size) + block_size); - for (var i = 0; i < blocki.length; i++) - tempBlock.push(String.fromCharCode(blocki.charCodeAt(i) ^ encblock[i])); - blockc = tempBlock.join(''); - tempBlock = []; - cyphertext.push(blockc); + for (var i = 0; i < blocki.length; i++) { + tempBlock += String.fromCharCode(blocki.charCodeAt(i) ^ encblock[i]); + } + blockc = tempBlock; + tempBlock = ''; + cyphertext += blockc; pos++; } - return cyphertext.join(''); + return cyphertext; }, normalDecrypt: function(cipherfn, key, ciphertext, iv) { cipherfn = new cipher[cipherfn](key); var block_size = cipherfn.blockSize; - var blockp = ""; + var blockp = ''; var pos = 0; - var plaintext = []; + var plaintext = ''; var offset = 0; var i; if (iv === null) - for (i = 0; i < block_size; i++) blockp += String.fromCharCode(0); + for (i = 0; i < block_size; i++) { + blockp += String.fromCharCode(0); + } else blockp = iv.substring(0, block_size); while (ciphertext.length > (block_size * pos)) { var decblock = cipherfn.encrypt(util.str2bin(blockp)); blockp = ciphertext.substring((pos * (block_size)) + offset, (pos * (block_size)) + (block_size) + offset); for (i = 0; i < blockp.length; i++) { - plaintext.push(String.fromCharCode(blockp.charCodeAt(i) ^ decblock[i])); + plaintext += String.fromCharCode(blockp.charCodeAt(i) ^ decblock[i]); } pos++; } - return plaintext.join(''); + return plaintext; } }; diff --git a/src/crypto/cipher/aes.js b/src/crypto/cipher/aes.js index 3892aa08..db3a76fe 100644 --- a/src/crypto/cipher/aes.js +++ b/src/crypto/cipher/aes.js @@ -16,6 +16,8 @@ * @module crypto/cipher/aes */ +'use strict'; + var util = require('../../util.js'); // The round constants used in subkey expansion From 49ac4e6816df0c48197e3dc07602e061d28112da Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 10:30:57 +0100 Subject: [PATCH 4/9] add performance test --- test/general/basic.js | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/test/general/basic.js b/test/general/basic.js index 54bb6bf2..a5dc2ed3 100644 --- a/test/general/basic.js +++ b/test/general/basic.js @@ -18,16 +18,23 @@ describe('Basic', function() { var info = '\npassphrase: ' + passphrase + '\n' + 'userid: ' + userid + '\n' + 'message: ' + message; var privKeys = openpgp.key.readArmored(key.privateKeyArmored); + var publicKeys = openpgp.key.readArmored(key.publicKeyArmored); expect(privKeys).to.exist; expect(privKeys.err).to.not.exist; expect(privKeys.keys).to.have.length(1); var privKey = privKeys.keys[0]; + var pubKey = publicKeys.keys[0]; expect(privKey).to.exist; + expect(pubKey).to.exist; - var encrypted = openpgp.encryptMessage([privKey], message); + var success = privKey.decrypt(passphrase); + + expect(success).to.be.true; + + var encrypted = openpgp.signAndEncryptMessage([pubKey], privKey, message); expect(encrypted).to.exist; @@ -39,13 +46,10 @@ describe('Basic', function() { expect(keyids).to.exist; - var success = privKey.decryptKeyPacket(keyids, passphrase); - - expect(success).to.be.true; - - var decrypted = openpgp.decryptMessage(privKey, msg); + var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); expect(decrypted).to.exist; - expect(decrypted).to.equal(message); + expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.text).to.equal(message); }; it('ASCII Text', function (done) { @@ -56,6 +60,26 @@ describe('Basic', function() { testHelper('●●●●', '♔♔♔♔ ', 'łäóć'); done(); }); + it('Performance test', function (done) { + function randomString(length, chars) { + var result = ''; + for (var i = length; i > 0; --i) result += chars[Math.round(Math.random() * (chars.length - 1))]; + return result; + } + var rString = randomString(1024*1024, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); + + if (console.profile) { + console.profile("encrypt/sign/verify/decrypt"); + } + + testHelper('password', 'Test McTestington ', rString); + + if (console.profileEnd) { + console.profileEnd(); + } + + done(); + }); }); describe("Message encryption/decryption", function() { From 31f9064e90fc82d2fc5fd849a039eb8e223cd944 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 10:50:12 +0100 Subject: [PATCH 5/9] use string instead of array in cfb encrypt --- src/crypto/cfb.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index 3bb9e1c6..6d21c3e4 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -130,7 +130,7 @@ module.exports = { ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt(i)); } - var tempCiphertext = ciphertext.substring(0, 2 * block_size).split(''); + var tempCiphertext = ciphertext.substring(0, 2 * block_size); var tempCiphertextString = ciphertext.substring(block_size); var tempCiphertextBlock; for (n = block_size; n < plaintext.length; n += block_size) { @@ -148,11 +148,11 @@ module.exports = { // process is repeated until the plaintext is used up. for (i = 0; i < block_size; i++) { tempCiphertextBlock = String.fromCharCode(FRE[i] ^ plaintext.charCodeAt(n + i)); - tempCiphertext[tempCiphertext.length] = tempCiphertextBlock; + tempCiphertext += tempCiphertextBlock; tempCiphertextString += tempCiphertextBlock; } } - ciphertext = tempCiphertext.join(''); + ciphertext = tempCiphertext; } ciphertext = ciphertext.substring(0, plaintext.length + 2 + block_size); From afabb56d41d0b7f9211868b290fb6e1bc825d288 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 17 Feb 2014 14:53:00 +0100 Subject: [PATCH 6/9] use forge sha256 implementation --- package.json | 2 +- src/crypto/hash/forge_sha256.js | 290 +++++++++++ src/crypto/hash/forge_util.js | 836 ++++++++++++++++++++++++++++++++ src/crypto/hash/index.js | 7 +- 4 files changed, 1132 insertions(+), 3 deletions(-) create mode 100644 src/crypto/hash/forge_sha256.js create mode 100644 src/crypto/hash/forge_util.js diff --git a/package.json b/package.json index 1285baf3..f557e3d8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openpgp", "description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.", - "version": "0.4.0", + "version": "0.4.1-dev", "homepage": "http://openpgpjs.org/", "engines": { "node": ">=0.8" diff --git a/src/crypto/hash/forge_sha256.js b/src/crypto/hash/forge_sha256.js new file mode 100644 index 00000000..6dad2271 --- /dev/null +++ b/src/crypto/hash/forge_sha256.js @@ -0,0 +1,290 @@ +/** + * Secure Hash Algorithm with 256-bit digest (SHA-256) implementation. + * + * See FIPS 180-2 for details. + * + * This implementation is currently limited to message lengths (in bytes) that + * are up to 32-bits in size. + * + * @author Dave Longley + * + * Copyright (c) 2010-2012 Digital Bazaar, Inc. + */ + +var sha256 = module.exports = {}; +var util = require('./forge_util.js'); + +// sha-256 padding bytes not initialized yet +var _padding = null; +var _initialized = false; + +// table of constants +var _k = null; + +/** + * Initializes the constant tables. + */ +var _init = function() { + // create padding + _padding = String.fromCharCode(128); + _padding += util.fillString(String.fromCharCode(0x00), 64); + + // create K table for SHA-256 + _k = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ]; + + // now initialized + _initialized = true; +}; + +/** + * Updates a SHA-256 state with the given byte buffer. + * + * @param s the SHA-256 state to update. + * @param w the array to use to store words. + * @param bytes the byte buffer to update with. + */ +var _update = function(s, w, bytes) { + // consume 512 bit (64 byte) chunks + var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h; + var len = bytes.length(); + while (len >= 64) { + // the w array will be populated with sixteen 32-bit big-endian words + // and then extended into 64 32-bit words according to SHA-256 + for (i = 0; i < 16; ++i) { + w[i] = bytes.getInt32(); + } + for (; i < 64; ++i) { + // XOR word 2 words ago rot right 17, rot right 19, shft right 10 + t1 = w[i - 2]; + t1 = + ((t1 >>> 17) | (t1 << 15)) ^ + ((t1 >>> 19) | (t1 << 13)) ^ + (t1 >>> 10); + // XOR word 15 words ago rot right 7, rot right 18, shft right 3 + t2 = w[i - 15]; + t2 = + ((t2 >>> 7) | (t2 << 25)) ^ + ((t2 >>> 18) | (t2 << 14)) ^ + (t2 >>> 3); + // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32 + w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) & 0xFFFFFFFF; + } + + // initialize hash value for this chunk + a = s.h0; + b = s.h1; + c = s.h2; + d = s.h3; + e = s.h4; + f = s.h5; + g = s.h6; + h = s.h7; + + // round function + for (i = 0; i < 64; ++i) { + // Sum1(e) + s1 = + ((e >>> 6) | (e << 26)) ^ + ((e >>> 11) | (e << 21)) ^ + ((e >>> 25) | (e << 7)); + // Ch(e, f, g) (optimized the same way as SHA-1) + ch = g ^ (e & (f ^ g)); + // Sum0(a) + s0 = + ((a >>> 2) | (a << 30)) ^ + ((a >>> 13) | (a << 19)) ^ + ((a >>> 22) | (a << 10)); + // Maj(a, b, c) (optimized the same way as SHA-1) + maj = (a & b) | (c & (a ^ b)); + + // main algorithm + t1 = h + s1 + ch + _k[i] + w[i]; + t2 = s0 + maj; + h = g; + g = f; + f = e; + e = (d + t1) & 0xFFFFFFFF; + d = c; + c = b; + b = a; + a = (t1 + t2) & 0xFFFFFFFF; + } + + // update hash state + s.h0 = (s.h0 + a) & 0xFFFFFFFF; + s.h1 = (s.h1 + b) & 0xFFFFFFFF; + s.h2 = (s.h2 + c) & 0xFFFFFFFF; + s.h3 = (s.h3 + d) & 0xFFFFFFFF; + s.h4 = (s.h4 + e) & 0xFFFFFFFF; + s.h5 = (s.h5 + f) & 0xFFFFFFFF; + s.h6 = (s.h6 + g) & 0xFFFFFFFF; + s.h7 = (s.h7 + h) & 0xFFFFFFFF; + len -= 64; + } +}; + +/** + * Creates a SHA-256 message digest object. + * + * @return a message digest object. + */ +sha256.create = function() { + // do initialization as necessary + if (!_initialized) { + _init(); + } + + // SHA-256 state contains eight 32-bit integers + var _state = null; + + // input buffer + var _input = util.createBuffer(); + + // used for word storage + var _w = new Array(64); + + // message digest object + var md = { + algorithm: 'sha256', + blockLength: 64, + digestLength: 32, + // length of message so far (does not including padding) + messageLength: 0 + }; + + /** + * Starts the digest. + * + * @return this digest object. + */ + md.start = function() { + md.messageLength = 0; + _input = util.createBuffer(); + _state = { + h0: 0x6A09E667, + h1: 0xBB67AE85, + h2: 0x3C6EF372, + h3: 0xA54FF53A, + h4: 0x510E527F, + h5: 0x9B05688C, + h6: 0x1F83D9AB, + h7: 0x5BE0CD19 + }; + return md; + }; + // start digest automatically for first time + md.start(); + + /** + * Updates the digest with the given message input. The given input can + * treated as raw input (no encoding will be applied) or an encoding of + * 'utf8' maybe given to encode the input using UTF-8. + * + * @param msg the message input to update with. + * @param encoding the encoding to use (default: 'raw', other: 'utf8'). + * + * @return this digest object. + */ + md.update = function(msg, encoding) { + if (encoding === 'utf8') { + msg = util.encodeUtf8(msg); + } + + // update message length + md.messageLength += msg.length; + + // add bytes to input buffer + _input.putBytes(msg); + + // process bytes + _update(_state, _w, _input); + + // compact input buffer every 2K or if empty + if (_input.read > 2048 || _input.length() === 0) { + _input.compact(); + } + + return md; + }; + + /** + * Produces the digest. + * + * @return a byte buffer containing the digest value. + */ + md.digest = function() { + /* Note: Here we copy the remaining bytes in the input buffer and + add the appropriate SHA-256 padding. Then we do the final update + on a copy of the state so that if the user wants to get + intermediate digests they can do so. */ + + /* Determine the number of bytes that must be added to the message + to ensure its length is congruent to 448 mod 512. In other words, + a 64-bit integer that gives the length of the message will be + appended to the message and whatever the length of the message is + plus 64 bits must be a multiple of 512. So the length of the + message must be congruent to 448 mod 512 because 512 - 64 = 448. + + In order to fill up the message length it must be filled with + padding that begins with 1 bit followed by all 0 bits. Padding + must *always* be present, so if the message length is already + congruent to 448 mod 512, then 512 padding bits must be added. */ + + // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes + // _padding starts with 1 byte with first bit is set in it which + // is byte value 128, then there may be up to 63 other pad bytes + var len = md.messageLength; + var padBytes = util.createBuffer(); + padBytes.putBytes(_input.bytes()); + padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64))); + + /* Now append length of the message. The length is appended in bits + as a 64-bit number in big-endian order. Since we store the length + in bytes, we must multiply it by 8 (or left shift by 3). So here + store the high 3 bits in the low end of the first 32-bits of the + 64-bit number and the lower 5 bits in the high end of the second + 32-bits. */ + padBytes.putInt32((len >>> 29) & 0xFF); + padBytes.putInt32((len << 3) & 0xFFFFFFFF); + var s2 = { + h0: _state.h0, + h1: _state.h1, + h2: _state.h2, + h3: _state.h3, + h4: _state.h4, + h5: _state.h5, + h6: _state.h6, + h7: _state.h7 + }; + _update(s2, _w, padBytes); + var rval = util.createBuffer(); + rval.putInt32(s2.h0); + rval.putInt32(s2.h1); + rval.putInt32(s2.h2); + rval.putInt32(s2.h3); + rval.putInt32(s2.h4); + rval.putInt32(s2.h5); + rval.putInt32(s2.h6); + rval.putInt32(s2.h7); + return rval; + }; + + return md; +}; \ No newline at end of file diff --git a/src/crypto/hash/forge_util.js b/src/crypto/hash/forge_util.js new file mode 100644 index 00000000..5c56a118 --- /dev/null +++ b/src/crypto/hash/forge_util.js @@ -0,0 +1,836 @@ +/** + * Utility functions for web applications. + * + * @author Dave Longley + * + * Copyright (c) 2010-2012 Digital Bazaar, Inc. + */ + +/* Utilities API */ +var util = module.exports = {}; + +// define isArray +util.isArray = Array.isArray || function(x) { + return Object.prototype.toString.call(x) === '[object Array]'; +}; + +// define isArrayBuffer +util.isArrayBuffer = function(x) { + return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer; +}; + +// define isArrayBufferView +var _arrayBufferViews = []; +if(typeof Int8Array !== 'undefined') { + _arrayBufferViews.push(Int8Array); +} +if(typeof Uint8Array !== 'undefined') { + _arrayBufferViews.push(Uint8Array); +} +if(typeof Uint8ClampedArray !== 'undefined') { + _arrayBufferViews.push(Uint8ClampedArray); +} +if(typeof Int16Array !== 'undefined') { + _arrayBufferViews.push(Int16Array); +} +if(typeof Uint16Array !== 'undefined') { + _arrayBufferViews.push(Uint16Array); +} +if(typeof Int32Array !== 'undefined') { + _arrayBufferViews.push(Int32Array); +} +if(typeof Uint32Array !== 'undefined') { + _arrayBufferViews.push(Uint32Array); +} +if(typeof Float32Array !== 'undefined') { + _arrayBufferViews.push(Float32Array); +} +if(typeof Float64Array !== 'undefined') { + _arrayBufferViews.push(Float64Array); +} +util.isArrayBufferView = function(x) { + for(var i = 0; i < _arrayBufferViews.length; ++i) { + if(x instanceof _arrayBufferViews[i]) { + return true; + } + } + return false; +}; + +/** + * Constructor for a byte buffer. + * + * @param [b] the bytes to wrap (either encoded as string, one byte per + * character, or as an ArrayBuffer or Typed Array). + */ +util.ByteBuffer = function(b) { + // the data in this buffer + this.data = ''; + // the pointer for reading from this buffer + this.read = 0; + + if(typeof b === 'string') { + this.data = b; + } + else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) { + // convert native buffer to forge buffer + // FIXME: support native buffers internally instead + var arr = new Uint8Array(b); + try { + this.data = String.fromCharCode.apply(null, arr); + } + catch(e) { + for(var i = 0; i < arr.length; ++i) { + this.putByte(arr[i]); + } + } + } +}; + +/** + * Gets the number of bytes in this buffer. + * + * @return the number of bytes in this buffer. + */ +util.ByteBuffer.prototype.length = function() { + return this.data.length - this.read; +}; + +/** + * Gets whether or not this buffer is empty. + * + * @return true if this buffer is empty, false if not. + */ +util.ByteBuffer.prototype.isEmpty = function() { + return this.length() <= 0; +}; + +/** + * Puts a byte in this buffer. + * + * @param b the byte to put. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putByte = function(b) { + this.data += String.fromCharCode(b); + return this; +}; + +/** + * Puts a byte in this buffer N times. + * + * @param b the byte to put. + * @param n the number of bytes of value b to put. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.fillWithByte = function(b, n) { + b = String.fromCharCode(b); + var d = this.data; + while(n > 0) { + if(n & 1) { + d += b; + } + n >>>= 1; + if(n > 0) { + b += b; + } + } + this.data = d; + return this; +}; + +/** + * Puts bytes in this buffer. + * + * @param bytes the bytes (as a UTF-8 encoded string) to put. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putBytes = function(bytes) { + this.data += bytes; + return this; +}; + +/** + * Puts a UTF-16 encoded string into this buffer. + * + * @param str the string to put. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putString = function(str) { + this.data += util.encodeUtf8(str); + return this; +}; + +/** + * Puts a 16-bit integer in this buffer in big-endian order. + * + * @param i the 16-bit integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt16 = function(i) { + this.data += + String.fromCharCode(i >> 8 & 0xFF) + + String.fromCharCode(i & 0xFF); + return this; +}; + +/** + * Puts a 24-bit integer in this buffer in big-endian order. + * + * @param i the 24-bit integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt24 = function(i) { + this.data += + String.fromCharCode(i >> 16 & 0xFF) + + String.fromCharCode(i >> 8 & 0xFF) + + String.fromCharCode(i & 0xFF); + return this; +}; + +/** + * Puts a 32-bit integer in this buffer in big-endian order. + * + * @param i the 32-bit integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt32 = function(i) { + this.data += + String.fromCharCode(i >> 24 & 0xFF) + + String.fromCharCode(i >> 16 & 0xFF) + + String.fromCharCode(i >> 8 & 0xFF) + + String.fromCharCode(i & 0xFF); + return this; +}; + +/** + * Puts a 16-bit integer in this buffer in little-endian order. + * + * @param i the 16-bit integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt16Le = function(i) { + this.data += + String.fromCharCode(i & 0xFF) + + String.fromCharCode(i >> 8 & 0xFF); + return this; +}; + +/** + * Puts a 24-bit integer in this buffer in little-endian order. + * + * @param i the 24-bit integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt24Le = function(i) { + this.data += + String.fromCharCode(i & 0xFF) + + String.fromCharCode(i >> 8 & 0xFF) + + String.fromCharCode(i >> 16 & 0xFF); + return this; +}; + +/** + * Puts a 32-bit integer in this buffer in little-endian order. + * + * @param i the 32-bit integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt32Le = function(i) { + this.data += + String.fromCharCode(i & 0xFF) + + String.fromCharCode(i >> 8 & 0xFF) + + String.fromCharCode(i >> 16 & 0xFF) + + String.fromCharCode(i >> 24 & 0xFF); + return this; +}; + +/** + * Puts an n-bit integer in this buffer in big-endian order. + * + * @param i the n-bit integer. + * @param n the number of bits in the integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putInt = function(i, n) { + do { + n -= 8; + this.data += String.fromCharCode((i >> n) & 0xFF); + } + while(n > 0); + return this; +}; + +/** + * Puts a signed n-bit integer in this buffer in big-endian order. Two's + * complement representation is used. + * + * @param i the n-bit integer. + * @param n the number of bits in the integer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putSignedInt = function(i, n) { + if(i < 0) { + i += 2 << (n - 1); + } + return this.putInt(i, n); +}; + +/** + * Puts the given buffer into this buffer. + * + * @param buffer the buffer to put into this one. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.putBuffer = function(buffer) { + this.data += buffer.getBytes(); + return this; +}; + +/** + * Gets a byte from this buffer and advances the read pointer by 1. + * + * @return the byte. + */ +util.ByteBuffer.prototype.getByte = function() { + return this.data.charCodeAt(this.read++); +}; + +/** + * Gets a uint16 from this buffer in big-endian order and advances the read + * pointer by 2. + * + * @return the uint16. + */ +util.ByteBuffer.prototype.getInt16 = function() { + var rval = ( + this.data.charCodeAt(this.read) << 8 ^ + this.data.charCodeAt(this.read + 1)); + this.read += 2; + return rval; +}; + +/** + * Gets a uint24 from this buffer in big-endian order and advances the read + * pointer by 3. + * + * @return the uint24. + */ +util.ByteBuffer.prototype.getInt24 = function() { + var rval = ( + this.data.charCodeAt(this.read) << 16 ^ + this.data.charCodeAt(this.read + 1) << 8 ^ + this.data.charCodeAt(this.read + 2)); + this.read += 3; + return rval; +}; + +/** + * Gets a uint32 from this buffer in big-endian order and advances the read + * pointer by 4. + * + * @return the word. + */ +util.ByteBuffer.prototype.getInt32 = function() { + var rval = ( + this.data.charCodeAt(this.read) << 24 ^ + this.data.charCodeAt(this.read + 1) << 16 ^ + this.data.charCodeAt(this.read + 2) << 8 ^ + this.data.charCodeAt(this.read + 3)); + this.read += 4; + return rval; +}; + +/** + * Gets a uint16 from this buffer in little-endian order and advances the read + * pointer by 2. + * + * @return the uint16. + */ +util.ByteBuffer.prototype.getInt16Le = function() { + var rval = ( + this.data.charCodeAt(this.read) ^ + this.data.charCodeAt(this.read + 1) << 8); + this.read += 2; + return rval; +}; + +/** + * Gets a uint24 from this buffer in little-endian order and advances the read + * pointer by 3. + * + * @return the uint24. + */ +util.ByteBuffer.prototype.getInt24Le = function() { + var rval = ( + this.data.charCodeAt(this.read) ^ + this.data.charCodeAt(this.read + 1) << 8 ^ + this.data.charCodeAt(this.read + 2) << 16); + this.read += 3; + return rval; +}; + +/** + * Gets a uint32 from this buffer in little-endian order and advances the read + * pointer by 4. + * + * @return the word. + */ +util.ByteBuffer.prototype.getInt32Le = function() { + var rval = ( + this.data.charCodeAt(this.read) ^ + this.data.charCodeAt(this.read + 1) << 8 ^ + this.data.charCodeAt(this.read + 2) << 16 ^ + this.data.charCodeAt(this.read + 3) << 24); + this.read += 4; + return rval; +}; + +/** + * Gets an n-bit integer from this buffer in big-endian order and advances the + * read pointer by n/8. + * + * @param n the number of bits in the integer. + * + * @return the integer. + */ +util.ByteBuffer.prototype.getInt = function(n) { + var rval = 0; + do { + rval = (rval << 8) + this.data.charCodeAt(this.read++); + n -= 8; + } + while(n > 0); + return rval; +}; + +/** + * Gets a signed n-bit integer from this buffer in big-endian order, using + * two's complement, and advances the read pointer by n/8. + * + * @param n the number of bits in the integer. + * + * @return the integer. + */ +util.ByteBuffer.prototype.getSignedInt = function(n) { + var x = this.getInt(n); + var max = 2 << (n - 2); + if(x >= max) { + x -= max << 1; + } + return x; +}; + +/** + * Reads bytes out into a UTF-8 string and clears them from the buffer. + * + * @param count the number of bytes to read, undefined or null for all. + * + * @return a UTF-8 string of bytes. + */ +util.ByteBuffer.prototype.getBytes = function(count) { + var rval; + if(count) { + // read count bytes + count = Math.min(this.length(), count); + rval = this.data.slice(this.read, this.read + count); + this.read += count; + } + else if(count === 0) { + rval = ''; + } + else { + // read all bytes, optimize to only copy when needed + rval = (this.read === 0) ? this.data : this.data.slice(this.read); + this.clear(); + } + return rval; +}; + +/** + * Gets a UTF-8 encoded string of the bytes from this buffer without modifying + * the read pointer. + * + * @param count the number of bytes to get, omit to get all. + * + * @return a string full of UTF-8 encoded characters. + */ +util.ByteBuffer.prototype.bytes = function(count) { + return (typeof(count) === 'undefined' ? + this.data.slice(this.read) : + this.data.slice(this.read, this.read + count)); +}; + +/** + * Gets a byte at the given index without modifying the read pointer. + * + * @param i the byte index. + * + * @return the byte. + */ +util.ByteBuffer.prototype.at = function(i) { + return this.data.charCodeAt(this.read + i); +}; + +/** + * Puts a byte at the given index without modifying the read pointer. + * + * @param i the byte index. + * @param b the byte to put. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.setAt = function(i, b) { + this.data = this.data.substr(0, this.read + i) + + String.fromCharCode(b) + + this.data.substr(this.read + i + 1); + return this; +}; + +/** + * Gets the last byte without modifying the read pointer. + * + * @return the last byte. + */ +util.ByteBuffer.prototype.last = function() { + return this.data.charCodeAt(this.data.length - 1); +}; + +/** + * Creates a copy of this buffer. + * + * @return the copy. + */ +util.ByteBuffer.prototype.copy = function() { + var c = util.createBuffer(this.data); + c.read = this.read; + return c; +}; + +/** + * Compacts this buffer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.compact = function() { + if(this.read > 0) { + this.data = this.data.slice(this.read); + this.read = 0; + } + return this; +}; + +/** + * Clears this buffer. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.clear = function() { + this.data = ''; + this.read = 0; + return this; +}; + +/** + * Shortens this buffer by triming bytes off of the end of this buffer. + * + * @param count the number of bytes to trim off. + * + * @return this buffer. + */ +util.ByteBuffer.prototype.truncate = function(count) { + var len = Math.max(0, this.length() - count); + this.data = this.data.substr(this.read, len); + this.read = 0; + return this; +}; + +/** + * Converts this buffer to a hexadecimal string. + * + * @return a hexadecimal string. + */ +util.ByteBuffer.prototype.toHex = function() { + var rval = ''; + for(var i = this.read; i < this.data.length; ++i) { + var b = this.data.charCodeAt(i); + if(b < 16) { + rval += '0'; + } + rval += b.toString(16); + } + return rval; +}; + +/** + * Converts this buffer to a UTF-16 string (standard JavaScript string). + * + * @return a UTF-16 string. + */ +util.ByteBuffer.prototype.toString = function() { + return util.decodeUtf8(this.bytes()); +}; + +/** + * Creates a buffer that stores bytes. A value may be given to put into the + * buffer that is either a string of bytes or a UTF-16 string that will + * be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding). + * + * @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode + * as UTF-8. + * @param [encoding] (default: 'raw', other: 'utf8'). + */ +util.createBuffer = function(input, encoding) { + encoding = encoding || 'raw'; + if(input !== undefined && encoding === 'utf8') { + input = util.encodeUtf8(input); + } + return new util.ByteBuffer(input); +}; + +/** + * Fills a string with a particular value. If you want the string to be a byte + * string, pass in String.fromCharCode(theByte). + * + * @param c the character to fill the string with, use String.fromCharCode + * to fill the string with a byte value. + * @param n the number of characters of value c to fill with. + * + * @return the filled string. + */ +util.fillString = function(c, n) { + var s = ''; + while(n > 0) { + if(n & 1) { + s += c; + } + n >>>= 1; + if(n > 0) { + c += c; + } + } + return s; +}; + +/** + * Performs a per byte XOR between two byte strings and returns the result as a + * string of bytes. + * + * @param s1 first string of bytes. + * @param s2 second string of bytes. + * @param n the number of bytes to XOR. + * + * @return the XOR'd result. + */ +util.xorBytes = function(s1, s2, n) { + var s3 = ''; + var b = ''; + var t = ''; + var i = 0; + var c = 0; + for(; n > 0; --n, ++i) { + b = s1.charCodeAt(i) ^ s2.charCodeAt(i); + if(c >= 10) { + s3 += t; + t = ''; + c = 0; + } + t += String.fromCharCode(b); + ++c; + } + s3 += t; + return s3; +}; + +/** + * Converts a hex string into a UTF-8 string of bytes. + * + * @param hex the hexadecimal string to convert. + * + * @return the string of bytes. + */ +util.hexToBytes = function(hex) { + var rval = ''; + var i = 0; + if(hex.length & 1 == 1) { + // odd number of characters, convert first character alone + i = 1; + rval += String.fromCharCode(parseInt(hex[0], 16)); + } + // convert 2 characters (1 byte) at a time + for(; i < hex.length; i += 2) { + rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + return rval; +}; + +/** + * Converts a UTF-8 byte string into a string of hexadecimal characters. + * + * @param bytes the byte string to convert. + * + * @return the string of hexadecimal characters. + */ +util.bytesToHex = function(bytes) { + return util.createBuffer(bytes).toHex(); +}; + +/** + * Converts an 32-bit integer to 4-big-endian byte string. + * + * @param i the integer. + * + * @return the byte string. + */ +util.int32ToBytes = function(i) { + return ( + String.fromCharCode(i >> 24 & 0xFF) + + String.fromCharCode(i >> 16 & 0xFF) + + String.fromCharCode(i >> 8 & 0xFF) + + String.fromCharCode(i & 0xFF)); +}; + +// base64 characters, reverse mapping +var _base64 = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; +var _base64Idx = [ +/*43 -43 = 0*/ +/*'+', 1, 2, 3,'/' */ + 62, -1, -1, -1, 63, + +/*'0','1','2','3','4','5','6','7','8','9' */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + +/*15, 16, 17,'=', 19, 20, 21 */ + -1, -1, -1, 64, -1, -1, -1, + +/*65 - 43 = 22*/ +/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + +/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */ + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + +/*91 - 43 = 48 */ +/*48, 49, 50, 51, 52, 53 */ + -1, -1, -1, -1, -1, -1, + +/*97 - 43 = 54*/ +/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */ + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + +/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */ + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +]; + +/** + * Base64 encodes a UTF-8 string of bytes. + * + * @param input the UTF-8 string of bytes to encode. + * @param maxline the maximum number of encoded bytes per line to use, + * defaults to none. + * + * @return the base64-encoded output. + */ +util.encode64 = function(input, maxline) { + var line = ''; + var output = ''; + var chr1, chr2, chr3; + var i = 0; + while(i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + // encode 4 character group + line += _base64.charAt(chr1 >> 2); + line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4)); + if(isNaN(chr2)) { + line += '=='; + } + else { + line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6)); + line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63); + } + + if(maxline && line.length > maxline) { + output += line.substr(0, maxline) + '\r\n'; + line = line.substr(maxline); + } + } + output += line; + + return output; +}; + +/** + * Base64 decodes a string into a UTF-8 string of bytes. + * + * @param input the base64-encoded input. + * + * @return the raw bytes. + */ +util.decode64 = function(input) { + // remove all non-base64 characters + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + + var output = ''; + var enc1, enc2, enc3, enc4; + var i = 0; + + while(i < input.length) { + enc1 = _base64Idx[input.charCodeAt(i++) - 43]; + enc2 = _base64Idx[input.charCodeAt(i++) - 43]; + enc3 = _base64Idx[input.charCodeAt(i++) - 43]; + enc4 = _base64Idx[input.charCodeAt(i++) - 43]; + + output += String.fromCharCode((enc1 << 2) | (enc2 >> 4)); + if(enc3 !== 64) { + // decoded at least 2 bytes + output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2)); + if(enc4 !== 64) { + // decoded 3 bytes + output += String.fromCharCode(((enc3 & 3) << 6) | enc4); + } + } + } + + return output; +}; + +/** + * UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript + * string). Non-ASCII characters will be encoded as multiple bytes according + * to UTF-8. + * + * @param str the string to encode. + * + * @return the UTF-8 encoded string. + */ +util.encodeUtf8 = function(str) { + return unescape(encodeURIComponent(str)); +}; + +/** + * Decodes a UTF-8 encoded string into a UTF-16 string. + * + * @param str the string to encode. + * + * @return the UTF-16 encoded string (standard JavaScript string). + */ +util.decodeUtf8 = function(str) { + return decodeURIComponent(escape(str)); +}; diff --git a/src/crypto/hash/index.js b/src/crypto/hash/index.js index 9dcd6a99..8d35a38d 100644 --- a/src/crypto/hash/index.js +++ b/src/crypto/hash/index.js @@ -2,7 +2,8 @@ * @requires crypto/hash/sha * @module crypto/hash */ -var sha = require('./sha.js'); +var sha = require('./sha.js'), + forge_sha256 = require('./forge_sha256.js'); module.exports = { /** @see module:crypto/hash/md5 */ @@ -39,7 +40,9 @@ module.exports = { return this.ripemd(data); case 8: // - SHA256 [FIPS180] - return this.sha256(data); + var sha256 = forge_sha256.create(); + sha256.update(data); + return sha256.digest().getBytes(); case 9: // - SHA384 [FIPS180] return this.sha384(data); From 01a0a218c7ba05f3c92a5dc57aac514b6935804b Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 13:19:41 +0100 Subject: [PATCH 7/9] update becnhmark test --- test/general/basic.js | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/test/general/basic.js b/test/general/basic.js index a5dc2ed3..3a27d572 100644 --- a/test/general/basic.js +++ b/test/general/basic.js @@ -61,23 +61,51 @@ describe('Basic', function() { done(); }); it('Performance test', function (done) { + // init test data function randomString(length, chars) { var result = ''; for (var i = length; i > 0; --i) result += chars[Math.round(Math.random() * (chars.length - 1))]; return result; } - var rString = randomString(1024*1024, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); + var message = randomString(1024*1024*3, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); + + var userid = 'Test McTestington '; + var passphrase = 'password'; + + var key = openpgp.generateKeyPair(openpgp.enums.publicKey.rsa_encrypt_sign, 512, userid, passphrase); + + var info = '\npassphrase: ' + passphrase + '\n' + 'userid: ' + userid + '\n' + 'message: ' + message; + + var privKeys = openpgp.key.readArmored(key.privateKeyArmored); + var publicKeys = openpgp.key.readArmored(key.publicKeyArmored); + + var privKey = privKeys.keys[0]; + var pubKey = publicKeys.keys[0]; + + var success = privKey.decrypt(passphrase); if (console.profile) { console.profile("encrypt/sign/verify/decrypt"); } - testHelper('password', 'Test McTestington ', rString); + var encrypted = openpgp.signAndEncryptMessage([pubKey], privKey, message); if (console.profileEnd) { console.profileEnd(); } + var msg = openpgp.message.readArmored(encrypted); + + var keyids = msg.getEncryptionKeyIds(); + + expect(keyids).to.exist; + + var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + + expect(decrypted).to.exist; + expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.text).to.equal(message); + done(); }); }); From ec4a0aca463529b3dbebb5b009f9d934c9b242c1 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 14:38:52 +0100 Subject: [PATCH 8/9] use typed array in aes and cfb, cleanup for loops --- src/crypto/cfb.js | 12 +++---- src/crypto/cipher/aes.js | 68 +++++++++++++++++++++------------------ test/crypto/cipher/aes.js | 4 +-- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index 6d21c3e4..3f556ec3 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -48,8 +48,8 @@ module.exports = { cipherfn = new cipher[cipherfn](key); var block_size = cipherfn.blockSize; - var FR = new Array(block_size); - var FRE = new Array(block_size); + var FR = new Uint8Array(block_size); + var FRE = new Uint8Array(block_size); prefixrandom = prefixrandom + prefixrandom.charAt(block_size - 2) + prefixrandom.charAt(block_size - 1); util.print_debug("prefixrandom:" + util.hexstrdump(prefixrandom)); @@ -173,8 +173,8 @@ module.exports = { cipherfn = new cipher[cipherfn](key); var block_size = cipherfn.blockSize; - var iblock = new Array(block_size); - var ablock = new Array(block_size); + var iblock = new Uint8Array(block_size); + var ablock = new Uint8Array(block_size); var i; @@ -214,8 +214,8 @@ module.exports = { cipherfn = new cipher[cipherfn](key); var block_size = cipherfn.blockSize; - var iblock = new Array(block_size); - var ablock = new Array(block_size); + var iblock = new Uint8Array(block_size); + var ablock = new Uint8Array(block_size); var i, n = ''; var text = ''; diff --git a/src/crypto/cipher/aes.js b/src/crypto/cipher/aes.js index db3a76fe..386fe6b3 100644 --- a/src/crypto/cipher/aes.js +++ b/src/crypto/cipher/aes.js @@ -21,14 +21,14 @@ var util = require('../../util.js'); // The round constants used in subkey expansion -var Rcon = [ +var Rcon = new Uint8Array([ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 -]; +]); // Precomputed lookup table for the SBox -var S = [ +var S = new Uint8Array([ 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, @@ -47,9 +47,9 @@ var S = [ 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 -]; +]); -var T1 = [ +var T1 = new Uint32Array([ 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, @@ -114,9 +114,9 @@ var T1 = [ 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c -]; +]); -var T2 = [ +var T2 = new Uint32Array([ 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, @@ -181,9 +181,9 @@ var T2 = [ 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a -]; +]); -var T3 = [ +var T3 = new Uint32Array([ 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, @@ -248,9 +248,9 @@ var T3 = [ 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16 -]; +]); -var T4 = [ +var T4 = new Uint32Array([ 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, @@ -315,7 +315,7 @@ var T4 = [ 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616 -]; +]); function B0(x) { return (x & 255); @@ -334,8 +334,7 @@ function B3(x) { } function F1(x0, x1, x2, x3) { - return B1(T1[x0 & 255]) | (B1(T1[(x1 >> 8) & 255]) << 8) | (B1(T1[(x2 >> 16) & 255]) << 16) | (B1(T1[x3 >>> 24]) << - 24); + return B1(T1[x0 & 255]) | (B1(T1[(x1 >> 8) & 255]) << 8) | (B1(T1[(x2 >> 16) & 255]) << 16) | (B1(T1[x3 >>> 24]) << 24); } function packBytes(octets) { @@ -345,8 +344,9 @@ function packBytes(octets) { if (!octets || len % 4) return; - for (i = 0, j = 0; j < len; j += 4) + for (i = 0, j = 0; j < len; j += 4) { b[i++] = octets[j] | (octets[j + 1] << 8) | (octets[j + 2] << 16) | (octets[j + 3] << 24); + } return b; } @@ -393,20 +393,22 @@ function keyExpansion(key) { throw new Error('Invalid key-length for AES key:' + keylen); } - for (i = 0; i < maxrk + 1; i++) keySched[i] = new Array(4); + for (i = 0; i < maxrk + 1; i++) { + keySched[i] = new Uint32Array(4); + } - for (i = 0, j = 0; j < keylen; j++, i += 4) - k[j] = key.charCodeAt(i) | (key.charCodeAt(i + 1) << 8) | (key.charCodeAt(i + 2) << 16) | (key.charCodeAt(i + 3) << - 24); + for (i = 0, j = 0; j < keylen; j++, i += 4) { + k[j] = key.charCodeAt(i) | (key.charCodeAt(i + 1) << 8) | (key.charCodeAt(i + 2) << 16) | (key.charCodeAt(i + 3) << 24); + } - for (j = kc - 1; j >= 0; j--) tk[j] = k[j]; + for (j = kc - 1; j >= 0; j--) { + tk[j] = k[j]; + } r = 0; t = 0; - for (j = 0; - (j < kc) && (r < rounds + 1);) { - for (; - (j < kc) && (t < 4); j++, t++) { + for (j = 0; (j < kc) && (r < rounds + 1);) { + for (; (j < kc) && (t < 4); j++, t++) { keySched[r][t] = tk[j]; } if (t == 4) { @@ -422,20 +424,24 @@ function keyExpansion(key) { tk[0] ^= Rcon[rconpointer++]; if (kc != 8) { - for (j = 1; j < kc; j++) tk[j] ^= tk[j - 1]; + for (j = 1; j < kc; j++) { + tk[j] ^= tk[j - 1]; + } } else { - for (j = 1; j < kc / 2; j++) tk[j] ^= tk[j - 1]; + for (j = 1; j < kc / 2; j++) { + tk[j] ^= tk[j - 1]; + } temp = tk[kc / 2 - 1]; tk[kc / 2] ^= S[B0(temp)] | (S[B1(temp)] << 8) | (S[B2(temp)] << 16) | (S[B3(temp)] << 24); - for (j = kc / 2 + 1; j < kc; j++) tk[j] ^= tk[j - 1]; + for (j = kc / 2 + 1; j < kc; j++) { + tk[j] ^= tk[j - 1]; + } } - for (j = 0; - (j < kc) && (r < rounds + 1);) { - for (; - (j < kc) && (t < 4); j++, t++) { + for (j = 0; (j < kc) && (r < rounds + 1);) { + for (; (j < kc) && (t < 4); j++, t++) { keySched[r][t] = tk[j]; } if (t == 4) { diff --git a/test/crypto/cipher/aes.js b/test/crypto/cipher/aes.js index 9acc36ea..833e3f90 100644 --- a/test/crypto/cipher/aes.js +++ b/test/crypto/cipher/aes.js @@ -10,10 +10,10 @@ describe('AES Rijndael cipher test with test vectors from ecb_tbl.txt', function function test_aes(input, key, output) { var aes = new openpgp.crypto.cipher.aes128(util.bin2str(key)); - var result = util.bin2str(aes.encrypt(input)); + var result = util.bin2str(aes.encrypt(new Uint8Array(input))); return util.hexstrdump(result) == util.hexstrdump(util.bin2str(output)); - }; + } var testvectors128 = [[[0x00,0x01,0x02,0x03,0x05,0x06,0x07,0x08,0x0A,0x0B,0x0C,0x0D,0x0F,0x10,0x11,0x12],[0x50,0x68,0x12,0xA4,0x5F,0x08,0xC8,0x89,0xB9,0x7F,0x59,0x80,0x03,0x8B,0x83,0x59],[0xD8,0xF5,0x32,0x53,0x82,0x89,0xEF,0x7D,0x06,0xB5,0x06,0xA4,0xFD,0x5B,0xE9,0xC9]], [[0x14,0x15,0x16,0x17,0x19,0x1A,0x1B,0x1C,0x1E,0x1F,0x20,0x21,0x23,0x24,0x25,0x26],[0x5C,0x6D,0x71,0xCA,0x30,0xDE,0x8B,0x8B,0x00,0x54,0x99,0x84,0xD2,0xEC,0x7D,0x4B],[0x59,0xAB,0x30,0xF4,0xD4,0xEE,0x6E,0x4F,0xF9,0x90,0x7E,0xF6,0x5B,0x1F,0xB6,0x8C]], From 9e23b94db39cd834b6fefd4835535e42a02d830c Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 25 Feb 2014 14:55:29 +0100 Subject: [PATCH 9/9] clean wroning api usage in cfb --- src/crypto/cfb.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index 3f556ec3..e10dc0e6 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -96,7 +96,7 @@ module.exports = { } } // 8. FR is encrypted to produce FRE. - FRE = cipherfn.encrypt(FR, key); + FRE = cipherfn.encrypt(FR); if (resync) { // 9. FRE is xored with the first 8 octets of the given plaintext, now @@ -224,13 +224,13 @@ module.exports = { iblock[i] = 0; } - iblock = cipherfn.encrypt(iblock, key); + iblock = cipherfn.encrypt(iblock); for (i = 0; i < block_size; i++) { ablock[i] = ciphertext.charCodeAt(i); iblock[i] ^= ablock[i]; } - ablock = cipherfn.encrypt(ablock, key); + ablock = cipherfn.encrypt(ablock); // test check octets if (iblock[block_size - 2] != (ablock[0] ^ ciphertext.charCodeAt(block_size)) || iblock[block_size - 1] != (ablock[