Diffstat (limited to 'frontend/gamma/tests/tests/Clipperz/Crypto/jscrypto.js') (more/less context) (ignore whitespace changes)
-rw-r--r-- | frontend/gamma/tests/tests/Clipperz/Crypto/jscrypto.js | 1577 |
1 files changed, 1577 insertions, 0 deletions
diff --git a/frontend/gamma/tests/tests/Clipperz/Crypto/jscrypto.js b/frontend/gamma/tests/tests/Clipperz/Crypto/jscrypto.js new file mode 100644 index 0000000..e9db091 --- a/dev/null +++ b/frontend/gamma/tests/tests/Clipperz/Crypto/jscrypto.js @@ -0,0 +1,1577 @@ +/* + +Copyright 2008-2011 Clipperz Srl + +This file is part of Clipperz's Javascript Crypto Library. +Javascript Crypto Library provides web developers with an extensive +and efficient set of cryptographic functions. The library aims to +obtain maximum execution speed while preserving modularity and +reusability. +For further information about its features and functionalities please +refer to http://www.clipperz.com + +* Javascript Crypto Library is free software: you can redistribute + it and/or modify it under the terms of the GNU Affero General Public + License as published by the Free Software Foundation, either version + 3 of the License, or (at your option) any later version. + +* Javascript Crypto Library is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public + License along with Javascript Crypto Library. If not, see + <http://www.gnu.org/licenses/>. + +*/ + +/* jsCrypto +Core AES + +Emily Stark (estark@stanford.edu) +Mike Hamburg (mhamburg@stanford.edu) +Dan Boneh (dabo@cs.stanford.edu) + +Symmetric AES in Javascript using precomputed lookup tables for round transformations rather for speed improvements +and code size reduction. Provides authenticated encryption in OCB and CCM modes. +Parts of this code are based on the OpenSSL implementation of AES: http://www.openssl.org + +Public domain, 2009. + +*/ + + +// CCM mode is the default +var CCM = 1, OCB = 2; + +/* aes object constructor. Takes as arguments: +- 16-byte key, or an array of 4 32-bit words +- Optionally specify a mode (aes.OCB or aes.CCM). Defaults to CCM +- Optionally specify a MAC tag length for integrity. Defaults to 16 bytes +*/ +function aes(key, mode, Tlen) { + // initialize objects for CCM and OCB modes + this._CCM = new cipherCCM(this); + this._OCB = new cipherOCB(this); + + this._decryptScheduled = false; + + if (mode) this._mode = mode; + else this._mode = OCB; + + // AES round constants + this._RCON = [ + [0x00, 0x00, 0x00, 0x00], + [0x01, 0x00, 0x00, 0x00], + [0x02, 0x00, 0x00, 0x00], + [0x04, 0x00, 0x00, 0x00], + [0x08, 0x00, 0x00, 0x00], + [0x10, 0x00, 0x00, 0x00], + [0x20, 0x00, 0x00, 0x00], + [0x40, 0x00, 0x00, 0x00], + [0x80, 0x00, 0x00, 0x00], + [0x1b, 0x00, 0x00, 0x00], + [0x36, 0x00, 0x00, 0x00] + ]; + + this._key_len = 16; + if (key.length == 4) { + this._key = []; + aes.wordsToBytes(key, this._key); + } + else + this._key = key; + + if (Tlen) this._Tlen = Tlen; + else this._Tlen = 16; // tag length in bytes + + this._nr = 10; + + // initialize tables that will be precomputed + this._SBOX = []; + this._INV_SBOX = []; + this._T = new Array(4); + this._Tin = new Array(4); + for (var i=0; i < 4; i++) { + this._T[i] = []; + this._Tin[i] = []; + } + + this._precompute(); + this.scheduleEncrypt(); + + // initialize encryption and decryption buffers + this._ctBuffer = []; + this._ptBuffer = []; +} + + +////////////////// +// KEY SCHEDULING +////////////////// + +aes.prototype.scheduleEncrypt = function () { + this._decryptScheduled = false; + + this._w = new Array(this._nr+1); + this._w[0] = new Array(4); + for (var i=0; i < 4; i++) + this._w[0][i] = this._key[i]; + var temp = new Array(4); + for (var i=1; i < this._nr+1; i++) { + temp[0] = this._w[i-1][3]; + this._w[i] = new Array(4); + temp[0] = (this._T[2][(temp[0]>>>16)&0xff] & 0xff000000) ^ (this._T[3][(temp[0]>>>8)&0xff]&0x00ff0000) ^ (this._T[0][(temp[0]&0xff)]&0x0000ff00) ^ (this._T[1][(temp[0]>>>24)]&0x000000ff) ^ (this._RCON[i][0]<<24); + this._w[i][0] = this._w[i-1][0] ^ temp[0]; + for (var j=1; j < 4; j++) { + temp[j] = this._w[i][j-1]; + this._w[i][j] = this._w[i-1][j] ^ temp[j]; + } + } +}; + +aes.prototype.scheduleDecrypt = function() { + + if (!this._w) this.scheduleEncrypt(); + this._decryptScheduled = true; + var temp = []; + var j = this._w.length-1; + for (var i=0; i<j; i++) { + temp[0] = this._w[i][0]; + temp[1] = this._w[i][1]; + temp[2] = this._w[i][2]; + temp[3] = this._w[i][3]; + this._w[i][0] = this._w[j][0]; + this._w[i][1] = this._w[j][1]; + this._w[i][2] = this._w[j][2]; + this._w[i][3] = this._w[j][3]; + this._w[j][0] = temp[0]; + this._w[j][1] = temp[1]; + this._w[j][2] = temp[2]; + this._w[j][3] = temp[3]; + j--; + } + + var td0 = this._Tin[0], td1 = this._Tin[1], td2 = this._Tin[2], td3 = this._Tin[3], te1 = this._T[1]; + for (var i=1; i < this._w.length-1; i++) { + this._w[i][0] = td0[te1[(this._w[i][0] >>> 24) ] & 0xff] ^ + td1[te1[(this._w[i][0] >>> 16) & 0xff] & 0xff] ^ + td2[te1[(this._w[i][0] >>> 8) & 0xff] & 0xff] ^ + td3[te1[(this._w[i][0] ) & 0xff] & 0xff]; + this._w[i][1] = td0[te1[(this._w[i][1] >>> 24) ] & 0xff] ^ + td1[te1[(this._w[i][1] >>> 16) & 0xff] & 0xff] ^ + td2[te1[(this._w[i][1] >>> 8) & 0xff] & 0xff] ^ + td3[te1[(this._w[i][1] ) & 0xff] & 0xff]; + this._w[i][2] = td0[te1[(this._w[i][2] >>> 24) ] & 0xff] ^ + td1[te1[(this._w[i][2] >>> 16) & 0xff] & 0xff] ^ + td2[te1[(this._w[i][2] >>> 8) & 0xff] & 0xff] ^ + td3[te1[(this._w[i][2] ) & 0xff] & 0xff]; + this._w[i][3] = td0[te1[(this._w[i][3] >>> 24) ] & 0xff] ^ + td1[te1[(this._w[i][3] >>> 16) & 0xff] & 0xff] ^ + td2[te1[(this._w[i][3] >>> 8) & 0xff] & 0xff] ^ + td3[te1[(this._w[i][3] ) & 0xff] & 0xff]; + } +}; + + +///////////////////////// +// ENCRYPTION/DECRYPTION +///////////////////////// + + +/* Authenticated encryption on a multi-block message in OCB or CCM mode. +iv should be an array of 32-bit words - either 4 words for OCB mode or 1, 2, or 3 words for CCM. +Use a unique IV for every message encrypted. +The plaintext argument will be encrypted and MACed; adata will be sent in plaintext but MACed. +Plaintext and adata are strings. +ciphertext is an array of bytes. tag is an array of 32-bit words. +*/ +aes.prototype.encrypt = function(iv, plaintext, ciphertext, adata, tag) { + var plaintextBytes = [], adataBytes = []; + aes.asciiToBytes(plaintext, plaintextBytes); + aes.asciiToBytes(adata, adataBytes); + + this._iv = iv; + if (this._mode == CCM) + this._CCM.encrypt(plaintextBytes, ciphertext, adataBytes, tag); + else if (this._mode == OCB) { + this._OCB.encrypt(plaintextBytes, ciphertext, adataBytes, tag); + } + + // prepend to the ciphertext the length of the iv (in bytes) and the iv + var ivbytes=[]; + aes.wordsToBytes(iv, ivbytes); + var ct = [iv.length*4].concat(ivbytes, ciphertext); + for (var i=0; i < ct.length; i++) ciphertext[i] = ct[i]; + + for (var i=0; i < ciphertext.length; i++) + this._ctBuffer[this._ctBuffer.length] = ciphertext[i]; + +}; + +/* Authenticated decryption on a multi-block ciphertext in OCB or CCM mode. +ciphertext is an array of bytes. tag is an array of 32-bit words. +plaintext and adata are strings. +*/ +aes.prototype.decrypt = function(ciphertext, adata, tag) { + var ivlen = ciphertext[0]; + var ivbytes = ciphertext.slice(1, ivlen+1); + var iv = []; + aes.bytesToWords(ivbytes, iv); + this._iv = iv; + var ct = ciphertext.slice(ivlen+1); + + var valid = false; + var plaintextBytes = [], adataBytes = []; + aes.asciiToBytes(adata, adataBytes); + if (this._mode == CCM) + valid = this._CCM.decrypt(ct, plaintextBytes, adataBytes); + else if (this._mode == OCB) + valid = this._OCB.decrypt(ct, plaintextBytes, adataBytes, tag); + if (valid) { + var plaintext = aes.bytesToAscii(plaintextBytes); + for (var i=0; i < plaintext.length; i++) + this._ptBuffer[this._ptBuffer.length] = plaintext.charAt(i); + return plaintext; + } + return ""; +}; + +// MACs (but doesn't encrypt) data using CMAC (in CCM mode) or PMAC (in OCB mode) +aes.prototype.sign = function(data, tag) { + if (this._mode == CCM) + this._CCM.CMAC(data, "", tag, this._Tlen, false); + else if (this._mode == OCB) { + this._OCB.PMAC(data, tag); + } +}; + +// Verifies a CMAC or PMAC tag +aes.prototype.verify = function(data, tag) { + var validTag = []; + if (this._mode == CCM) + this._CCM.CMAC(data, "", validTag, this._Tlen, false); + else if (this._mode == OCB) { + this._OCB.PMAC(data, validTag); + } + if (validTag.length != tag.length) return false; + for (var i=0; i < tag.length; i++) { + if (tag[i] != validTag[i]) return false; + } + return true; +}; + +/* Encrypts a single block message in AES. Takes the plaintext, an array in which to dump +the ciphertext, and a boolean decrypt argument. If set to true, this function acts as +a decryption function. +block and ciphertext are both arrays of 4 32-bit words. +*/ +aes.prototype.encryptBlock = function(block, ciphertext, decrypt) { + if (!decrypt && this._decryptScheduled) this.scheduleEncrypt(); + + // get key schedule + var w = this._w; + // load round transformation tables + var te0, te1, te2, te3; + if (decrypt) { + te0 = this._Tin[0]; + te1 = this._Tin[1]; + te2 = this._Tin[2]; + te3 = this._Tin[3]; + } else { + te0 = this._T[0]; + te1 = this._T[1]; + te2 = this._T[2]; + te3 = this._T[3]; + } + + // perform rounds + var rk = w[0]; + var s0 = block[0] ^ rk[0]; + var s1 = block[1] ^ rk[1]; + var s2 = block[2] ^ rk[2]; + var s3 = block[3] ^ rk[3]; + var t0,t1,t2,t3; + rk = w[1]; + var order = []; + for (var round = 1; round < w.length-1; round++) { + order = [s1, s2, s3, s0]; + if (decrypt) order = [s3, s0, s1, s2]; + t0 = te0[(s0>>>24)] ^ te1[(order[0]>>>16) & 0xff]^ te2[(s2>>>8)&0xff] ^ te3[order[2]&0xff] ^ rk[0]; + t1 = te0[(s1>>>24)] ^ te1[(order[1]>>>16) & 0xff]^ te2[(s3>>>8)&0xff] ^ te3[order[3]&0xff] ^ rk[1]; + t2 = te0[(s2>>>24)] ^ te1[(order[2]>>>16) & 0xff]^ te2[(s0>>>8)&0xff] ^ te3[order[0]&0xff] ^ rk[2]; + t3 = te0[(s3>>>24)] ^ te1[(order[3]>>>16) & 0xff]^ te2[(s1>>>8)&0xff] ^ te3[order[1]&0xff] ^ rk[3]; + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + rk = w[round+1]; + } + if (decrypt) { + s0 = ((this._INV_SBOX[(t0>>>24)])<<24) ^ ((this._INV_SBOX[(t3>>>16)&0xff])<<16) ^ ((this._INV_SBOX[(t2>>>8)&0xff])<<8) ^ (this._INV_SBOX[(t1)&0xff]) ^ rk[0]; + s1 = ((this._INV_SBOX[(t1>>>24)])<<24) ^ ((this._INV_SBOX[(t0>>>16)&0xff])<<16) ^ ((this._INV_SBOX[(t3>>>8)&0xff])<<8) ^ (this._INV_SBOX[(t2)&0xff]) ^ rk[1] + s2 = ((this._INV_SBOX[(t2>>>24)])<<24) ^ ((this._INV_SBOX[(t1>>>16)&0xff])<<16) ^ ((this._INV_SBOX[(t0>>>8)&0xff])<<8) ^ (this._INV_SBOX[(t3)&0xff]) ^ rk[2]; + s3 = (this._INV_SBOX[(t3>>>24)]<<24) ^ (this._INV_SBOX[(t2>>>16)&0xff]<<16) ^ (this._INV_SBOX[(t1>>>8)&0xff]<<8) ^ (this._INV_SBOX[(t0)&0xff]) ^ rk[3]; + } else { + s0 = (te2[t0>>>24]&0xff000000) ^ (te3[(t1>>>16)&0xff]&0x00ff0000) ^ (te0[(t2>>>8)&0xff]&0x0000ff00) ^ (te1[(t3)&0xff]&0x000000ff) ^ rk[0]; + s1 = (te2[t1>>>24]&0xff000000) ^ (te3[(t2>>>16)&0xff]&0x00ff0000) ^ (te0[(t3>>>8)&0xff]&0x0000ff00) ^ (te1[(t0)&0xff]&0x000000ff) ^ rk[1]; + s2 = (te2[t2>>>24]&0xff000000) ^ (te3[(t3>>>16)&0xff]&0x00ff0000) ^ (te0[(t0>>>8)&0xff]&0x0000ff00) ^ (te1[(t1)&0xff]&0x000000ff) ^ rk[2]; + s3 = (te2[t3>>>24]&0xff000000) ^ (te3[(t0>>>16)&0xff]&0x00ff0000) ^ (te0[(t1>>>8)&0xff]&0x0000ff00) ^ (te1[(t2)&0xff]&0x000000ff) ^ rk[3]; + } + ciphertext[0] = s0; + ciphertext[1] = s1; + ciphertext[2] = s2; + ciphertext[3] = s3; +}; + +// As above, block and plaintext are arrays of 4 32-bit words. +aes.prototype.decryptBlock = function(block, plaintext) { + if (!this._decryptScheduled) this.scheduleDecrypt(); + + this.encryptBlock(block, plaintext, true); +}; + + +//////////////////// +// HELPER FUNCTIONS +//////////////////// + +aes._hex = function(n) { + var out = "",i,digits="0123456789ABCDEF"; + for (i=0; i<8; i++) { + var digit = n&0xF; + out = digits.substring(digit,digit+1) + out; + n = n >>> 4; + } + return out; +} + +aes._hexall = function(nn) { + var out = "",i; + for (i=0;i<nn.length;i++) { + if (i%4 == 0) out+= "<br/>\n"; + else if (i) out += " "; + out += aes._hex(nn[i]); + } + return out; +} + +aes.bytesToAscii = function(bytes) { + var ascii = ""; + var len = bytes.length; + for (var i=0; i < len; i++) { + ascii = ascii + String.fromCharCode(bytes[i]); + } + return ascii; +}; + +aes.asciiToBytes = function(ascii, bytes) { + var len = ascii.length; + for (var i=0; i < len; i++) + bytes[i] = ascii.charCodeAt(i); +}; + +aes.wordsToBytes = function(words, bytes) { + var bitmask = 1; + for (var i=0; i < 7; i++) bitmask = (bitmask << 1) | 1; + for (var i=0; i < words.length; i++) { + var bstart = i*4; + for (var j=0; j < 4; j++) { + bytes[bstart+j] = (words[i] & (bitmask << (8*(3-j)))) >>> (8*(3-j)); + } + } +}; + +aes.bytesToWords = function(bytes, words) { + var paddedBytes = bytes.slice(); + while (paddedBytes.length % 4 != 0) paddedBytes.push(0); + var num_words = Math.floor(paddedBytes.length/4); + for (var j=0; j < num_words; j++) + words[j] = ((paddedBytes[(j<<2)+3]) | (paddedBytes[(j<<2)+2] << 8) | (paddedBytes[(j<<2)+1] << 16) | (paddedBytes[j<<2] << 24)); +}; + + +/////////////////////////////////////// +// KEY DERIVATION +////////////////////////////////////// + +// password is a string, presumably a password entered by the user. +// salt is eight random bytes associated with each user +// This function returns an array of bytes of length 16 +function generateKey(password, salt) { + var pwBytes = []; + aes.asciiToBytes(password, pwBytes); + var pwWords = [], saltWords = []; + aes.bytesToWords(pwBytes, pwWords); + aes.bytesToWords(salt, saltWords); + + var iterations = 1000; + + var derivedKey = []; + var blockIndex = 1; + + var xorHashes = function(h1, h2) { + var xor = []; + var i; + for (i=0; i < h1.length; i++) xor.push(h1[i] ^ h2[i]); + return xor; + }; + + while (derivedKey.length < 16) { + var hashBytes = pwWords.concat(saltWords); + hashBytes.push(blockIndex); + var T = SHA256.hash_words_big_endian(hashBytes); + var u = T; + for (var i=2; i < iterations; i++) { + var hash = SHA256.hash_words_big_endian(pwWords.concat(u)); + u = xorHashes(T, hash); + } + var block = []; + aes.wordsToBytes(T, block); + for (var i=0; i < block.length; i++) derivedKey.push(block[i]); + } + + if (derivedKey.length > 16) derivedKey.length = 16; + return derivedKey; +} + +/////////////////////////////////////// +// ROUND TRANSFORMATION PRECOMPUTATION +/////////////////////////////////////// + + +// Precomputation code by Mike Hamburg + +aes.prototype._precompute = function() { + var x,xi,sx,tx,tisx,i; + var d=[]; + + /* compute double table */ + for (x=0;x<256;x++) { + d[x]= x&128 ? x<<1 ^ 0x11b : x<<1; + //d[x] = x<<1 ^ (x>>7)*0x11b; //but I think that's less clear. + } + + /* Compute the round tables. + * + * We'll need access to x and x^-1, which we'll get by walking + * GF(2^8) as generated by (82,5). + */ + for(x=xi=0;;) { + // compute sx := sbox(x) + sx = xi^ xi<<1 ^ xi<<2 ^ xi<<3 ^ xi<<4; + sx = sx>>8 ^ sx&0xFF ^ 0x63; + + var dsx = d[sx], x2=d[x],x4=d[x2],x8=d[x4]; + + // te(x) = rotations of (2,1,1,3) * sx + tx = dsx<<24 ^ sx<<16 ^ sx<<8 ^ sx^dsx; + + // similarly, td(sx) = (E,9,D,B) * x + tisx = (x8^x4^x2) <<24 ^ + (x8^x ) <<16 ^ + (x8^x4^x ) << 8 ^ + (x8^x2^x ); + + // This can be done by multiplication instead but I think that's less clear + // tisx = x8*0x1010101 ^ x4*0x1000100 ^ x2*0x1000001 ^ x*0x10101; + // tx = dsx*0x1000001^sx*0x10101; + + // rotate and load + for (i=0;i<4;i++) { + this._T[i][x] = tx; + this._Tin[i][sx] = tisx; + tx = tx<<24 | tx>>>8; + tisx = tisx<<24 | tisx>>>8; + } + + // te[4] is the sbox; td[4] is its inverse + this._SBOX[ x] = sx; + this._INV_SBOX[sx] = x; + + + // wonky iteration goes through 0 + if (x==5) { + break; + } else if (x) { + x = x2^d[d[d[x8^x2]]]; // x *= 82 = 0b1010010 + xi ^= d[d[xi]]; // xi *= 5 = 0b101 + } else { + x=xi=1; + } + } + + // We computed the arrays out of order. On Firefox, this matters. + // Compact them. + for (i=0; i<4; i++) { + this._T[i] = this._T[i].slice(0); + this._Tin[i] = this._Tin[i].slice(0); + } + this._SBOX = this._SBOX.slice(0); + this._INV_SBOX = this._INV_SBOX.slice(0); + + +}; + + + + + +/* jsCrypto +CCM mode + +Emily Stark (estark@stanford.edu) +Mike Hamburg (mhamburg@stanford.edu) +Dan Boneh (dabo@cs.stanford.edu) + +CCM mode for authenticated encryption of multiple 16-byte blocks. Uses AES as core cipher. + +Public domain, 2009. + +*/ + +// Constructor takes an aes object as its core cipher +function cipherCCM(cipher) { + this._cipher = cipher; +} + +/* Formats plaintext and adata for MACing and encryption. +adata and plaintext are arrays of bytes, B will be an array of arrays of 16 bytes +Tlen specifies the number of bytes in the tag. +Formatted according to the CCM specification. + */ +cipherCCM.prototype._formatInput = function(adata, plaintext, Tlen, B) { + // compute B[0] + var flags, nbytes=[]; + aes.wordsToBytes(this._cipher._iv, nbytes); + if (adata) flags = 0x01<<6; + else flags = 0x00<<6; + flags = flags | (((Tlen-2)/2)<<3); // (t-2)/2 + var q = 15-this._cipher._iv.length*4; + flags = flags | (q-1); + B[0] = new Array(16); + B[0][0] = flags; + for (var i=1; i <= 15-q; i++) B[0][i] = nbytes[i-1]; + var Q = plaintext.length; + + // make some bitmasks + var bitmask = 1; + for (var i=0; i < 7; i++) bitmask = (bitmask<<1) | 1; + for (var i=15; i > 15-q; i--) { + B[0][i] = Q & bitmask; + Q = Q>>>8; + } + + // compute the blocks which identify adata + if (adata) { + var a = adata.length, Bind=1, BIind = 0, aind=0; + B[1] = new Array(16); + if (a < (2<<16 - 2<<8)) { + B[1][0] = a>>>8; + B[1][1] = a & bitmask; + BIind = 2; + } else if (a < (2<<32)) { + B[1][0] = 0xff; + B[1][1] = 0xfe; + for (var i=5; i >= 0; i--) { + B[1][2+i] = a & bitmask; + a = a>>>8; + } + BIind=8; + } else { + B[1][0] = 0xff; + B[1][0] = 0xff; + for (i=9; i >= 0; i--) { + B[1][2+i] = a & bitmask; + a = a >>> 8; + } + BIind = 12; + } + } + + while (aind < adata.length) { + B[Bind][BIind] = adata[aind]; + aind++; + if (BIind == 15) { + Bind++; + BIind = 0; + if (aind != adata.length) B[Bind] = new Array(16); + } else BIind++; + } + if (BIind != 0) { + while (BIind <= 15) { + B[Bind][BIind] = 0x00; + BIind++; + } + } + + Bind++; + BIind=0; + B[Bind] = new Array(16); + + // compute the payload blocks + var pind = 0; + while (pind < plaintext.length) { + B[Bind][BIind] = plaintext[pind]; + pind++; + if (BIind == 15) { + Bind++; + BIind = 0; + if (pind != plaintext.length) B[Bind] = new Array(16); + } else BIind++; + } + if (BIind != 0) { + while (BIind <= 15) { + B[Bind][BIind] = 0x00; + BIind++; + } + } + +}; + +/* Generate the blocks that will be used as counters. +ctr will be an array of m+1 arrays of 16 bytes. */ +cipherCCM.prototype._generateCtrBlocks = function(m, ctr) { + var nbytes = []; + aes.wordsToBytes(this._cipher._iv, nbytes); + var flags = 15 - (this._cipher._iv.length*4) - 1; + var bitmask = 1; + for (var i=0; i < 7; i++) bitmask = (bitmask<<1) | 1; + for (var i=0; i <= m; i++) { + ctr[i] = new Array(16); + ctr[i][0] = flags; + for (var j=0; j < nbytes.length; j++) { + ctr[i][j+1] = nbytes[j]; + } + for (var j=15; j > nbytes.length; j--) { + ctr[i][j] = (i>>>(8*(15-j))) & bitmask; + } + } + +}; + + +/* CBC-MAC adata and plaintext, and store the tag in tag. +adata and plaintext are arrays of bytes +tag will be an array of Tlen/4 32-bit words +Tlen is an integer divisible by 4 that specifies the number of bytes in the tag. +*/ +cipherCCM.prototype.CBCMAC = function(adata, plaintext, tag, Tlen, formatInput) { + var B = []; + if (formatInput) + this._formatInput(adata,plaintext,Tlen,B); + else { + var Sind = 0, SIind = 0, aind = 0, alen = adata.length; + B[0] = []; + while (aind < alen) { + B[Sind][SIind] = adata[aind]; + SIind++; + if (SIind == 16) { + SIind = 0; + Sind++; + if (aind != alen-1) B[Sind] = []; + } + aind++; + } + } + var words = []; + var Yprev = [], Y = []; + aes.bytesToWords(B[0],words); + this._cipher.encryptBlock(words, Y); + var r = B.length, t = new Array(4); + + for (var i=1; i < r; i++) { + for (var j=0; j < 4; j++) { + var bstart = j*4; + t[j] = Y[j] ^ ((B[i][bstart++]<<24) | (B[i][bstart++]<<16) | (B[i][bstart++]<<8) | (B[i][bstart++])); + Yprev[j] = Y[j]; + } + this._cipher.encryptBlock(t, Y); + } + for (var i=0; i < Tlen/4; i++) + tag[i] = Y[i]; +}; + + +/* Provides authenticated encryption using CBCMAC and CTR-mode encryption on plaintext. +adata is MACed but not encrypted. +plaintext, adata, and tag are arrays of bytes +Tlen is the number of bytes in the tag +ciphertext will be an array of bytes. */ +cipherCCM.prototype.encrypt = function(plaintext, ciphertext, adata, tag) { + var Tlen = this._cipher._Tlen; + this.CBCMAC(adata, plaintext, tag, Tlen, true); + var ctr = [], m = Math.ceil(plaintext.length/16); + this._generateCtrBlocks(m, ctr); + var cblocks = [], S=[], t = new Array(4); + for (var i=0; i <= m; i++) { + S[i] = new Array(16); + aes.bytesToWords(ctr[i], cblocks); + this._cipher.encryptBlock(cblocks, t); + aes.wordsToBytes(t, S[i]); + } + var Sind = 1, SIind = 0; + for (var i=0; i < plaintext.length; i++) { + ciphertext[i] = plaintext[i] ^ S[Sind][SIind]; + SIind++; + if (SIind == 16) { + Sind++; + SIind = 0; + } + } + var tbytes = []; + aes.wordsToBytes(tag, tbytes); + var cstart = plaintext.length; + for (var i=0; i < Tlen; i++) + ciphertext[cstart+i] = tbytes[i] ^ S[0][i]; +}; + + +/* Decrypt and verify the MAC on ciphertext and adata. The integrity of adata is verified, but isn't decrypted. +ciphertext, adata are arrays of bytes +plaintext will be an array of bytes +Returns true if tag is valid, false otherwise. +*/ +cipherCCM.prototype.decrypt = function(ciphertext, plaintext, adata) { + var Tlen = this._cipher._Tlen; + if (ciphertext.length <= Tlen) return false; + var ctr = [], tag = new Array(Tlen), m = Math.ceil(ciphertext.length/16); + this._generateCtrBlocks(m, ctr); + var S = [], t = new Array(4), cblocks=[]; + + for (var i=0; i <= m; i++) { + S[i] = new Array(16); + aes.bytesToWords(ctr[i], cblocks); + this._cipher.encryptBlock(cblocks, t); + aes.wordsToBytes(t, S[i]); + } + + var Sind = 1, SIind = 0; + for (var i=0; i < (ciphertext.length-Tlen); i++) { + plaintext[i] = ciphertext[i] ^ S[Sind][SIind]; + SIind++; + if (SIind == 16) { + SIind = 0; + Sind++; + } + } + + for (var i=0; i < Tlen; i++) + tag[i] = ciphertext[ciphertext.length-Tlen+i] ^ S[0][i]; + + // verify integrity + var validTag = [], vtbytes = []; + this.CBCMAC(adata, plaintext, validTag, Tlen, true); + aes.wordsToBytes(validTag, vtbytes); + for (var i=0; i < Tlen; i++) { + if (vtbytes[i] != tag[i]) + return false; + } + return true; + +}; + +// Generate subkeys according to the CCM specification. */ +cipherCCM.prototype._generateSubkeys = function(k1,k2) { + var t = [0x00000000,0x00000000,0x00000000,0x00000000], t2 = new Array(4); + this._cipher.encryptBlock(t, t2); + for (var i=0; i < 3; i++) + k1[i] = t2[i]<<1 | t2[i+1]>>>31; + k1[3] = t2[3]<<1; + if (t2[0]>>>31 != 0) + k1[3] = k1[3] ^ 135; + for (var i=0; i < 3; i++) + k2[i] = k1[i]<<1 | k1[i+1]>>>31; + k2[3] = k1[3]<<1; + if (k1[0]>>>31 != 0) + k2[3] = k2[3] ^ 135; +}; + + +/* CMAC used for integrity only (no encryption). */ +cipherCCM.prototype.CMAC = function(adata, plaintext, tag, Tlen, formatInput) { + var B = [], t = new Array(4); // will be an array of arrays of 16 bytes + if (formatInput) + this._formatInput(adata,plaintext,Tlen,B); + else { + var Sind = 0, SIind = 0, aind = 0, alen = adata.length; + B[0] = []; + while (aind < alen) { + B[Sind][SIind] = adata[aind]; + SIind++; + if (SIind == 16) { + SIind = 0; + Sind++; + if (aind != alen-1) B[Sind] = []; + } + aind++; + } + } + var k1 = new Array(4), k2 = new Array(4); + this._generateSubkeys(k1,k2); + var last = B.length-1, kbytes = []; + if (alen % 16 == 0) { + aes.wordsToBytes(k1, kbytes); + } else { + aes.wordsToBytes(k2, kbytes); + B[last][B[last].length] = 1<<7; + while (B[last].length % 16 != 0) + B[last][B[last].length] = 0x00; + } + for (var i=0; i < 16; i++) B[last][i] = B[last][i] ^ kbytes[i]; + var C = [0x00000000,0x00000000,0x00000000,0x00000000], Cprev = new Array(4), words = new Array(4); + for (var i=0; i < B.length; i++) { + aes.bytesToWords(B[i], words); + for (var j=0; j < 4; j++) { + Cprev[j] = C[j]; + t[j] = C[j] ^ words[j]; + } + this._cipher.encryptBlock(t, C); + } + var cbytes=[]; + aes.wordsToBytes(C, cbytes); + for (var i=0; i < Tlen; i++) + tag[i] = cbytes[i]; + +}; + + + +/* jsCrypto +OCB mode + +Emily Stark (estark@stanford.edu) +Mike Hamburg (mhamburg@stanford.edu) +Dan Boneh (dabo@cs.stanford.edu) + +OCB mode for authenticated encryption of multiple 16-byte blocks. Uses AES as core cipher. + +Public domain, 2009. +*/ + +/* Constructor takes an aes object as the core cipher. */ +function cipherOCB(cipher) { + this._cipher = cipher; +} + + +/* Provides integrity only, no encryption. +header is an array of bytes, tag will be an array of 4 32-bit words */ +cipherOCB.prototype.PMAC = function(header, tag) { + var carry, t = new Array(4), t2 = new Array(4), Checksum = [0x00000000,0x00000000,0x00000000,0x00000000]; + var Offset = new Array(4); + this._cipher.encryptBlock(Checksum, Offset); + this._times2(t, Offset); + for (var i=0; i < 4; i++) Offset[i] = t[i] ^ Offset[i]; + this._times2(t, Offset); + for (var i=0; i < 4; i++) Offset[i] = t[i] ^ Offset[i]; + + // accumulate all but the last block + var num_blocks = Math.floor((header.length-1)/16); + for (var i=0; i < num_blocks; i++) { + this._times2(Offset,Offset); + var bstart = i*16; // start-of-block index + for (var j=0; j < 4; j++) + t[j] = Offset[j] ^ ((header[bstart+(j<<2)+3]) | (header[bstart+(j<<2)+2] << 8) | (header[bstart+(j<<2)+1] << 16) | (header[bstart+(j<<2)] << 24)); + this._cipher.encryptBlock(t, t2); + for (var j=0; j < 4; j++) Checksum[j] = Checksum[j] ^ t2[j]; + } + + // accumulate the last block + this._times2(Offset,Offset); + + if (header.length%16 == 0) { + var bstart = header.length-16; + for (var j=0; j < 4; j++) + Checksum[j] = Checksum[j] ^ ((header[bstart+(j<<2)+3]) | (header[bstart+(j<<2)+2] << 8) | (header[bstart+(j<<2)+1] << 16) | (header[bstart+(j<<2)] << 24)); + this._times2(t, Offset); + for (var i=0; i < 4; i++) Offset[i] = Offset[i] ^ t[i]; + } else { + var block_bytes = [], block = new Array(4), len = header.length, ind=0; + for (var i=(header.length-(header.length%16)); i < len; i++) { + block_bytes[ind] = header[i]; + ind++; + } + block_bytes[ind] = 0x80; + ind++; + while (block_bytes.length%16 != 0) { + block_bytes[ind] = 0x00; + ind++; + } + aes.bytesToWords(block_bytes,block); + for (var j=0; j < 4; j++) { + var bstart = 4*j; + Checksum[j] = Checksum[j] ^ ((block_bytes[bstart++]<<24) | (block_bytes[bstart++]<<16) | (block_bytes[bstart++]<<8) | (block_bytes[bstart++])); + } + this._times2(t, Offset); + for (var i=0; i < 4; i++) Offset[i] = Offset[i] ^ t[i]; + this._times2(t, Offset); + for (var i=0; i < 4; i++) Offset[i] = Offset[i] ^ t[i]; + } + + // compute result + for (var i=0; i < 4; i++) t[i] = Offset[i] ^ Checksum[i]; + this._cipher.encryptBlock(t, tag); +}; + + +/* Encrypts and MACs plaintext, only MACS header. +plaintext, ciphertext and header are arrays of bytes. tag will be an array of 4 32-bit words. */ +cipherOCB.prototype.encrypt = function(plaintext, ciphertext, header, tag) { + var Checksum = [0x00000000,0x00000000,0x00000000,0x00000000]; + var t = [0x00000000,0x00000000,0x00000000,0x00000000], t2 = new Array(4); + var Offset = new Array(4); + this._cipher.encryptBlock(this._cipher._iv, Offset); + var cbytes = []; + + // encrypt and accumulate all but last block + var num_blocks = Math.floor((plaintext.length-1)/16), bstart=0, block = new Array(4); + for (var i=0; i < num_blocks; i++) { + this._times2(Offset,Offset); + bstart = 16*i; + for (var j=0; j < 4; j++) + block[j] = ((plaintext[bstart+(j<<2)+3]) | (plaintext[bstart+(j<<2)+2] << 8) | (plaintext[bstart+(j<<2)+1] << 16) | (plaintext[bstart+(j<<2)] << 24)); + for (var j=0; j < 4; j++) + t[j] = Offset[j] ^ block[j]; + this._cipher.encryptBlock(t,t2); + for (var j=0; j < 4; j++) t[j] = Offset[j] ^ t2[j]; + aes.wordsToBytes(t, cbytes); + for (var j=0; j < 16; j++) ciphertext[bstart+j] = cbytes[j]; + for (var j=0; j < 4; j++) Checksum[j] = Checksum[j] ^ block[j]; + } + + // encrypt and accumulate last block + var num_bytes = plaintext.length%16; + if ((num_bytes == 0) && (plaintext.length > 0)) num_bytes=16; + this._times2(Offset,Offset); + t = [0x00000000,0x00000000,0x00000000,0x00000000]; + t[3] = num_bytes*8; + for (var i=0; i < 4; i++) t[i] = Offset[i] ^ t[i]; + var Pad = new Array(4); + this._cipher.encryptBlock(t, Pad); + var pad_bytes = new Array(16); + aes.wordsToBytes(Pad, pad_bytes); + var tempbytes = []; + bstart = plaintext.length-num_bytes; + for (var i=0; i < num_bytes; i++) { + ciphertext[bstart+i] = plaintext[bstart+i] ^ pad_bytes[i]; + tempbytes[tempbytes.length] = plaintext[bstart+i]; + } + for (var i=num_bytes; i < 16; i++) + tempbytes[tempbytes.length] = pad_bytes[i]; + aes.bytesToWords(tempbytes, t); + for (var i=0; i < 4; i++) Checksum[i] = Checksum[i] ^ t[i]; + + // compute authentication tag + this._times2(t,Offset); + for (var i=0; i < 4; i++) { + Offset[i] = t[i] ^ Offset[i]; + t[i] = Checksum[i] ^ Offset[i]; + } + this._cipher.encryptBlock(t,t2); + if (header.length > 0) { + this.PMAC(header, t); + for (var i=0; i < 4; i++) tag[i] = t[i] ^ t2[i]; + } else { + for (var i=0; i < 4; i++) tag[i] = t2[i]; + } +}; + + +/* Decrypts and verifies integrity of ciphertext, only verifies integrity of header. +ciphertext, plaintext, and header are arrays of bytes. tag is an array of 4 32-bit words. +Returns true if tag is valid, false otherwise. */ +cipherOCB.prototype.decrypt = function(ciphertext, plaintext, header, tag) { + var Offset = new Array(4), Checksum = [0x00000000,0x00000000,0x00000000,0x00000000]; + this._cipher.encryptBlock(this._cipher._iv, Offset); + + var t = new Array(4), t2 = new Array(4), block = new Array(4); + + // decrypt and accumulate first m-1 blocks + var num_blocks = Math.floor((ciphertext.length-1)/16); + var bstart = 0, pbytes = new Array(16); + this._cipher.scheduleDecrypt(); + for (var i=0; i < num_blocks; i++) { + this._times2(Offset,Offset); + bstart = i*16; + for (var j=0; j < 4; j++) + t[j] = Offset[j] ^ ((ciphertext[bstart+(j<<2)+3]) | (ciphertext[bstart+(j<<2)+2] << 8) | (ciphertext[bstart+(j<<2)+1] << 16) | (ciphertext[bstart+(j<<2)] << 24)); + this._cipher.decryptBlock(t,t2); + for (var j=0; j < 4; j++) { + block[j] = Offset[j] ^ t2[j]; + Checksum[j] = block[j] ^ Checksum[j]; + } + aes.wordsToBytes(block,pbytes); + for (var j=0; j < 16; j++) + plaintext[bstart+j] = pbytes[j]; + } + + // decrypt and accumulate final block + var Pad = new Array(4), padbytes=[]; + this._cipher.scheduleEncrypt() + this._times2(Offset,Offset); + var num_bytes = ciphertext.length%16; + if ((num_bytes == 0) && (ciphertext.length > 0)) num_bytes=16; + t = [0x00000000,0x00000000,0x00000000,0x00000000]; + t[3] = num_bytes*8; + for (var i=0; i < 4; i++) t[i] = t[i] ^ Offset[i] + this._cipher.encryptBlock(t,Pad); + aes.wordsToBytes(Pad, padbytes); + bstart = ciphertext.length - num_bytes; + for (var i=0; i < num_bytes; i++) { + plaintext[bstart+i] = ciphertext[bstart+i] ^ padbytes[i]; + t[i] = plaintext[bstart+i]; + } + for (var i = num_bytes; i < 16; i++) + t[i] = padbytes[i]; + aes.bytesToWords(t,t2); + for (var i=0; i < 4; i++) Checksum[i] = Checksum[i] ^ t2[i]; + + // compute valid authentication tag + this._times2(t, Offset); + for (var i=0; i < 4; i++) { + Offset[i] = Offset[i] ^ t[i]; + t[i] = Offset[i] ^ Checksum[i]; + } + var validTag = new Array(4); + this._cipher.encryptBlock(t,validTag); + t = new Array(4); + if (header.length > 0) { + this.PMAC(header, t); + for (var i=0; i < 4; i++) validTag[i] = validTag[i] ^ t[i]; + } + // compute results + for (var i=0; i < 4; i++) { + if (aes._hex(tag[i]) != aes._hex(validTag[i])) { + return false; + } + } + return true; +}; + + + +cipherOCB.prototype._times2 = function(dst, src) { + var carry = src[0]>>>31; + for (var i=0; i < 3; i++) + dst[i] = (src[i]<<1) | (src[i+1]>>>31); + dst[3] = (src[3]<<1) ^ (carry * 0x87); +}; + + + + + + + + +/* +jsCrypto + +sha256.js +Mike Hamburg, 2008. Public domain. + */ + + +function SHA256() { + if (!this.k[0]) + this.precompute(); + this.initialize(); +} + +SHA256.prototype = { + /* + init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19], + + 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], + */ + + init:[], k:[], + + precompute: function() { + var p=2,i=0,j; + + function frac(x) { return (x-Math.floor(x)) * 4294967296 | 0 } + + outer: for (;i<64;p++) { + for (j=2;j*j<=p;j++) + if (p % j == 0) + continue outer; + + if (i<8) this.init[i] = frac(Math.pow(p,1/2)); + this.k[i] = frac(Math.pow(p,1/3)); + i++; + } + }, + + initialize:function() { + this.h = this.init.slice(0); + this.word_buffer = []; + this.bit_buffer = 0; + this.bits_buffered = 0; + this.length = 0; + this.length_upper = 0; + }, + + // one cycle of SHA256 + block:function(words) { + var w=words.slice(0),i,h=this.h,tmp,k=this.k; + + var h0=h[0],h1=h[1],h2=h[2],h3=h[3],h4=h[4],h5=h[5],h6=h[6],h7=h[7]; + + for (i=0;i<64;i++) { + if (i<16) { + tmp=w[i]; + } else { + var a=w[(i+1)&15], b=w[(i+14)&15]; + tmp=w[i&15]=((a>>>7^a>>>18^a>>>3^a<<25^a<<14) + (b>>>17^b>>>19^b>>>10^b<<15^b<<13) + w[i&15] + w[(i+9)&15]) | 0; + } + + tmp += h7 + (h4>>>6^h4>>>11^h4>>>25^h4<<26^h4<<21^h4<<7) + (h6 ^ h4&(h5^h6)) + k[i]; + + h7=h6; h6=h5; h5=h4; + h4 = h3 + tmp | 0; + + h3=h2; h2=h1; h1=h0; + + h0 = (tmp + ((h1&h2)^(h3&(h1^h2))) + (h1>>>2^h1>>>13^h1>>>22^h1<<30^h1<<19^h1<<10)) | 0; + } + + h[0]+=h0; h[1]+=h1; h[2]+=h2; h[3]+=h3; + h[4]+=h4; h[5]+=h5; h[6]+=h6; h[7]+=h7; + }, + + update_word_big_endian:function(word) { + var bb; + if ((bb = this.bits_buffered)) { + this.word_buffer.push(word>>>(32-bb) ^ this.bit_buffer); + this.bit_buffer = word << bb; + } else { + this.word_buffer.push(word); + } + this.length += 32; + if (this.length == 0) this.length_upper ++; // mmhm.. + if (this.word_buffer.length == 16) { + this.block(this.word_buffer); + this.word_buffer = []; + } + }, + + update_word_little_endian:function(word) { + word = word >>> 16 ^ word << 16; + word = ((word>>>8) & 0xFF00FF) ^ ((word<<8) & 0xFF00FF00); + this.update_word_big_endian(word); + }, + + update_words_big_endian: function(words) { + for (var i=0; i<words.length; i++) this.update_word_big_endian(words[i]); + }, + + update_words_little_endian: function(words) { + for (var i=0; i<words.length; i++) this.update_word_little_endian(words[i]); + }, + + update_byte:function(byte) { + this.bit_buffer ^= (byte & 0xff) << (24 - (this.bits_buffered)); + this.bits_buffered += 8; + if (this.bits_buffered == 32) { + this.bits_buffered = 0; + this.update_word_big_endian(this.bit_buffer); + this.bit_buffer = 0; + } + }, + + update_string:function(string) { + throw "not yet implemented"; + }, + + finalize:function() { + var i, wb = this.word_buffer; + + wb.push(this.bit_buffer ^ (0x1 << (31 - this.bits_buffered))); + for (i = (wb.length + 2) & 15; i<16; i++) { + wb.push(0); + } + + wb.push(this.length_upper); + wb.push(this.length + this.bits_buffered); + + this.block(wb.slice(0,16)); + if (wb.length > 16) { + this.block(wb.slice(0,16)); + } + + var h = this.h; + this.initialize(); + return h; + } +} + +SHA256.hash_words_big_endian = function(words) { + var s = new SHA256(); + for (var i=0; i<=words.length-16; i+=16) { + s.block(words.slice(i,i+16)); + } + s.length = i << 5; // so don't pass this function more than 128M words + if (i<words.length) + s.update_words_little_endian(words.slice(i)); + return s.finalize(); +} + +SHA256.hash_words_little_endian = function(words) { + var w = words.slice(0); + for (var i=0; i<w.length; i++) { + w[i] = w[i] >>> 16 ^ w[i] << 16; + w[i] = ((w[i]>>>8) & 0xFF00FF) ^ ((w[i]<<8) & 0xFF00FF00); + } + return SHA256.hash_words_big_endian(w); +} + + + + + + + +/* + + jsCrypto + + * Random.js -- cryptographic random number generator + * Mike Hamburg, 2008. Public domain. + * + * This generator uses a modified version of Fortuna. Fortuna has + * excellent resilience to compromise, relies on a state file, and is + * intended to run for a long time. As such, it does not need an + * entropy estimator. Unfortunately, Fortuna's startup in low-entropy + * conditions leaves much to be desired. + * + * This generator features the following modifications. First, the + * generator does not create the n-th entropy pool until it exhausts + * the n-1-st. This means that entropy doesn't get "stuck" in pools + * 10-31, which will never be used on a typical webpage. It also + * means that the entropy will all go into a single pool until the + * generator is seeded. + * + * Second, there is a very crude entropy estimator. The primary goal + * of this estimator is to prevent the generator from being used in + * low-entropy situations. Corresponding to this entropy estimator, + * there is a "paranoia control". This controls how many bits of + * estimated entropy must be present before the generator is used. + * The generator cannot have more than 256 bits of actual entropy in + * the main pool; rather, the paranoia control is designed to deal + * with the fact that the entropy estimator is probably horrible. + * + * Third, the "statefile" is optional and stored in a cookie. As + * such, it is not protected from multiple simultaneous usage, and so + * is treated conservatively. + */ + +Random = { + /* public */ +NOT_READY: 0, +READY: 1, +REQUIRES_RESEED: 2, + + /* generate one random word */ +random_word: function(paranoia) { + return this.random_words(1, paranoia)[0]; +}, + + /* generate nwords random words, and return them in an array */ +random_words: function(nwords, paranoia) { + var out = [], i, readiness = this.is_ready(paranoia); + + if (readiness == this.NOT_READY) + throw("Random: generator isn't seeded!"); + + else if (readiness && this.REQUIRES_RESEED) + this._reseed_from_pools(!(readiness & this.READY)); + + for (i=0; i<nwords; i+= 4) { + if ((i+1) % this._max_words_per_burst == 0) + this._gate(); + + var g = this._gen_4_words(); + out.push(g[0],g[1],g[2],g[3]); + } + this._gate(); + + return out.slice(0,nwords); +}, + +set_default_paranoia: function(paranoia) { + this._default_paranoia = paranoia; +}, + + /* Add entropy to the pools. Pass data as an array, number or + * string. Pass estimated_entropy in bits. Pass the source as a + * number or string. + */ +add_entropy: function(data, estimated_entropy, source) { + source = source || "user"; + + var id = this._collector_ids[source] || + (this._collector_ids[source] = this._collector_id_next ++); + + var i, ty = 0; + + var t = new Date().valueOf(); + + var robin = this._robins[source]; + if (robin == undefined) robin = this._robins[source] = 0; + this._robins[source] = ( this._robins[source] + 1 ) % this._pools.length; + + switch(typeof(data)) { + + case "number": + data=[data]; + ty=1; + break; + + case "object": + if (!estimated_entropy) { + /* horrible entropy estimator */ + estimated_entropy = 0; + for (i=0; i<data.length; i++) { + var x = data[i]; + while (x>0) { + estimated_entropy++; + x = x >>> 1; + } + } + } + this._pools[robin].update_words_big_endian([id,this._event_id++,ty||2,estimated_entropy,t,data.length].concat(data)); + break; + + case "string": + if (!estimated_entropy) { + /* English text has just over 1 bit per character of entropy. + * But this might be HTML or something, and have far less + * entropy than English... Oh well, let's just say one bit. + */ + estimated_entropy = data.length; + } + this._pools[robin].update_words_big_endian([id,this._event_id++,3,estimated_entropy,t,data.length]) + this._pools[robin].update_string(data); + break; + + default: + throw "add_entropy: must give an array, number or string" + } + + var old_ready = this.is_ready(); + + /* record the new strength */ + this._pool_entropy[robin] += estimated_entropy; + this._pool_strength += estimated_entropy; + + /* fire off events */ + if (!old_ready && this.is_ready()) + this._fire_event("seeded", Math.max(this._strength, this._pool_strength)); + + if (!old_ready) + this._fire_event("progress", this.get_progress()); +}, + + /* is the generator ready? */ +is_ready: function(paranoia) { + var entropy_required = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._default_paranoia ]; + + if (this._strength >= entropy_required) { + return (this._pool_entropy[0] > this._BITS_PER_RESEED && new Date.valueOf() > this._next_reseed) ? + this.REQUIRES_RESEED | this.READY : + this.READY; + } else { + return (this._pool_strength > entropy_required) ? + this.REQUIRES_RESEED | this.NOT_READY : + this.NOT_READY; + } +}, + + /* how close to ready is it? */ +get_progress: function(paranoia) { + var entropy_required = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._default_paranoia ]; + + if (this._strength >= entropy_required) { + return 1.0; + } else { + return (this._pool_strength > entropy_required) ? + 1.0 : + this._pool_strength / entropy_required; + } +}, + + /* start the built-in entropy collectors */ +start_collectors: function() { + if (this._collectors_started) return; + + if (window.addEventListener) { + window.addEventListener("load", this._load_time_collector, false); + window.addEventListener("mousemove", this._mouse_collector, false); + } else if (document.attachEvent) { + document.attachEvent("onload", this._load_time_collector); + document.attachEvent("onmousemove", this._mouse_collector); + } + else throw("can't attach event"); + + this._collectors_started = true; +}, + + /* stop the built-in entropy collectors */ +stop_collectors: function() { + if (!this._collectors_started) return; + + if (window.removeEventListener) { + window.removeEventListener("load", this._load_time_collector); + window.removeEventListener("mousemove", this._mouse_collector); + } else if (window.detachEvent) { + window.detachEvent("onload", this._load_time_collector); + window.detachEvent("onmousemove", this._mouse_collector) + } + this._collectors_started = false; +}, + +use_cookie: function(all_cookies) { + throw "TODO: implement use_cookie"; +}, + + /* add an event listener for progress or seeded-ness */ +addEventListener: function(name, callback) { + this._callbacks[name][this._callback_i++] = callback; +}, + + /* remove an event listener for progress or seeded-ness */ +removeEventListener: function(name, cb) { + var i, j, cbs=this._callbacks[name], js_temp=[]; + + /* I'm not sure if this is necessary; in C++, iterating over a + * collection and modifying it at the same time is a no-no. + */ + + for (j in cbs) + if (cbs.hasOwnProperty[j] && cbs[j] === cb) + js_temp.push(j); + + for (i=0; i<js_temp.length; i++) { + j = js[i]; + delete cbs[j]; + } +}, + + /* private */ + _pools : [new SHA256()], + _pool_entropy : [0], + _reseed_count : 0, + _robins : {}, + _event_id : 0, + + _collector_ids : {}, + _collector_id_next : 0, + + _strength : 0, + _pool_strength : 0, + _next_reseed : 0, + _key : [0,0,0,0,0,0,0,0], + _counter : [0,0,0,0], + _cipher : undefined, + _default_paranoia : 6, + + /* event listener stuff */ + _collectors_started : false, + _callbacks : {progress: {}, seeded: {}}, + _callback_i : 0, + + /* constants */ + _MAX_WORDS_PER_BURST : 65536, + _PARANOIA_LEVELS : [0,48,64,96,128,192,256,384,512,768,1024], + _MILLISECONDS_PER_RESEED : 100, + _BITS_PER_RESEED : 80, + + /* generate 4 random words, no reseed, no gate */ +_gen_4_words: function() { + var words = []; + for (var i=0; i<3; i++) if (++this._counter[i]) break; + this._cipher.encryptBlock(this._counter, words); + return words; +}, + + /* rekey the AES instance with itself after a request, or every _MAX_WORDS_PER_BURST words */ +_gate: function() { + this._key = this._gen_4_words().concat(this._gen_4_words()); + this._cipher = new aes(this._key); +}, + + /* reseed the generator with the given words */ +_reseed: function(seedWords) { + this._key = SHA256.hash_words_big_endian(this._key.concat(seedWords)); + this._cipher = new aes(this._key); + for (var i=0; i<3; i++) if (++this._counter[i]) break; +}, + + /* reseed the data from the entropy pools */ +_reseed_from_pools: function(full) { + var reseed_data = [], strength = 0; + + this._next_reseed = new Date().valueOf() + this._MILLISECONDS_PER_RESEED; + + for (i=0; i<this._pools.length; i++) { + reseed_data = reseed_data.concat(this._pools[i].finalize()); + strength += this._pool_entropy[i]; + this._pool_entropy[i] = 0; + + if (!full && (this._reseed_count & (1<<i))) break; + } + + /* if we used the last pool, push a new one onto the stack */ + if (this._reseed_count >= 1 << this._pools.length) { + this._pools.push(new SHA256()); + this._pool_entropy.push(0); + } + + /* how strong was this reseed? */ + this._pool_strength -= strength; + if (strength > this._strength) this._strength = strength; + + this._reseed_count ++; + this._reseed(reseed_data); +}, + +_mouse_collector: function(ev) { + var x = ev.x || ev.clientX || ev.offsetX; + var y = ev.y || ev.clientY || ev.offsetY; + Random.add_entropy([x,y], 2, "mouse"); +}, + +_load_time_collector: function(ev) { + var d = new Date(); + Random.add_entropy(d, 2, "loadtime"); +}, + +_fire_event: function(name, arg) { + var j, cbs=Random._callbacks[name], cbs_temp=[]; + + /* I'm not sure if this is necessary; in C++, iterating over a + * collection and modifying it at the same time is a no-no. + */ + + for (j in cbs) { + if (cbs.hasOwnProperty(j)) { + cbs_temp.push(cbs[j]); + } + } + + for (j=0; j<cbs_temp.length; j++) { + cbs_temp[j](arg); + } +} +}; + |