diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/bitArray.js | 16 | ||||
-rw-r--r-- | core/bn.js | 334 | ||||
-rw-r--r-- | core/cbc.js | 16 | ||||
-rw-r--r-- | core/ccm.js | 88 | ||||
-rw-r--r-- | core/ccmArrayBuffer.js | 247 | ||||
-rw-r--r-- | core/codecArrayBuffer.js | 113 | ||||
-rw-r--r-- | core/codecBase32.js | 88 | ||||
-rw-r--r-- | core/codecBase64.js | 8 | ||||
-rw-r--r-- | core/codecHex.js | 2 | ||||
-rw-r--r-- | core/convenience.js | 163 | ||||
-rw-r--r-- | core/ecc.js | 310 | ||||
-rw-r--r-- | core/gcm.js | 183 | ||||
-rw-r--r-- | core/hmac.js | 29 | ||||
-rw-r--r-- | core/ocb2progressive.js | 137 | ||||
-rw-r--r-- | core/pbkdf2.js | 6 | ||||
-rw-r--r-- | core/random.js | 266 | ||||
-rw-r--r-- | core/ripemd160.js | 207 | ||||
-rw-r--r-- | core/scrypt.js | 146 | ||||
-rw-r--r-- | core/sha1.js | 3 | ||||
-rw-r--r-- | core/sha256.js | 2 | ||||
-rw-r--r-- | core/sha512.js | 348 | ||||
-rw-r--r-- | core/sjcl.js | 17 | ||||
-rw-r--r-- | core/srp.js | 133 |
23 files changed, 2536 insertions, 326 deletions
diff --git a/core/bitArray.js b/core/bitArray.js index a6d0617..54112ac 100644 --- a/core/bitArray.js +++ b/core/bitArray.js @@ -74,7 +74,7 @@ sjcl.bitArray = { return a1.concat(a2); } - var out, i, last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last); + var last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last); if (shift === 32) { return a1.concat(a2); } else { @@ -183,5 +183,19 @@ sjcl.bitArray = { */ _xor4: function(x,y) { return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; + }, + + /** byteswap a word array inplace. + * (does not handle partial words) + * @param {sjcl.bitArray} a word array + * @return {sjcl.bitArray} byteswapped array + */ + byteswapM: function(a) { + var i, v, m = 0xff00; + for (i = 0; i < a.length; ++i) { + v = a[i]; + a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24); + } + return a; } }; @@ -1,4 +1,5 @@ /** + * @constructor * Constructs a new bignum from another bignum, a number or a hex string. */ sjcl.bn = function(it) { @@ -9,7 +10,7 @@ sjcl.bn.prototype = { radix: 24, maxMul: 8, _class: sjcl.bn, - + copy: function() { return new this._class(this); }, @@ -18,17 +19,17 @@ sjcl.bn.prototype = { * Initializes this with it, either as a bn, a number, or a hex string. */ initWith: function(it) { - var i=0, k, n, l; + var i=0, k; switch(typeof it) { case "object": this.limbs = it.limbs.slice(0); break; - + case "number": this.limbs = [it]; this.normalize(); break; - + case "string": it = it.replace(/^0x/, ''); this.limbs = []; @@ -59,14 +60,14 @@ sjcl.bn.prototype = { } return (difference === 0); }, - + /** * Get the i'th limb of this, zero if i is too large. */ getLimb: function(i) { return (i >= this.limbs.length) ? 0 : this.limbs[i]; }, - + /** * Constant time comparison function. * Returns 1 if this >= that, or zero otherwise. @@ -83,7 +84,7 @@ sjcl.bn.prototype = { } return (greater | ~less) >>> 31; }, - + /** * Convert to a hex string. */ @@ -99,7 +100,7 @@ sjcl.bn.prototype = { } return "0x"+out; }, - + /** this += that. Does not normalize. */ addM: function(that) { if (typeof(that) !== "object") { that = new this._class(that); } @@ -112,7 +113,7 @@ sjcl.bn.prototype = { } return this; }, - + /** this *= 2. Requires normalized; ends up normalized. */ doubleM: function() { var i, carry=0, tmp, r=this.radix, m=this.radixMask, l=this.limbs; @@ -127,7 +128,7 @@ sjcl.bn.prototype = { } return this; }, - + /** this /= 2, rounded down. Requires normalized; ends up normalized. */ halveM: function() { var i, carry=0, tmp, r=this.radix, l=this.limbs; @@ -154,14 +155,21 @@ sjcl.bn.prototype = { } return this; }, - + mod: function(that) { + var neg = !this.greaterEquals(new sjcl.bn(0)); + that = new sjcl.bn(that).normalize(); // copy before we begin var out = new sjcl.bn(this).normalize(), ci=0; - + + if (neg) out = (new sjcl.bn(0)).subM(out).normalize(); + for (; out.greaterEquals(that); ci++) { that.doubleM(); } + + if (neg) out = that.sub(out).normalize(); + for (; ci > 0; ci--) { that.halveM(); if (out.greaterEquals(that)) { @@ -170,15 +178,15 @@ sjcl.bn.prototype = { } return out.trim(); }, - + /** return inverse mod prime p. p must be odd. Binary extended Euclidean algorithm mod p. */ inverseMod: function(p) { var a = new sjcl.bn(1), b = new sjcl.bn(0), x = new sjcl.bn(this), y = new sjcl.bn(p), tmp, i, nz=1; - + if (!(p.limbs[0] & 1)) { throw (new sjcl.exception.invalid("inverseMod: p must be odd")); } - + // invariant: y is odd do { if (x.limbs[0] & 1) { @@ -189,13 +197,13 @@ sjcl.bn.prototype = { } x.subM(y); x.normalize(); - + if (!a.greaterEquals(b)) { a.addM(p); } a.subM(b); } - + // cut everything in half x.halveM(); if (a.limbs[0] & 1) { @@ -203,20 +211,20 @@ sjcl.bn.prototype = { } a.normalize(); a.halveM(); - + // check for termination: x ?= 0 for (i=nz=0; i<x.limbs.length; i++) { nz |= x.limbs[i]; } } while(nz); - + if (!y.equals(1)) { throw (new sjcl.exception.invalid("inverseMod: p and x must be relatively prime")); } - + return b; }, - + /** this + that. Does not normalize. */ add: function(that) { return this.copy().addM(that); @@ -226,7 +234,7 @@ sjcl.bn.prototype = { sub: function(that) { return this.copy().subM(that); }, - + /** this * that. Normalizes and reduces. */ mul: function(that) { if (typeof(that) === "number") { that = new this._class(that); } @@ -240,7 +248,7 @@ sjcl.bn.prototype = { for (j=0; j<bl; j++) { c[i+j] += ai * b[j]; } - + if (!--ii) { ii = this.maxMul; out.cnormalize(); @@ -256,22 +264,18 @@ sjcl.bn.prototype = { /** this ^ n. Uses square-and-multiply. Normalizes and reduces. */ power: function(l) { - if (typeof(l) === "number") { - l = [l]; - } else if (l.limbs !== undefined) { - l = l.normalize().limbs; - } + l = new sjcl.bn(l).normalize().trim().limbs; var i, j, out = new this._class(1), pow = this; for (i=0; i<l.length; i++) { for (j=0; j<this.radix; j++) { - if (l[i] & (1<<j)) { - out = out.mul(pow); - } + if (l[i] & (1<<j)) { out = out.mul(pow); } + if (i == (l.length - 1) && l[i]>>(j + 1) == 0) { break; } + pow = pow.square(); } } - + return out; }, @@ -282,14 +286,184 @@ sjcl.bn.prototype = { /** this ^ x mod N */ powermod: function(x, N) { - var result = new sjcl.bn(1), a = new sjcl.bn(this), k = new sjcl.bn(x); - while (true) { - if (k.limbs[0] & 1) { result = result.mulmod(a, N); } - k.halveM(); - if (k.equals(0)) { break; } - a = a.mulmod(a, N); - } - return result.normalize().reduce(); + x = new sjcl.bn(x); + N = new sjcl.bn(N); + + // Jump to montpowermod if possible. + if ((N.limbs[0] & 1) == 1) { + var montOut = this.montpowermod(x, N); + + if (montOut != false) { return montOut; } // else go to slow powermod + } + + var i, j, l = x.normalize().trim().limbs, out = new this._class(1), pow = this; + + for (i=0; i<l.length; i++) { + for (j=0; j<this.radix; j++) { + if (l[i] & (1<<j)) { out = out.mulmod(pow, N); } + if (i == (l.length - 1) && l[i]>>(j + 1) == 0) { break; } + + pow = pow.mulmod(pow, N); + } + } + + return out; + }, + + /** this ^ x mod N with Montomery reduction */ + montpowermod: function(x, N) { + x = new sjcl.bn(x).normalize().trim(); + N = new sjcl.bn(N); + + var i, j, + radix = this.radix, + out = new this._class(1), + pow = this.copy(); + + // Generate R as a cap of N. + var R, s, wind, bitsize = x.bitLength(); + + R = new sjcl.bn({ + limbs: N.copy().normalize().trim().limbs.map(function() { return 0; }) + }); + + for (s = this.radix; s > 0; s--) { + if (((N.limbs[N.limbs.length - 1] >> s) & 1) == 1) { + R.limbs[R.limbs.length - 1] = 1 << s; + break; + } + } + + // Calculate window size as a function of the exponent's size. + if (bitsize == 0) { + return this; + } else if (bitsize < 18) { + wind = 1; + } else if (bitsize < 48) { + wind = 3; + } else if (bitsize < 144) { + wind = 4; + } else if (bitsize < 768) { + wind = 5; + } else { + wind = 6; + } + + // Find R' and N' such that R * R' - N * N' = 1. + var RR = R.copy(), NN = N.copy(), RP = new sjcl.bn(1), NP = new sjcl.bn(0), RT = R.copy(); + + while (RT.greaterEquals(1)) { + RT.halveM(); + + if ((RP.limbs[0] & 1) == 0) { + RP.halveM(); + NP.halveM(); + } else { + RP.addM(NN); + RP.halveM(); + + NP.halveM(); + NP.addM(RR); + } + } + + RP = RP.normalize(); + NP = NP.normalize(); + + RR.doubleM() + var R2 = RR.mulmod(RR, N); + + // Check whether the invariant holds. + // If it doesn't, we can't use Montgomery reduction on this modulus. + if (!RR.mul(RP).sub(N.mul(NP)).equals(1)) { + return false; + } + + var montIn = function(c) { return montMul(c, R2) }, + montMul = function(a, b) { + // Standard Montgomery reduction + var k, carry, ab, right, abBar, mask = (1 << (s + 1)) - 1; + + ab = a.mul(b); + + right = ab.mul(NP); + right.limbs = right.limbs.slice(0, R.limbs.length); + + if (right.limbs.length == R.limbs.length) { + right.limbs[R.limbs.length - 1] &= mask; + } + + right = right.mul(N); + + abBar = ab.add(right).normalize().trim(); + abBar.limbs = abBar.limbs.slice(R.limbs.length - 1); + + // Division. Equivelent to calling *.halveM() s times. + for (k=0; k < abBar.limbs.length; k++) { + if (k > 0) { + abBar.limbs[k - 1] |= (abBar.limbs[k] & mask) << (radix - s - 1) + } + + abBar.limbs[k] = abBar.limbs[k] >> (s + 1) + } + + if (abBar.greaterEquals(N)) { + abBar.subM(N) + } + + return abBar; + }, + montOut = function(c) { return montMul(c, 1); }; + + pow = montIn(pow); + out = montIn(out); + + // Sliding-Window Exponentiation (HAC 14.85) + var h, precomp = {}, cap = (1 << (wind - 1)) - 1; + + precomp[1] = pow.copy(); + precomp[2] = montMul(pow, pow); + + for (h=1; h<=cap; h++) { + precomp[(2 * h) + 1] = montMul(precomp[(2 * h) - 1], precomp[2]); + } + + var getBit = function(exp, i) { // Gets ith bit of exp. + var off = i % exp.radix; + + return (exp.limbs[Math.floor(i / exp.radix)] & (1 << off)) >> off; + } + + for (i = x.bitLength() - 1; i >= 0; ) { + if (getBit(x, i) == 0) { + // If the next bit is zero: + // Square, move forward one bit. + out = montMul(out, out) + i = i - 1; + } else { + // If the next bit is one: + // Find the longest sequence of bits after this one, less than `wind` + // bits long, that ends with a 1. Convert the sequence into an + // integer and look up the pre-computed value to add. + var l = i - wind + 1; + + while (getBit(x, l) == 0) { + l++; + } + + var indx = 0; + for (j = l; j <= i; j++) { + indx += getBit(x, j) << (j - l) + out = montMul(out, out); + } + + out = montMul(out, precomp[indx]) + + i = l - 1; + } + } + + return montOut(out); }, trim: function() { @@ -300,7 +474,7 @@ sjcl.bn.prototype = { l.push(p); return this; }, - + /** Reduce mod a modulus. Stubbed for subclassing. */ reduce: function() { return this; @@ -310,7 +484,7 @@ sjcl.bn.prototype = { fullReduce: function() { return this.normalize(); }, - + /** Propagate carries. */ normalize: function() { var carry=0, i, pv = this.placeVal, ipv = this.ipv, l, m, limbs = this.limbs, ll = limbs.length, mask = this.radixMask; @@ -320,7 +494,10 @@ sjcl.bn.prototype = { carry = (l-m)*ipv; } if (carry === -1) { - limbs[i-1] -= this.placeVal; + limbs[i-1] -= pv; + } + while (limbs.length > 0 && limbs[limbs.length-1] === 0) { + limbs.pop(); } return this; }, @@ -336,35 +513,39 @@ sjcl.bn.prototype = { limbs[i] += carry; return this; }, - + /** Serialize to a bit array */ toBits: function(len) { this.fullReduce(); - len = len || this.exponent || this.limbs.length * this.radix; + len = len || this.exponent || this.bitLength(); var i = Math.floor((len-1)/24), w=sjcl.bitArray, e = (len + 7 & -8) % this.radix || this.radix, out = [w.partial(e, this.getLimb(i))]; for (i--; i >= 0; i--) { - out = w.concat(out, [w.partial(this.radix, this.getLimb(i))]); + out = w.concat(out, [w.partial(Math.min(this.radix,len), this.getLimb(i))]); + len -= this.radix; } return out; }, - + /** Return the length in bits, rounded up to the nearest byte. */ bitLength: function() { this.fullReduce(); var out = this.radix * (this.limbs.length - 1), b = this.limbs[this.limbs.length - 1]; - for (; b; b >>= 1) { + for (; b; b >>>= 1) { out ++; } return out+7 & -8; } }; +/** @memberOf sjcl.bn +* @this { sjcl.bn } +*/ sjcl.bn.fromBits = function(bits) { var Class = this, out = new Class(), words=[], w=sjcl.bitArray, t = this.prototype, l = Math.min(this.bitLength || 0x100000000, w.bitLength(bits)), e = l % t.radix || t.radix; - + words[0] = w.extract(bits, 0, e); for (; e < l; e += t.radix) { words.unshift(w.extract(bits, e, t.radix)); @@ -384,6 +565,9 @@ sjcl.bn.prototype.radixMask = (1 << sjcl.bn.prototype.radix) - 1; * i.e. a prime of the form 2^e + sum(a * 2^b),where the sum is negative and sparse. */ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { + /** @constructor + * @private + */ function p(it) { this.initWith(it); /*if (this.limbs[this.modOffset]) { @@ -401,7 +585,7 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { ppr.fullOffset = []; ppr.fullFactor = []; ppr.modulus = p.modulus = new sjcl.bn(Math.pow(2,exponent)); - + ppr.fullMask = 0|-Math.pow(2, exponent % ppr.radix); for (i=0; i<coeff.length; i++) { @@ -415,9 +599,12 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { ppr._class = p; ppr.modulus.cnormalize(); - /** Approximate reduction mod p. May leave a number which is negative or slightly larger than p. */ + /** Approximate reduction mod p. May leave a number which is negative or slightly larger than p. + * @memberof sjcl.bn + * @this { sjcl.bn } + */ ppr.reduce = function() { - var i, k, l, mo = this.modOffset, limbs = this.limbs, aff, off = this.offset, ol = this.offset.length, fac = this.factor, ll; + var i, k, l, mo = this.modOffset, limbs = this.limbs, off = this.offset, ol = this.offset.length, fac = this.factor, ll; i = this.minOffset; while (limbs.length > mo) { @@ -426,7 +613,7 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { for (k=0; k<ol; k++) { limbs[ll+off[k]] -= fac[k] * l; } - + i--; if (!i) { limbs.push(0); @@ -438,7 +625,10 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { return this; }; - + + /** @memberof sjcl.bn + * @this { sjcl.bn } + */ ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() { var limbs = this.limbs, i = limbs.length - 1, k, l; this.reduce(); @@ -452,11 +642,14 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { } }; - /** mostly constant-time, very expensive full reduction. */ + /** mostly constant-time, very expensive full reduction. + * @memberof sjcl.bn + * @this { sjcl.bn } + */ ppr.fullReduce = function() { var greater, i; // massively above the modulus, may be negative - + this._strongReduce(); // less than twice the modulus, may be negative @@ -464,7 +657,7 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { this.addM(this.modulus); this.normalize(); // probably 2-3x the modulus - + this._strongReduce(); // less than the power of 2. still may be more than // the modulus @@ -473,7 +666,7 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { for (i=this.limbs.length; i<this.modOffset; i++) { this.limbs[i] = 0; } - + // constant-time subtract modulus greater = this.greaterEquals(this.modulus); for (i=0; i<this.limbs.length; i++) { @@ -484,6 +677,10 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { return this; }; + + /** @memberof sjcl.bn + * @this { sjcl.bn } + */ ppr.inverse = function() { return (this.power(this.modulus.sub(2))); }; @@ -494,18 +691,24 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { }; // a small Mersenne prime +var sbp = sjcl.bn.pseudoMersennePrime; sjcl.bn.prime = { - p127: sjcl.bn.pseudoMersennePrime(127, [[0,-1]]), + p127: sbp(127, [[0,-1]]), // Bernstein's prime for Curve25519 - p25519: sjcl.bn.pseudoMersennePrime(255, [[0,-19]]), + p25519: sbp(255, [[0,-19]]), + + // Koblitz primes + p192k: sbp(192, [[32,-1],[12,-1],[8,-1],[7,-1],[6,-1],[3,-1],[0,-1]]), + p224k: sbp(224, [[32,-1],[12,-1],[11,-1],[9,-1],[7,-1],[4,-1],[1,-1],[0,-1]]), + p256k: sbp(256, [[32,-1],[9,-1],[8,-1],[7,-1],[6,-1],[4,-1],[0,-1]]), // NIST primes - p192: sjcl.bn.pseudoMersennePrime(192, [[0,-1],[64,-1]]), - p224: sjcl.bn.pseudoMersennePrime(224, [[0,1],[96,-1]]), - p256: sjcl.bn.pseudoMersennePrime(256, [[0,-1],[96,1],[192,1],[224,-1]]), - p384: sjcl.bn.pseudoMersennePrime(384, [[0,-1],[32,1],[96,-1],[128,-1]]), - p521: sjcl.bn.pseudoMersennePrime(521, [[0,-1]]) + p192: sbp(192, [[0,-1],[64,-1]]), + p224: sbp(224, [[0,1],[96,-1]]), + p256: sbp(256, [[0,-1],[96,1],[192,1],[224,-1]]), + p384: sbp(384, [[0,-1],[32,1],[96,-1],[128,-1]]), + p521: sbp(521, [[0,-1]]) }; sjcl.bn.random = function(modulus, paranoia) { @@ -531,4 +734,3 @@ sjcl.bn.random = function(modulus, paranoia) { } } }; - diff --git a/core/cbc.js b/core/cbc.js index 9a98ed8..39026cd 100644 --- a/core/cbc.js +++ b/core/cbc.js @@ -5,18 +5,18 @@ * @author Dan Boneh */ -/** @namespace - * Dangerous: CBC mode with PKCS#5 padding. - * - * @author Emily Stark - * @author Mike Hamburg - * @author Dan Boneh - */ if (sjcl.beware === undefined) { sjcl.beware = {}; } sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity." ] = function() { + /** @namespace + * Dangerous: CBC mode with PKCS#5 padding. + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ sjcl.mode.cbc = { /** The name of the mode. * @constant @@ -100,7 +100,7 @@ sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity. /* check and remove the pad */ bi = output[i-1] & 255; - if (bi == 0 || bi > 16) { + if (bi === 0 || bi > 16) { throw new sjcl.exception.corrupt("pkcs#5 padding corrupt"); } bo = bi * 0x1010101; diff --git a/core/ccm.js b/core/ccm.js index 8a32c5a..1efde02 100644 --- a/core/ccm.js +++ b/core/ccm.js @@ -15,6 +15,27 @@ sjcl.mode.ccm = { */ name: "ccm", + _progressListeners: [], + + listenProgress: function (cb) { + sjcl.mode.ccm._progressListeners.push(cb); + }, + + unListenProgress: function (cb) { + var index = sjcl.mode.ccm._progressListeners.indexOf(cb); + if (index > -1) { + sjcl.mode.ccm._progressListeners.splice(index, 1); + } + }, + + _callProgressListener: function (val) { + var p = sjcl.mode.ccm._progressListeners.slice(), i; + + for (i = 0; i < p.length; i += 1) { + p[i](val); + } + }, + /** Encrypt in CCM mode. * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. @@ -25,7 +46,7 @@ sjcl.mode.ccm = { * @return {bitArray} The encrypted data, an array of bytes. */ encrypt: function(prf, plaintext, iv, adata, tlen) { - var L, i, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; + var L, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; tlen = tlen || 64; adata = adata || []; @@ -59,7 +80,7 @@ sjcl.mode.ccm = { decrypt: function(prf, ciphertext, iv, adata, tlen) { tlen = tlen || 64; adata = adata || []; - var L, i, + var L, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(ciphertext), @@ -90,6 +111,36 @@ sjcl.mode.ccm = { return out.data; }, + _macAdditionalData: function (prf, adata, iv, tlen, ol, L) { + var mac, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; + + // mac the flags + mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)]; + + // mac the iv and length + mac = w.concat(mac, iv); + mac[3] |= ol; + mac = prf.encrypt(mac); + + if (adata.length) { + // mac the associated data. start with its length... + tmp = w.bitLength(adata)/8; + if (tmp <= 0xFEFF) { + macData = [w.partial(16, tmp)]; + } else if (tmp <= 0xFFFFFFFF) { + macData = w.concat([w.partial(16,0xFFFE)], [tmp]); + } // else ... + + // mac the data itself + macData = w.concat(macData, adata); + for (i=0; i<macData.length; i += 4) { + mac = prf.encrypt(xor(mac, macData.slice(i,i+4).concat([0,0,0]))); + } + } + + return mac; + }, + /* Compute the (unencrypted) authentication tag, according to the CCM specification * @param {Object} prf The pseudorandom function. * @param {bitArray} plaintext The plaintext data. @@ -101,7 +152,7 @@ sjcl.mode.ccm = { */ _computeTag: function(prf, plaintext, iv, adata, tlen, L) { // compute B[0] - var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; + var mac, i, w=sjcl.bitArray, xor = w._xor4; tlen /= 8; @@ -115,31 +166,8 @@ sjcl.mode.ccm = { throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"); } - // mac the flags - mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)]; + mac = sjcl.mode.ccm._macAdditionalData(prf, adata, iv, tlen, w.bitLength(plaintext)/8, L); - // mac the iv and length - mac = w.concat(mac, iv); - mac[3] |= w.bitLength(plaintext)/8; - mac = prf.encrypt(mac); - - - if (adata.length) { - // mac the associated data. start with its length... - tmp = w.bitLength(adata)/8; - if (tmp <= 0xFEFF) { - macData = [w.partial(16, tmp)]; - } else if (tmp <= 0xFFFFFFFF) { - macData = w.concat([w.partial(16,0xFFFE)], [tmp]); - } // else ... - - // mac the data itself - macData = w.concat(macData, adata); - for (i=0; i<macData.length; i += 4) { - mac = prf.encrypt(xor(mac, macData.slice(i,i+4).concat([0,0,0]))); - } - } - // mac the plaintext for (i=0; i<plaintext.length; i+=4) { mac = prf.encrypt(xor(mac, plaintext.slice(i,i+4).concat([0,0,0]))); @@ -161,7 +189,7 @@ sjcl.mode.ccm = { * @private */ _ctrMode: function(prf, data, iv, tag, tlen, L) { - var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, b, l = data.length, bl=w.bitLength(data); + var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, l = data.length, bl=w.bitLength(data), n = l/50, p = n; // start the ctr ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4); @@ -173,6 +201,10 @@ sjcl.mode.ccm = { if (!l) { return {tag:tag, data:[]}; } for (i=0; i<l; i+=4) { + if (i > n) { + sjcl.mode.ccm._callProgressListener(i/l); + n += p; + } ctr[3]++; enc = prf.encrypt(ctr); data[i] ^= enc[0]; diff --git a/core/ccmArrayBuffer.js b/core/ccmArrayBuffer.js new file mode 100644 index 0000000..a7b44b0 --- /dev/null +++ b/core/ccmArrayBuffer.js @@ -0,0 +1,247 @@ +/** @fileOverview Really fast & small implementation of CCM using JS' array buffers + * + * @author Marco Munizaga + */ + +/** @namespace CTR mode with CBC MAC. */ + +sjcl.arrayBuffer = sjcl.arrayBuffer || {}; + +//patch arraybuffers if they don't exist +if (typeof(ArrayBuffer) === 'undefined') { + (function(globals){ + "use strict"; + globals.ArrayBuffer = function(){}; + globals.DataView = function(){}; + }(this)); +} + + +sjcl.arrayBuffer.ccm = { + mode: "ccm", + + defaults: { + tlen:128 //this is M in the NIST paper + }, + + /** Encrypt in CCM mode. Meant to return the same exact thing as the bitArray ccm to work as a drop in replacement + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} plaintext The plaintext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [adata=[]] The authenticated data. + * @param {Number} [tlen=64] the desired tag length, in bits. + * @return {bitArray} The encrypted data, an array of bytes. + */ + compat_encrypt: function(prf, plaintext, iv, adata, tlen){ + var plaintext_buffer = sjcl.codec.arrayBuffer.fromBits(plaintext, true, 16), + ol = sjcl.bitArray.bitLength(plaintext)/8, + encrypted_obj, + ct, + tag; + + tlen = tlen || 64; + adata = adata || []; + + encrypted_obj = sjcl.arrayBuffer.ccm.encrypt(prf, plaintext_buffer, iv, adata, tlen, ol); + ct = sjcl.codec.arrayBuffer.toBits(encrypted_obj.ciphertext_buffer); + + ct = sjcl.bitArray.clamp(ct, ol*8); + + + return sjcl.bitArray.concat(ct, encrypted_obj.tag); + }, + + /** Decrypt in CCM mode. Meant to imitate the bitArray ccm + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} ciphertext The ciphertext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [[]] adata The authenticated data. + * @param {Number} [64] tlen the desired tag length, in bits. + * @return {bitArray} The decrypted data. + */ + compat_decrypt: function(prf, ciphertext, iv, adata, tlen){ + tlen = tlen || 64; + adata = adata || []; + var L, i, + w=sjcl.bitArray, + ol = w.bitLength(ciphertext), + out = w.clamp(ciphertext, ol - tlen), + tag = w.bitSlice(ciphertext, ol - tlen), tag2, + ciphertext_buffer = sjcl.codec.arrayBuffer.fromBits(out, true, 16); + + var plaintext_buffer = sjcl.arrayBuffer.ccm.decrypt(prf, ciphertext_buffer, iv, tag, adata, tlen, (ol-tlen)/8); + return sjcl.bitArray.clamp(sjcl.codec.arrayBuffer.toBits(plaintext_buffer), ol-tlen); + + }, + + /** Really fast ccm encryption, uses arraybufer and mutates the plaintext buffer + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {ArrayBuffer} plaintext_buffer The plaintext data. + * @param {bitArray} iv The initialization value. + * @param {ArrayBuffer} [adata=[]] The authenticated data. + * @param {Number} [tlen=128] the desired tag length, in bits. + * @return {ArrayBuffer} The encrypted data, in the same array buffer as the given plaintext, but given back anyways + */ + encrypt: function(prf, plaintext_buffer, iv, adata, tlen, ol){ + var auth_blocks, mac, L, w = sjcl.bitArray, + ivl = w.bitLength(iv) / 8; + + //set up defaults + adata = adata || []; + tlen = tlen || sjcl.arrayBuffer.ccm.defaults.tlen; + ol = ol || plaintext_buffer.byteLength; + tlen = Math.ceil(tlen/8); + + for (L=2; L<4 && ol >>> 8*L; L++) {} + if (L < 15 - ivl) { L = 15-ivl; } + iv = w.clamp(iv,8*(15-L)); + + //prf should use a 256 bit key to make precomputation attacks infeasible + + mac = sjcl.arrayBuffer.ccm._computeTag(prf, plaintext_buffer, iv, adata, tlen, ol, L); + + //encrypt the plaintext and the mac + //returns the mac since the plaintext will be left encrypted inside the buffer + mac = sjcl.arrayBuffer.ccm._ctrMode(prf, plaintext_buffer, iv, mac, tlen, L); + + + //the plaintext_buffer has been modified so it is now the ciphertext_buffer + return {'ciphertext_buffer':plaintext_buffer, 'tag':mac}; + }, + + /** Really fast ccm decryption, uses arraybufer and mutates the given buffer + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {ArrayBuffer} ciphertext_buffer The Ciphertext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} The authentication tag for the ciphertext + * @param {ArrayBuffer} [adata=[]] The authenticated data. + * @param {Number} [tlen=128] the desired tag length, in bits. + * @return {ArrayBuffer} The decrypted data, in the same array buffer as the given buffer, but given back anyways + */ + decrypt: function(prf, ciphertext_buffer, iv, tag, adata, tlen, ol){ + var mac, mac2, i, L, w = sjcl.bitArray, + ivl = w.bitLength(iv) / 8; + + //set up defaults + adata = adata || []; + tlen = tlen || sjcl.arrayBuffer.ccm.defaults.tlen; + ol = ol || ciphertext_buffer.byteLength; + tlen = Math.ceil(tlen/8) ; + + for (L=2; L<4 && ol >>> 8*L; L++) {} + if (L < 15 - ivl) { L = 15-ivl; } + iv = w.clamp(iv,8*(15-L)); + + //prf should use a 256 bit key to make precomputation attacks infeasible + + //decrypt the buffer + mac = sjcl.arrayBuffer.ccm._ctrMode(prf, ciphertext_buffer, iv, tag, tlen, L); + + mac2 = sjcl.arrayBuffer.ccm._computeTag(prf, ciphertext_buffer, iv, adata, tlen, ol, L); + + //check the tag + if (!sjcl.bitArray.equal(mac, mac2)){ + throw new sjcl.exception.corrupt("ccm: tag doesn't match"); + } + + return ciphertext_buffer; + + }, + + /* Compute the (unencrypted) authentication tag, according to the CCM specification + * @param {Object} prf The pseudorandom function. + * @param {ArrayBuffer} data_buffer The plaintext data in an arraybuffer. + * @param {bitArray} iv The initialization value. + * @param {bitArray} adata The authenticated data. + * @param {Number} tlen the desired tag length, in bits. + * @return {bitArray} The tag, but not yet encrypted. + * @private + */ + _computeTag: function(prf, data_buffer, iv, adata, tlen, ol, L){ + var i, plaintext, mac, data, data_blocks_size, data_blocks, + w = sjcl.bitArray, tmp, macData; + + mac = sjcl.mode.ccm._macAdditionalData(prf, adata, iv, tlen, ol, L); + + if (data_buffer.byteLength !== 0) { + data = new DataView(data_buffer); + //set padding bytes to 0 + for (i=ol; i< data_buffer.byteLength; i++){ + data.setUint8(i,0); + } + + //now to mac the plaintext blocks + for (i=0; i < data.byteLength; i+=16){ + mac[0] ^= data.getUint32(i); + mac[1] ^= data.getUint32(i+4); + mac[2] ^= data.getUint32(i+8); + mac[3] ^= data.getUint32(i+12); + + mac = prf.encrypt(mac); + } + } + + return sjcl.bitArray.clamp(mac,tlen*8); + }, + + /** CCM CTR mode. + * Encrypt or decrypt data and tag with the prf in CCM-style CTR mode. + * Mutates given array buffer + * @param {Object} prf The PRF. + * @param {ArrayBuffer} data_buffer The data to be encrypted or decrypted. + * @param {bitArray} iv The initialization vector. + * @param {bitArray} tag The authentication tag. + * @param {Number} tlen The length of th etag, in bits. + * @return {Object} An object with data and tag, the en/decryption of data and tag values. + * @private + */ + _ctrMode: function(prf, data_buffer, iv, mac, tlen, L){ + var data, ctr, word0, word1, word2, word3, keyblock, i, w = sjcl.bitArray, xor = w._xor4, n = data_buffer.byteLength/50, p = n; + + ctr = new DataView(new ArrayBuffer(16)); //create the first block for the counter + + //prf should use a 256 bit key to make precomputation attacks infeasible + + // start the ctr + ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4); + + // en/decrypt the tag + mac = w.bitSlice(xor(mac,prf.encrypt(ctr)), 0, tlen*8); + + ctr[3]++; + if (ctr[3]===0) ctr[2]++; //increment higher bytes if the lowest 4 bytes are 0 + + if (data_buffer.byteLength !== 0) { + data = new DataView(data_buffer); + //now lets encrypt the message + for (i=0; i<data.byteLength;i+=16){ + if (i > n) { + sjcl.mode.ccm._callProgressListener(i/data_buffer.byteLength); + n += p; + } + keyblock = prf.encrypt(ctr); + + word0 = data.getUint32(i); + word1 = data.getUint32(i+4); + word2 = data.getUint32(i+8); + word3 = data.getUint32(i+12); + + data.setUint32(i,word0 ^ keyblock[0]); + data.setUint32(i+4, word1 ^ keyblock[1]); + data.setUint32(i+8, word2 ^ keyblock[2]); + data.setUint32(i+12, word3 ^ keyblock[3]); + + ctr[3]++; + if (ctr[3]===0) ctr[2]++; //increment higher bytes if the lowest 4 bytes are 0 + } + } + + //return the mac, the ciphered data is available through the same data_buffer that was given + return mac; + } + +}; diff --git a/core/codecArrayBuffer.js b/core/codecArrayBuffer.js new file mode 100644 index 0000000..c2be4ca --- /dev/null +++ b/core/codecArrayBuffer.js @@ -0,0 +1,113 @@ +/** @fileOverview Bit array codec implementations. + * + * @author Marco Munizaga + */ + +//patch arraybuffers if they don't exist +if (typeof(ArrayBuffer) === 'undefined') { + (function(globals){ + "use strict"; + globals.ArrayBuffer = function(){}; + globals.DataView = function(){}; + }(this)); +} + +/** @namespace ArrayBuffer */ +sjcl.codec.arrayBuffer = { + /** Convert from a bitArray to an ArrayBuffer. + * Will default to 8byte padding if padding is undefined*/ + fromBits: function (arr, padding, padding_count) { + var out, i, ol, tmp, smallest; + padding = padding==undefined ? true : padding + padding_count = padding_count || 8 + + if (arr.length === 0) { + return new ArrayBuffer(0); + } + + ol = sjcl.bitArray.bitLength(arr)/8; + + //check to make sure the bitLength is divisible by 8, if it isn't + //we can't do anything since arraybuffers work with bytes, not bits + if ( sjcl.bitArray.bitLength(arr)%8 !== 0 ) { + throw new sjcl.exception.invalid("Invalid bit size, must be divisble by 8 to fit in an arraybuffer correctly") + } + + if (padding && ol%padding_count !== 0){ + ol += padding_count - (ol%padding_count); + } + + + //padded temp for easy copying + tmp = new DataView(new ArrayBuffer(arr.length*4)); + for (i=0; i<arr.length; i++) { + tmp.setUint32(i*4, (arr[i]<<32)); //get rid of the higher bits + } + + //now copy the final message if we are not going to 0 pad + out = new DataView(new ArrayBuffer(ol)); + + //save a step when the tmp and out bytelength are === + if (out.byteLength === tmp.byteLength){ + return tmp.buffer; + } + + smallest = tmp.byteLength < out.byteLength ? tmp.byteLength : out.byteLength; + for(i=0; i<smallest; i++){ + out.setUint8(i,tmp.getUint8(i)); + } + + + return out.buffer + }, + + toBits: function (buffer) { + var i, out=[], len, inView, tmp; + + if (buffer.byteLength === 0) { + return []; + } + + inView = new DataView(buffer); + len = inView.byteLength - inView.byteLength%4; + + for (var i = 0; i < len; i+=4) { + out.push(inView.getUint32(i)); + } + + if (inView.byteLength%4 != 0) { + tmp = new DataView(new ArrayBuffer(4)); + for (var i = 0, l = inView.byteLength%4; i < l; i++) { + //we want the data to the right, because partial slices off the starting bits + tmp.setUint8(i+4-l, inView.getUint8(len+i)); // big-endian, + } + out.push( + sjcl.bitArray.partial( (inView.byteLength%4)*8, tmp.getUint32(0) ) + ); + } + return out; + }, + + + + /** Prints a hex output of the buffer contents, akin to hexdump **/ + hexDumpBuffer: function(buffer){ + var stringBufferView = new DataView(buffer) + var string = '' + var pad = function (n, width) { + n = n + ''; + return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n; + } + + for (var i = 0; i < stringBufferView.byteLength; i+=2) { + if (i%16 == 0) string += ('\n'+(i).toString(16)+'\t') + string += ( pad(stringBufferView.getUint16(i).toString(16),4) + ' ') + } + + if ( typeof console === undefined ){ + console = console || {log:function(){}} //fix for IE + } + console.log(string.toUpperCase()) + } +}; + diff --git a/core/codecBase32.js b/core/codecBase32.js new file mode 100644 index 0000000..9251e04 --- /dev/null +++ b/core/codecBase32.js @@ -0,0 +1,88 @@ +/** @fileOverview Bit array codec implementations. + * + * @author Nils Kenneweg + */ + +/** @namespace Base32 encoding/decoding */ +sjcl.codec.base32 = { + /** The base32 alphabet. + * @private + */ + _chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", + _hexChars: "0123456789ABCDEFGHIJKLMNOPQRSTUV", + + /* bits in an array */ + BITS: 32, + /* base to encode at (2^x) */ + BASE: 5, + /* bits - base */ + REMAINING: 27, + + /** Convert from a bitArray to a base32 string. */ + fromBits: function (arr, _noEquals, _hex) { + var BITS = sjcl.codec.base32.BITS, BASE = sjcl.codec.base32.BASE, REMAINING = sjcl.codec.base32.REMAINING; + var out = "", i, bits=0, c = sjcl.codec.base32._chars, ta=0, bl = sjcl.bitArray.bitLength(arr); + + if (_hex) { + c = sjcl.codec.base32._hexChars; + } + + for (i=0; out.length * BASE < bl; ) { + out += c.charAt((ta ^ arr[i]>>>bits) >>> REMAINING); + if (bits < BASE) { + ta = arr[i] << (BASE-bits); + bits += REMAINING; + i++; + } else { + ta <<= BASE; + bits -= BASE; + } + } + while ((out.length & 7) && !_noEquals) { out += "="; } + + return out; + }, + + /** Convert from a base32 string to a bitArray */ + toBits: function(str, _hex) { + str = str.replace(/\s|=/g,'').toUpperCase(); + var BITS = sjcl.codec.base32.BITS, BASE = sjcl.codec.base32.BASE, REMAINING = sjcl.codec.base32.REMAINING; + var out = [], i, bits=0, c = sjcl.codec.base32._chars, ta=0, x, format="base32"; + + if (_hex) { + c = sjcl.codec.base32._hexChars; + format = "base32hex" + } + + for (i=0; i<str.length; i++) { + x = c.indexOf(str.charAt(i)); + if (x < 0) { + // Invalid character, try hex format + if (!_hex) { + try { + return sjcl.codec.base32hex.toBits(str); + } + catch (e) {} + } + throw new sjcl.exception.invalid("this isn't " + format + "!"); + } + if (bits > REMAINING) { + bits -= REMAINING; + out.push(ta ^ x>>>bits); + ta = x << (BITS-bits); + } else { + bits += BASE; + ta ^= x << (BITS-bits); + } + } + if (bits&56) { + out.push(sjcl.bitArray.partial(bits&56, ta, 1)); + } + return out; + } +}; + +sjcl.codec.base32hex = { + fromBits: function (arr, _noEquals) { return sjcl.codec.base32.fromBits(arr,_noEquals,1); }, + toBits: function (str) { return sjcl.codec.base32.toBits(str,1); } +}; diff --git a/core/codecBase64.js b/core/codecBase64.js index 33b2b7a..f518083 100644 --- a/core/codecBase64.js +++ b/core/codecBase64.js @@ -15,7 +15,9 @@ sjcl.codec.base64 = { /** Convert from a bitArray to a base64 string. */ fromBits: function (arr, _noEquals, _url) { var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr); - if (_url) c = c.substr(0,62) + '-_'; + if (_url) { + c = c.substr(0,62) + '-_'; + } for (i=0; out.length * 6 < bl; ) { out += c.charAt((ta ^ arr[i]>>>bits) >>> 26); if (bits < 6) { @@ -35,7 +37,9 @@ sjcl.codec.base64 = { toBits: function(str, _url) { str = str.replace(/\s|=/g,''); var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x; - if (_url) c = c.substr(0,62) + '-_'; + if (_url) { + c = c.substr(0,62) + '-_'; + } for (i=0; i<str.length; i++) { x = c.indexOf(str.charAt(i)); if (x < 0) { diff --git a/core/codecHex.js b/core/codecHex.js index 03fb628..c14d5a0 100644 --- a/core/codecHex.js +++ b/core/codecHex.js @@ -9,7 +9,7 @@ sjcl.codec.hex = { /** Convert from a bitArray to a hex string. */ fromBits: function (arr) { - var out = "", i, x; + var out = "", i; for (i=0; i<arr.length; i++) { out += ((arr[i]|0)+0xF00000000000).toString(16).substr(4); } diff --git a/core/convenience.js b/core/convenience.js index 8d68b07..ecc8cfa 100644 --- a/core/convenience.js +++ b/core/convenience.js @@ -4,7 +4,7 @@ * @author Mike Hamburg * @author Dan Boneh */ - + /** @namespace JSON encapsulation */ sjcl.json = { /** Default values for encryption */ @@ -15,13 +15,13 @@ * @param {String} plaintext The data to encrypt. * @param {Object} [params] The parameters including tag, iv and salt. * @param {Object} [rp] A returned version with filled-in parameters. - * @return {String} The ciphertext. + * @return {Object} The cipher raw data. * @throws {sjcl.exception.invalid} if a parameter is invalid. */ - encrypt: function (password, plaintext, params, rp) { + _encrypt: function (password, plaintext, params, rp) { params = params || {}; rp = rp || {}; - + var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) }, j.defaults), tmp, prp, adata; j._add(p, params); @@ -32,7 +32,7 @@ if (typeof p.iv === "string") { p.iv = sjcl.codec.base64.toBits(p.iv); } - + if (!sjcl.mode[p.mode] || !sjcl.cipher[p.cipher] || (typeof password === "string" && p.iter <= 100) || @@ -41,52 +41,73 @@ (p.iv.length < 2 || p.iv.length > 4)) { throw new sjcl.exception.invalid("json encrypt: invalid parameters"); } - + if (typeof password === "string") { tmp = sjcl.misc.cachedPbkdf2(password, p); password = tmp.key.slice(0,p.ks/32); p.salt = tmp.salt; + } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.publicKey) { + tmp = password.kem(); + p.kemtag = tmp.tag; + password = tmp.key.slice(0,p.ks/32); } if (typeof plaintext === "string") { plaintext = sjcl.codec.utf8String.toBits(plaintext); } if (typeof adata === "string") { - adata = sjcl.codec.utf8String.toBits(adata); + p.adata = adata = sjcl.codec.utf8String.toBits(adata); } prp = new sjcl.cipher[p.cipher](password); - + /* return the json data */ j._add(rp, p); rp.key = password; - + /* do the encryption */ - p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); - + if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && plaintext instanceof ArrayBuffer) { + p.ct = sjcl.arrayBuffer.ccm.encrypt(prp, plaintext, p.iv, adata, p.ts); + } else { + p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); + } + //return j.encode(j._subtract(p, j.defaults)); + return p; + }, + + /** Simple encryption function. + * @param {String|bitArray} password The password or key. + * @param {String} plaintext The data to encrypt. + * @param {Object} [params] The parameters including tag, iv and salt. + * @param {Object} [rp] A returned version with filled-in parameters. + * @return {String} The ciphertext serialized data. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + */ + encrypt: function (password, plaintext, params, rp) { + var j = sjcl.json, p = j._encrypt.apply(j, arguments); return j.encode(p); }, - + /** Simple decryption function. * @param {String|bitArray} password The password or key. - * @param {String} ciphertext The ciphertext to decrypt. + * @param {Object} ciphertext The cipher raw data to decrypt. * @param {Object} [params] Additional non-default parameters. * @param {Object} [rp] A returned object with filled parameters. * @return {String} The plaintext. * @throws {sjcl.exception.invalid} if a parameter is invalid. * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. */ - decrypt: function (password, ciphertext, params, rp) { + _decrypt: function (password, ciphertext, params, rp) { params = params || {}; rp = rp || {}; - - var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),j.decode(ciphertext)), params, true), ct, tmp, prp, adata=p.adata; + + var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata; if (typeof p.salt === "string") { p.salt = sjcl.codec.base64.toBits(p.salt); } if (typeof p.iv === "string") { p.iv = sjcl.codec.base64.toBits(p.iv); } - + if (!sjcl.mode[p.mode] || !sjcl.cipher[p.cipher] || (typeof password === "string" && p.iter <= 100) || @@ -96,27 +117,51 @@ (p.iv.length < 2 || p.iv.length > 4)) { throw new sjcl.exception.invalid("json decrypt: invalid parameters"); } - + if (typeof password === "string") { tmp = sjcl.misc.cachedPbkdf2(password, p); password = tmp.key.slice(0,p.ks/32); p.salt = tmp.salt; + } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.secretKey) { + password = password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32); } if (typeof adata === "string") { adata = sjcl.codec.utf8String.toBits(adata); } prp = new sjcl.cipher[p.cipher](password); - + /* do the decryption */ - ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); - + if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && p.ct instanceof ArrayBuffer) { + ct = sjcl.arrayBuffer.ccm.decrypt(prp, p.ct, p.iv, p.tag, adata, p.ts); + } else { + ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); + } + /* return the json data */ j._add(rp, p); rp.key = password; - - return sjcl.codec.utf8String.fromBits(ct); + + if (params.raw === 1) { + return ct; + } else { + return sjcl.codec.utf8String.fromBits(ct); + } }, - + + /** Simple decryption function. + * @param {String|bitArray} password The password or key. + * @param {String} ciphertext The ciphertext to decrypt. + * @param {Object} [params] Additional non-default parameters. + * @param {Object} [rp] A returned object with filled parameters. + * @return {String} The plaintext. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. + */ + decrypt: function (password, ciphertext, params, rp) { + var j = sjcl.json; + return j._decrypt(password, j.decode(ciphertext), params, rp); + }, + /** Encode a flat structure into a JSON string. * @param {Object} obj The structure to encode. * @return {String} A JSON string. @@ -130,31 +175,31 @@ if (!i.match(/^[a-z0-9]+$/i)) { throw new sjcl.exception.invalid("json encode: invalid property name"); } - out += comma + '"' + i + '"' + ':'; + out += comma + '"' + i + '":'; comma = ','; - + switch (typeof obj[i]) { - case 'number': - case 'boolean': - out += obj[i]; - break; - - case 'string': - out += '"' + escape(obj[i]) + '"'; - break; - - case 'object': - out += '"' + sjcl.codec.base64.fromBits(obj[i],1) + '"'; - break; - - default: - throw new sjcl.exception.bug("json encode: unsupported type"); + case 'number': + case 'boolean': + out += obj[i]; + break; + + case 'string': + out += '"' + escape(obj[i]) + '"'; + break; + + case 'object': + out += '"' + sjcl.codec.base64.fromBits(obj[i],0) + '"'; + break; + + default: + throw new sjcl.exception.bug("json encode: unsupported type"); } } } return out+'}'; }, - + /** Decode a simple (flat) JSON string into a structure. The ciphertext, * adata, salt and iv will be base64-decoded. * @param {String} str The string. @@ -163,23 +208,25 @@ */ decode: function (str) { str = str.replace(/\s/g,''); - if (!str.match(/^\{.*\}$/)) { + if (!str.match(/^\{.*\}$/)) { throw new sjcl.exception.invalid("json decode: this isn't json!"); } var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m; for (i=0; i<a.length; i++) { - if (!(m=a[i].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))) { + if (!(m=a[i].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i))) { throw new sjcl.exception.invalid("json decode: this isn't json!"); } - if (m[3]) { + if (m[3] != null) { out[m[2]] = parseInt(m[3],10); - } else { - out[m[2]] = m[2].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]); + } else if (m[4] != null) { + out[m[2]] = m[2].match(/^(ct|adata|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]); + } else if (m[5] != null) { + out[m[2]] = m[5] === 'true'; } } return out; }, - + /** Insert all elements of src into target, modifying and returning target. * @param {Object} target The object to be modified. * @param {Object} src The object to pull data from. @@ -201,22 +248,22 @@ } return target; }, - + /** Remove all elements of minus from plus. Does not modify plus. * @private */ _subtract: function (plus, minus) { var out = {}, i; - + for (i in plus) { if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) { out[i] = plus[i]; } } - + return out; }, - + /** Return only the specified elements of src. * @private */ @@ -256,24 +303,22 @@ sjcl.misc._pbkdf2Cache = {}; /** Cached PBKDF2 key derivation. * @param {String} password The password. - * @param {Object} [params] The derivation params (iteration count and optional salt). + * @param {Object} [obj] The derivation params (iteration count and optional salt). * @return {Object} The derived data in key, the salt in salt. */ sjcl.misc.cachedPbkdf2 = function (password, obj) { var cache = sjcl.misc._pbkdf2Cache, c, cp, str, salt, iter; - + obj = obj || {}; iter = obj.iter || 1000; - + /* open the cache for this password and iteration count */ cp = cache[password] = cache[password] || {}; c = cp[iter] = cp[iter] || { firstSalt: (obj.salt && obj.salt.length) ? obj.salt.slice(0) : sjcl.random.randomWords(2,0) }; - + salt = (obj.salt === undefined) ? c.firstSalt : obj.salt; - + c[salt] = c[salt] || sjcl.misc.pbkdf2(password, salt, obj.iter); return { key: c[salt].slice(0), salt:salt.slice(0) }; }; - - diff --git a/core/ecc.js b/core/ecc.js index 504288f..67a54e7 100644 --- a/core/ecc.js +++ b/core/ecc.js @@ -1,3 +1,6 @@ +/** + * base class for all ecc operations. + */ sjcl.ecc = {}; /** @@ -11,8 +14,16 @@ sjcl.ecc.point = function(curve,x,y) { if (x === undefined) { this.isIdentity = true; } else { + if (x instanceof sjcl.bn) { + x = new curve.field(x); + } + if (y instanceof sjcl.bn) { + y = new curve.field(y); + } + this.x = x; this.y = y; + this.isIdentity = false; } this.curve = curve; @@ -28,7 +39,7 @@ sjcl.ecc.point.prototype = { mult: function(k) { return this.toJac().mult(k, this).toAffine(); }, - + /** * Multiply this point by k, added to affine2*k2, and return the answer in Jacobian coordinates. * @param {bigInt} k The coefficient to multiply this by. @@ -39,7 +50,7 @@ sjcl.ecc.point.prototype = { mult2: function(k, k2, affine2) { return this.toJac().mult2(k, this, k2, affine2).toAffine(); }, - + multiples: function() { var m, i, j; if (this._multiples === undefined) { @@ -53,6 +64,11 @@ sjcl.ecc.point.prototype = { return this._multiples; }, + negate: function() { + var newY = new this.curve.field(0).sub(this.y).normalize().reduce(); + return new sjcl.ecc.point(this.curve, this.x, newY); + }, + isValid: function() { return this.y.square().equals(this.curve.b.add(this.x.mul(this.curve.a.add(this.x.square())))); }, @@ -89,7 +105,7 @@ sjcl.ecc.pointJac.prototype = { * Adds S and T and returns the result in Jacobian coordinates. Note that S must be in Jacobian coordinates and T must be in affine coordinates. * @param {sjcl.ecc.pointJac} S One of the points to add, in Jacobian coordinates. * @param {sjcl.ecc.point} T The other point to add, in affine coordinates. - * @return {sjcl.ecc.pointJac} The sum of the two points, in Jacobian coordinates. + * @return {sjcl.ecc.pointJac} The sum of the two points, in Jacobian coordinates. */ add: function(T) { var S = this, sz2, c, d, c2, x1, x2, x, y1, y2, y, z; @@ -115,7 +131,7 @@ sjcl.ecc.pointJac.prototype = { return new sjcl.ecc.pointJac(S.curve); } } - + d = T.y.mul(sz2.mul(S.z)).subM(S.y); c2 = c.square(); @@ -131,7 +147,7 @@ sjcl.ecc.pointJac.prototype = { return new sjcl.ecc.pointJac(this.curve,x,y,z); }, - + /** * doubles this point. * @return {sjcl.ecc.pointJac} The doubled point. @@ -144,7 +160,9 @@ sjcl.ecc.pointJac.prototype = { a = y2.mul(this.x.mul(4)), b = y2.square().mul(8), z2 = this.z.square(), - c = this.x.sub(z2).mul(3).mul(this.x.add(z2)), + c = this.curve.a.toString() == (new sjcl.bn(-3)).toString() ? + this.x.sub(z2).mul(3).mul(this.x.add(z2)) : + this.x.square().mul(3).add(z2.square().mul(this.curve.a)), x = c.square().subM(a).subM(a), y = a.sub(x).mul(c).subM(b), z = this.y.add(this.y).mul(this.z); @@ -154,7 +172,7 @@ sjcl.ecc.pointJac.prototype = { /** * Returns a copy of this point converted to affine coordinates. * @return {sjcl.ecc.point} The converted point. - */ + */ toAffine: function() { if (this.isIdentity || this.z.equals(0)) { return new sjcl.ecc.point(this.curve); @@ -162,7 +180,7 @@ sjcl.ecc.pointJac.prototype = { var zi = this.z.inverse(), zi2 = zi.square(); return new sjcl.ecc.point(this.curve, this.x.mul(zi2).fullReduce(), this.y.mul(zi2.mul(zi)).fullReduce()); }, - + /** * Multiply this point by k and return the answer in Jacobian coordinates. * @param {bigInt} k The coefficient to multiply by. @@ -175,7 +193,7 @@ sjcl.ecc.pointJac.prototype = { } else if (k.limbs !== undefined) { k = k.normalize().limbs; } - + var i, j, out = new sjcl.ecc.point(this.curve).toJac(), multiples = affine.multiples(); for (i=k.length-1; i>=0; i--) { @@ -183,10 +201,10 @@ sjcl.ecc.pointJac.prototype = { out = out.doubl().doubl().doubl().doubl().add(multiples[k[i]>>j & 0xF]); } } - + return out; }, - + /** * Multiply this point by k, added to affine2*k2, and return the answer in Jacobian coordinates. * @param {bigInt} k The coefficient to multiply this by. @@ -201,13 +219,13 @@ sjcl.ecc.pointJac.prototype = { } else if (k1.limbs !== undefined) { k1 = k1.normalize().limbs; } - + if (typeof(k2) === "number") { k2 = [k2]; } else if (k2.limbs !== undefined) { k2 = k2.normalize().limbs; } - + var i, j, out = new sjcl.ecc.point(this.curve).toJac(), m1 = affine.multiples(), m2 = affine2.multiples(), l1, l2; @@ -218,10 +236,14 @@ sjcl.ecc.pointJac.prototype = { out = out.doubl().doubl().doubl().doubl().add(m1[l1>>j & 0xF]).add(m2[l2>>j & 0xF]); } } - + return out; }, + negate: function() { + return this.toAffine().negate().toJac(); + }, + isValid: function() { var z2 = this.z.square(), z4 = z2.square(), z6 = z4.mul(z2); return this.y.square().equals( @@ -242,7 +264,7 @@ sjcl.ecc.pointJac.prototype = { */ sjcl.ecc.curve = function(Field, r, a, b, x, y) { this.field = Field; - this.r = Field.prototype.modulus.sub(r); + this.r = new sjcl.bn(r); this.a = new Field(a); this.b = new Field(b); this.G = new sjcl.ecc.point(this, new Field(x), new Field(y)); @@ -261,7 +283,7 @@ sjcl.ecc.curve.prototype.fromBits = function (bits) { sjcl.ecc.curves = { c192: new sjcl.ecc.curve( sjcl.bn.prime.p192, - "0x662107c8eb94364e4b2dd7ce", + "0xffffffffffffffffffffffff99def836146bc9b1b4d22831", -3, "0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", "0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012", @@ -269,7 +291,7 @@ sjcl.ecc.curves = { c224: new sjcl.ecc.curve( sjcl.bn.prime.p224, - "0xe95c1f470fc1ec22d6baa3a3d5c4", + "0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", -3, "0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4", "0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21", @@ -277,7 +299,7 @@ sjcl.ecc.curves = { c256: new sjcl.ecc.curve( sjcl.bn.prime.p256, - "0x4319055358e8617b0c46353d039cdaae", + "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", -3, "0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", @@ -285,51 +307,143 @@ sjcl.ecc.curves = { c384: new sjcl.ecc.curve( sjcl.bn.prime.p384, - "0x389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68c", + "0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", -3, "0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef", "0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", - "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f") + "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"), + + c521: new sjcl.ecc.curve( + sjcl.bn.prime.p521, + "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", + -3, + "0x051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", + "0xC6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", + "0x11839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650"), + + k192: new sjcl.ecc.curve( + sjcl.bn.prime.p192k, + "0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d", + 0, + 3, + "0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d", + "0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d"), + + k224: new sjcl.ecc.curve( + sjcl.bn.prime.p224k, + "0x010000000000000000000000000001dce8d2ec6184caf0a971769fb1f7", + 0, + 5, + "0xa1455b334df099df30fc28a169a467e9e47075a90f7e650eb6b7a45c", + "0x7e089fed7fba344282cafbd6f7e319f7c0b0bd59e2ca4bdb556d61a5"), + + k256: new sjcl.ecc.curve( + sjcl.bn.prime.p256k, + "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + 0, + 7, + "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8") + }; +/** our basicKey classes +*/ +sjcl.ecc.basicKey = { + /** ecc publicKey. + * @constructor + * @param {curve} curve the elliptic curve + * @param {point} point the point on the curve + */ + publicKey: function(curve, point) { + this._curve = curve; + this._curveBitLength = curve.r.bitLength(); + if (point instanceof Array) { + this._point = curve.fromBits(point); + } else { + this._point = point; + } -/* Diffie-Hellman-like public-key system */ -sjcl.ecc._dh = function(cn) { - sjcl.ecc[cn] = { - publicKey: function(curve, point) { - this._curve = curve; - if (point instanceof Array) { - this._point = curve.fromBits(point); - } else { - this._point = point; - } - }, + /** get this keys point data + * @return x and y as bitArrays + */ + this.get = function() { + var pointbits = this._point.toBits(); + var len = sjcl.bitArray.bitLength(pointbits); + var x = sjcl.bitArray.bitSlice(pointbits, 0, len/2); + var y = sjcl.bitArray.bitSlice(pointbits, len/2); + return { x: x, y: y }; + }; + }, - secretKey: function(curve, exponent) { - this._curve = curve; - this._exponent = exponent; - }, + /** ecc secretKey + * @constructor + * @param {curve} curve the elliptic curve + * @param exponent + */ + secretKey: function(curve, exponent) { + this._curve = curve; + this._curveBitLength = curve.r.bitLength(); + this._exponent = exponent; + + /** get this keys exponent data + * @return {bitArray} exponent + */ + this.get = function () { + return this._exponent.toBits(); + }; + } +}; - generateKeys: function(curve, paranoia) { +/** @private */ +sjcl.ecc.basicKey.generateKeys = function(cn) { + return function generateKeys(curve, paranoia, sec) { + curve = curve || 256; + + if (typeof curve === "number") { + curve = sjcl.ecc.curves['c'+curve]; if (curve === undefined) { - curve = 256; - } - if (typeof curve === "number") { - curve = sjcl.ecc.curves['c'+curve]; - if (curve === undefined) { - throw new sjcl.exception.invalid("no such curve"); - } + throw new sjcl.exception.invalid("no such curve"); } - var sec = sjcl.bn.random(curve.r, paranoia), pub = curve.G.mult(sec); - return { pub: new sjcl.ecc[cn].publicKey(curve, pub), - sec: new sjcl.ecc[cn].secretKey(curve, sec) }; } - }; + sec = sec || sjcl.bn.random(curve.r, paranoia); + + var pub = curve.G.mult(sec); + return { pub: new sjcl.ecc[cn].publicKey(curve, pub), + sec: new sjcl.ecc[cn].secretKey(curve, sec) }; + }; }; -sjcl.ecc._dh("elGamal"); +/** elGamal keys */ +sjcl.ecc.elGamal = { + /** generate keys + * @function + * @param curve + * @param {int} paranoia Paranoia for generation (default 6) + * @param {secretKey} sec secret Key to use. used to get the publicKey for ones secretKey + */ + generateKeys: sjcl.ecc.basicKey.generateKeys("elGamal"), + /** elGamal publicKey. + * @constructor + * @augments sjcl.ecc.basicKey.publicKey + */ + publicKey: function (curve, point) { + sjcl.ecc.basicKey.publicKey.apply(this, arguments); + }, + /** elGamal secretKey + * @constructor + * @augments sjcl.ecc.basicKey.secretKey + */ + secretKey: function (curve, exponent) { + sjcl.ecc.basicKey.secretKey.apply(this, arguments); + } +}; sjcl.ecc.elGamal.publicKey.prototype = { + /** Kem function of elGamal Public Key + * @param paranoia paranoia to use for randomization. + * @return {object} key and tag. unkem(tag) with the corresponding secret key results in the key returned. + */ kem: function(paranoia) { var sec = sjcl.bn.random(this._curve.r, paranoia), tag = this._curve.G.mult(sec).toBits(), @@ -339,42 +453,108 @@ sjcl.ecc.elGamal.publicKey.prototype = { }; sjcl.ecc.elGamal.secretKey.prototype = { + /** UnKem function of elGamal Secret Key + * @param {bitArray} tag The Tag to decrypt. + * @return {bitArray} decrypted key. + */ unkem: function(tag) { return sjcl.hash.sha256.hash(this._curve.fromBits(tag).mult(this._exponent).toBits()); }, + /** Diffie-Hellmann function + * @param {elGamal.publicKey} pk The Public Key to do Diffie-Hellmann with + * @return {bitArray} diffie-hellmann result for this key combination. + */ dh: function(pk) { return sjcl.hash.sha256.hash(pk._point.mult(this._exponent).toBits()); - } + }, + + /** Diffie-Hellmann function, compatible with Java generateSecret + * @param {elGamal.publicKey} pk The Public Key to do Diffie-Hellmann with + * @return {bitArray} undigested X value, diffie-hellmann result for this key combination, + * compatible with Java generateSecret(). + */ + dhJavaEc: function(pk) { + return pk._point.mult(this._exponent).x.toBits(); + } }; -sjcl.ecc._dh("ecdsa"); +/** ecdsa keys */ +sjcl.ecc.ecdsa = { + /** generate keys + * @function + * @param curve + * @param {int} paranoia Paranoia for generation (default 6) + * @param {secretKey} sec secret Key to use. used to get the publicKey for ones secretKey + */ + generateKeys: sjcl.ecc.basicKey.generateKeys("ecdsa") +}; -sjcl.ecc.ecdsa.secretKey.prototype = { - sign: function(hash, paranoia) { - var R = this._curve.r, - l = R.bitLength(), - k = sjcl.bn.random(R.sub(1), paranoia).add(1), - r = this._curve.G.mult(k).x.mod(R), - s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).inverseMod(R).mul(k).mod(R); - return sjcl.bitArray.concat(r.toBits(l), s.toBits(l)); - } +/** ecdsa publicKey. +* @constructor +* @augments sjcl.ecc.basicKey.publicKey +*/ +sjcl.ecc.ecdsa.publicKey = function (curve, point) { + sjcl.ecc.basicKey.publicKey.apply(this, arguments); }; +/** specific functions for ecdsa publicKey. */ sjcl.ecc.ecdsa.publicKey.prototype = { - verify: function(hash, rs) { + /** Diffie-Hellmann function + * @param {bitArray} hash hash to verify. + * @param {bitArray} rs signature bitArray. + * @param {boolean} fakeLegacyVersion use old legacy version + */ + verify: function(hash, rs, fakeLegacyVersion) { + if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) { + hash = sjcl.bitArray.clamp(hash, this._curveBitLength); + } var w = sjcl.bitArray, R = this._curve.r, - l = R.bitLength(), + l = this._curveBitLength, r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)), - s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)), + ss = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)), + s = fakeLegacyVersion ? ss : ss.inverseMod(R), hG = sjcl.bn.fromBits(hash).mul(s).mod(R), hA = r.mul(s).mod(R), r2 = this._curve.G.mult2(hG, hA, this._point).x; - - if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) { - throw (new sjcl.exception.corrupt("signature didn't check out")); + if (r.equals(0) || ss.equals(0) || r.greaterEquals(R) || ss.greaterEquals(R) || !r2.equals(r)) { + if (fakeLegacyVersion === undefined) { + return this.verify(hash, rs, true); + } else { + throw (new sjcl.exception.corrupt("signature didn't check out")); + } } return true; } }; + +/** ecdsa secretKey +* @constructor +* @augments sjcl.ecc.basicKey.publicKey +*/ +sjcl.ecc.ecdsa.secretKey = function (curve, exponent) { + sjcl.ecc.basicKey.secretKey.apply(this, arguments); +}; + +/** specific functions for ecdsa secretKey. */ +sjcl.ecc.ecdsa.secretKey.prototype = { + /** Diffie-Hellmann function + * @param {bitArray} hash hash to sign. + * @param {int} paranoia paranoia for random number generation + * @param {boolean} fakeLegacyVersion use old legacy version + */ + sign: function(hash, paranoia, fakeLegacyVersion, fixedKForTesting) { + if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) { + hash = sjcl.bitArray.clamp(hash, this._curveBitLength); + } + var R = this._curve.r, + l = R.bitLength(), + k = fixedKForTesting || sjcl.bn.random(R.sub(1), paranoia).add(1), + r = this._curve.G.mult(k).x.mod(R), + ss = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)), + s = fakeLegacyVersion ? ss.inverseMod(R).mul(k).mod(R) + : ss.mul(k.inverseMod(R)).mod(R); + return sjcl.bitArray.concat(r.toBits(l), s.toBits(l)); + } +}; diff --git a/core/gcm.js b/core/gcm.js new file mode 100644 index 0000000..61bf59d --- /dev/null +++ b/core/gcm.js @@ -0,0 +1,183 @@ +/** @fileOverview GCM mode implementation. + * + * @author Juho Vähä-Herttua + */ + +/** @namespace Galois/Counter mode. */ +sjcl.mode.gcm = { + /** The name of the mode. + * @constant + */ + name: "gcm", + + /** Encrypt in GCM mode. + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} plaintext The plaintext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [adata=[]] The authenticated data. + * @param {Number} [tlen=128] The desired tag length, in bits. + * @return {bitArray} The encrypted data, an array of bytes. + */ + encrypt: function (prf, plaintext, iv, adata, tlen) { + var out, data = plaintext.slice(0), w=sjcl.bitArray; + tlen = tlen || 128; + adata = adata || []; + + // encrypt and tag + out = sjcl.mode.gcm._ctrMode(true, prf, data, adata, iv, tlen); + + return w.concat(out.data, out.tag); + }, + + /** Decrypt in GCM mode. + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} ciphertext The ciphertext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [adata=[]] The authenticated data. + * @param {Number} [tlen=128] The desired tag length, in bits. + * @return {bitArray} The decrypted data. + */ + decrypt: function (prf, ciphertext, iv, adata, tlen) { + var out, data = ciphertext.slice(0), tag, w=sjcl.bitArray, l=w.bitLength(data); + tlen = tlen || 128; + adata = adata || []; + + // Slice tag out of data + if (tlen <= l) { + tag = w.bitSlice(data, l-tlen); + data = w.bitSlice(data, 0, l-tlen); + } else { + tag = data; + data = []; + } + + // decrypt and tag + out = sjcl.mode.gcm._ctrMode(false, prf, data, adata, iv, tlen); + + if (!w.equal(out.tag, tag)) { + throw new sjcl.exception.corrupt("gcm: tag doesn't match"); + } + return out.data; + }, + + /* Compute the galois multiplication of X and Y + * @private + */ + _galoisMultiply: function (x, y) { + var i, j, xi, Zi, Vi, lsb_Vi, w=sjcl.bitArray, xor=w._xor4; + + Zi = [0,0,0,0]; + Vi = y.slice(0); + + // Block size is 128 bits, run 128 times to get Z_128 + for (i=0; i<128; i++) { + xi = (x[Math.floor(i/32)] & (1 << (31-i%32))) !== 0; + if (xi) { + // Z_i+1 = Z_i ^ V_i + Zi = xor(Zi, Vi); + } + + // Store the value of LSB(V_i) + lsb_Vi = (Vi[3] & 1) !== 0; + + // V_i+1 = V_i >> 1 + for (j=3; j>0; j--) { + Vi[j] = (Vi[j] >>> 1) | ((Vi[j-1]&1) << 31); + } + Vi[0] = Vi[0] >>> 1; + + // If LSB(V_i) is 1, V_i+1 = (V_i >> 1) ^ R + if (lsb_Vi) { + Vi[0] = Vi[0] ^ (0xe1 << 24); + } + } + return Zi; + }, + + _ghash: function(H, Y0, data) { + var Yi, i, l = data.length; + + Yi = Y0.slice(0); + for (i=0; i<l; i+=4) { + Yi[0] ^= 0xffffffff&data[i]; + Yi[1] ^= 0xffffffff&data[i+1]; + Yi[2] ^= 0xffffffff&data[i+2]; + Yi[3] ^= 0xffffffff&data[i+3]; + Yi = sjcl.mode.gcm._galoisMultiply(Yi, H); + } + return Yi; + }, + + /** GCM CTR mode. + * Encrypt or decrypt data and tag with the prf in GCM-style CTR mode. + * @param {Boolean} encrypt True if encrypt, false if decrypt. + * @param {Object} prf The PRF. + * @param {bitArray} data The data to be encrypted or decrypted. + * @param {bitArray} iv The initialization vector. + * @param {bitArray} adata The associated data to be tagged. + * @param {Number} tlen The length of the tag, in bits. + */ + _ctrMode: function(encrypt, prf, data, adata, iv, tlen) { + var H, J0, S0, enc, i, ctr, tag, last, l, bl, abl, ivbl, w=sjcl.bitArray; + + // Calculate data lengths + l = data.length; + bl = w.bitLength(data); + abl = w.bitLength(adata); + ivbl = w.bitLength(iv); + + // Calculate the parameters + H = prf.encrypt([0,0,0,0]); + if (ivbl === 96) { + J0 = iv.slice(0); + J0 = w.concat(J0, [1]); + } else { + J0 = sjcl.mode.gcm._ghash(H, [0,0,0,0], iv); + J0 = sjcl.mode.gcm._ghash(H, J0, [0,0,Math.floor(ivbl/0x100000000),ivbl&0xffffffff]); + } + S0 = sjcl.mode.gcm._ghash(H, [0,0,0,0], adata); + + // Initialize ctr and tag + ctr = J0.slice(0); + tag = S0.slice(0); + + // If decrypting, calculate hash + if (!encrypt) { + tag = sjcl.mode.gcm._ghash(H, S0, data); + } + + // Encrypt all the data + for (i=0; i<l; i+=4) { + ctr[3]++; + enc = prf.encrypt(ctr); + data[i] ^= enc[0]; + data[i+1] ^= enc[1]; + data[i+2] ^= enc[2]; + data[i+3] ^= enc[3]; + } + data = w.clamp(data, bl); + + // If encrypting, calculate hash + if (encrypt) { + tag = sjcl.mode.gcm._ghash(H, S0, data); + } + + // Calculate last block from bit lengths, ugly because bitwise operations are 32-bit + last = [ + Math.floor(abl/0x100000000), abl&0xffffffff, + Math.floor(bl/0x100000000), bl&0xffffffff + ]; + + // Calculate the final tag block + tag = sjcl.mode.gcm._ghash(H, tag, last); + enc = prf.encrypt(J0); + tag[0] ^= enc[0]; + tag[1] ^= enc[1]; + tag[2] ^= enc[2]; + tag[3] ^= enc[3]; + + return { tag:w.bitSlice(tag, 0, tlen), data:data }; + } +}; diff --git a/core/hmac.js b/core/hmac.js index 87237b2..84f9a15 100644 --- a/core/hmac.js +++ b/core/hmac.js @@ -27,14 +27,35 @@ sjcl.misc.hmac = function (key, Hash) { this._baseHash[0].update(exKey[0]); this._baseHash[1].update(exKey[1]); + this._resultHash = new Hash(this._baseHash[0]); }; /** HMAC with the specified hash function. Also called encrypt since it's a prf. * @param {bitArray|String} data The data to mac. - * @param {Codec} [encoding] the encoding function to use. */ -sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data, encoding) { - var w = new (this._hash)(this._baseHash[0]).update(data, encoding).finalize(); - return new (this._hash)(this._baseHash[1]).update(w).finalize(); +sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data) { + if (!this._updated) { + this.update(data); + return this.digest(data); + } else { + throw new sjcl.exception.invalid("encrypt on already updated hmac called!"); + } +}; + +sjcl.misc.hmac.prototype.reset = function () { + this._resultHash = new this._hash(this._baseHash[0]); + this._updated = false; }; +sjcl.misc.hmac.prototype.update = function (data) { + this._updated = true; + this._resultHash.update(data); +}; + +sjcl.misc.hmac.prototype.digest = function () { + var w = this._resultHash.finalize(), result = new (this._hash)(this._baseHash[1]).update(w).finalize(); + + this.reset(); + + return result; +};
\ No newline at end of file diff --git a/core/ocb2progressive.js b/core/ocb2progressive.js new file mode 100644 index 0000000..0cb1790 --- /dev/null +++ b/core/ocb2progressive.js @@ -0,0 +1,137 @@ +/** + * OCB2.0 implementation slightly modified by Yifan Gu + * to support progressive encryption + * @author Yifan Gu + */ + +/** @fileOverview OCB 2.0 implementation + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ + +/** @namespace + * Phil Rogaway's Offset CodeBook mode, version 2.0. + * May be covered by US and international patents. + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ + +sjcl.mode.ocb2progressive = { + createEncryptor: function(prp, iv, adata, tlen, premac) { + if (sjcl.bitArray.bitLength(iv) !== 128) { + throw new sjcl.exception.invalid("ocb iv must be 128 bits"); + } + var i, + times2 = sjcl.mode.ocb2._times2, + w = sjcl.bitArray, + xor = w._xor4, + checksum = [0,0,0,0], + delta = times2(prp.encrypt(iv)), + bi, bl, + datacache = [], + pad; + + adata = adata || []; + tlen = tlen || 64; + + return { + process: function(data){ + var datalen = sjcl.bitArray.bitLength(data); + if (datalen == 0){ // empty input natrually gives empty output + return []; + } + var output = []; + datacache = datacache.concat(data); + for (i=0; i+4 < datacache.length; i+=4) { + /* Encrypt a non-final block */ + bi = datacache.slice(i,i+4); + checksum = xor(checksum, bi); + output = output.concat(xor(delta,prp.encrypt(xor(delta, bi)))); + delta = times2(delta); + } + datacache = datacache.slice(i); // at end of each process we ensure size of datacache is smaller than 4 + return output; //spits out the result. + }, + finalize: function(){ + // the final block + bi = datacache; + bl = w.bitLength(bi); + pad = prp.encrypt(xor(delta,[0,0,0,bl])); + bi = w.clamp(xor(bi.concat([0,0,0]),pad), bl); + + /* Checksum the final block, and finalize the checksum */ + checksum = xor(checksum,xor(bi.concat([0,0,0]),pad)); + checksum = prp.encrypt(xor(checksum,xor(delta,times2(delta)))); + + /* MAC the header */ + if (adata.length) { + checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata)); + } + + return w.concat(bi, w.clamp(checksum, tlen)); // spits out the last block + } + }; + }, + createDecryptor: function(prp, iv, adata, tlen, premac){ + if (sjcl.bitArray.bitLength(iv) !== 128) { + throw new sjcl.exception.invalid("ocb iv must be 128 bits"); + } + tlen = tlen || 64; + var i, + times2 = sjcl.mode.ocb2._times2, + w = sjcl.bitArray, + xor = w._xor4, + checksum = [0,0,0,0], + delta = times2(prp.encrypt(iv)), + bi, bl, + datacache = [], + pad; + + adata = adata || []; + + return { + process: function(data){ + if (data.length == 0){ // empty input natrually gives empty output + return []; + } + var output = []; + datacache = datacache.concat(data); + var cachelen = sjcl.bitArray.bitLength(datacache); + for (i=0; i+4 < (cachelen-tlen)/32; i+=4) { + /* Decrypt a non-final block */ + bi = xor(delta, prp.decrypt(xor(delta, datacache.slice(i,i+4)))); + checksum = xor(checksum, bi); + output = output.concat(bi); + delta = times2(delta); + } + datacache = datacache.slice(i); + return output; + }, + finalize: function(){ + /* Chop out and decrypt the final block */ + bl = sjcl.bitArray.bitLength(datacache) - tlen; + pad = prp.encrypt(xor(delta,[0,0,0,bl])); + bi = xor(pad, w.clamp(datacache,bl).concat([0,0,0])); + + /* Checksum the final block, and finalize the checksum */ + checksum = xor(checksum, bi); + checksum = prp.encrypt(xor(checksum, xor(delta, times2(delta)))); + + /* MAC the header */ + if (adata.length) { + checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata)); + } + + if (!w.equal(w.clamp(checksum, tlen), w.bitSlice(datacache, bl))) { + throw new sjcl.exception.corrupt("ocb: tag doesn't match"); + } + + return w.clamp(bi,bl); + } + }; + } +} diff --git a/core/pbkdf2.js b/core/pbkdf2.js index 869e4df..3e9aa3f 100644 --- a/core/pbkdf2.js +++ b/core/pbkdf2.js @@ -12,7 +12,7 @@ * This is the method specified by RSA's PKCS #5 standard. * * @param {bitArray|String} password The password. - * @param {bitArray} salt The salt. Should have lots of entropy. + * @param {bitArray|String} salt The salt. Should have lots of entropy. * @param {Number} [count=1000] The number of iterations. Higher numbers make the function slower but more secure. * @param {Number} [length] The length of the derived key. Defaults to the output size of the hash function. @@ -30,6 +30,10 @@ sjcl.misc.pbkdf2 = function (password, salt, count, length, Prff) { password = sjcl.codec.utf8String.toBits(password); } + if (typeof salt === "string") { + salt = sjcl.codec.utf8String.toBits(salt); + } + Prff = Prff || sjcl.misc.hmac; var prf = new Prff(password), diff --git a/core/random.js b/core/random.js index 6def0d3..fed0ebf 100644 --- a/core/random.js +++ b/core/random.js @@ -3,11 +3,13 @@ * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh + * @author Michael Brooks */ -/** @namespace Random number generator - * +/** @constructor + * @class Random number generator * @description + * <b>Use sjcl.random as a singleton for this class!</b> * <p> * This random number generator is a derivative of Ferguson and Schneier's * generator Fortuna. It collects entropy from various events into several @@ -39,8 +41,45 @@ * look for improvements in future versions. * </p> */ -sjcl.random = { - /** Generate several random words, and return them in an array +sjcl.prng = function(defaultParanoia) { + + /* private */ + this._pools = [new sjcl.hash.sha256()]; + this._poolEntropy = [0]; + this._reseedCount = 0; + this._robins = {}; + this._eventId = 0; + + this._collectorIds = {}; + this._collectorIdNext = 0; + + this._strength = 0; + this._poolStrength = 0; + this._nextReseed = 0; + this._key = [0,0,0,0,0,0,0,0]; + this._counter = [0,0,0,0]; + this._cipher = undefined; + this._defaultParanoia = defaultParanoia; + + /* event listener stuff */ + this._collectorsStarted = false; + this._callbacks = {progress: {}, seeded: {}}; + this._callbackI = 0; + + /* constants */ + this._NOT_READY = 0; + this._READY = 1; + this._REQUIRES_RESEED = 2; + + this._MAX_WORDS_PER_BURST = 65536; + this._PARANOIA_LEVELS = [0,48,64,96,128,192,256,384,512,768,1024]; + this._MILLISECONDS_PER_RESEED = 30000; + this._BITS_PER_RESEED = 80; +}; + +sjcl.prng.prototype = { + /** Generate several random words, and return them in an array. + * A word consists of 32 bits (4 bytes) * @param {Number} nwords The number of words to generate. */ randomWords: function (nwords, paranoia) { @@ -65,7 +104,11 @@ sjcl.random = { return out.slice(0,nwords); }, - setDefaultParanoia: function (paranoia) { + setDefaultParanoia: function (paranoia, allowZeroParanoia) { + if (paranoia === 0 && allowZeroParanoia !== "Setting paranoia=0 will ruin your security; use it only for testing") { + throw "Setting paranoia=0 will ruin your security; use it only for testing"; + } + this._defaultParanoia = paranoia; }, @@ -82,7 +125,7 @@ sjcl.random = { i, tmp, t = (new Date()).valueOf(), robin = this._robins[source], - oldReady = this.isReady(), err = 0; + oldReady = this.isReady(), err = 0, objName; id = this._collectorIds[source]; if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; } @@ -100,7 +143,7 @@ sjcl.random = { break; case "object": - var objName = Object.prototype.toString.call(data); + objName = Object.prototype.toString.call(data); if (objName === "[object Uint32Array]") { tmp = []; for (i = 0; i < data.length; i++) { @@ -112,7 +155,7 @@ sjcl.random = { err = 1; } for (i=0; i<data.length && !err; i++) { - if (typeof(data[i]) != "number") { + if (typeof(data[i]) !== "number") { err = 1; } } @@ -197,14 +240,25 @@ sjcl.random = { startCollectors: function () { if (this._collectorsStarted) { return; } + this._eventListener = { + loadTimeCollector: this._bind(this._loadTimeCollector), + mouseCollector: this._bind(this._mouseCollector), + keyboardCollector: this._bind(this._keyboardCollector), + accelerometerCollector: this._bind(this._accelerometerCollector), + touchCollector: this._bind(this._touchCollector) + }; + if (window.addEventListener) { - window.addEventListener("load", this._loadTimeCollector, false); - window.addEventListener("mousemove", this._mouseCollector, false); + window.addEventListener("load", this._eventListener.loadTimeCollector, false); + window.addEventListener("mousemove", this._eventListener.mouseCollector, false); + window.addEventListener("keypress", this._eventListener.keyboardCollector, false); + window.addEventListener("devicemotion", this._eventListener.accelerometerCollector, false); + window.addEventListener("touchmove", this._eventListener.touchCollector, false); } else if (document.attachEvent) { - document.attachEvent("onload", this._loadTimeCollector); - document.attachEvent("onmousemove", this._mouseCollector); - } - else { + document.attachEvent("onload", this._eventListener.loadTimeCollector); + document.attachEvent("onmousemove", this._eventListener.mouseCollector); + document.attachEvent("keypress", this._eventListener.keyboardCollector); + } else { throw new sjcl.exception.bug("can't attach event"); } @@ -216,12 +270,17 @@ sjcl.random = { if (!this._collectorsStarted) { return; } if (window.removeEventListener) { - window.removeEventListener("load", this._loadTimeCollector, false); - window.removeEventListener("mousemove", this._mouseCollector, false); - } else if (window.detachEvent) { - window.detachEvent("onload", this._loadTimeCollector); - window.detachEvent("onmousemove", this._mouseCollector); + window.removeEventListener("load", this._eventListener.loadTimeCollector, false); + window.removeEventListener("mousemove", this._eventListener.mouseCollector, false); + window.removeEventListener("keypress", this._eventListener.keyboardCollector, false); + window.removeEventListener("devicemotion", this._eventListener.accelerometerCollector, false); + window.removeEventListener("touchmove", this._eventListener.touchCollector, false); + } else if (document.detachEvent) { + document.detachEvent("onload", this._eventListener.loadTimeCollector); + document.detachEvent("onmousemove", this._eventListener.mouseCollector); + document.detachEvent("keypress", this._eventListener.keyboardCollector); } + this._collectorsStarted = false; }, @@ -238,56 +297,30 @@ sjcl.random = { /** remove an event listener for progress or seeded-ness */ removeEventListener: function (name, cb) { var i, j, cbs=this._callbacks[name], jsTemp=[]; - + /* 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) { + if (cbs.hasOwnProperty(j) && cbs[j] === cb) { jsTemp.push(j); } } - + for (i=0; i<jsTemp.length; i++) { j = jsTemp[i]; delete cbs[j]; } }, - /* private */ - _pools : [new sjcl.hash.sha256()], - _poolEntropy : [0], - _reseedCount : 0, - _robins : {}, - _eventId : 0, - - _collectorIds : {}, - _collectorIdNext : 0, - - _strength : 0, - _poolStrength : 0, - _nextReseed : 0, - _key : [0,0,0,0,0,0,0,0], - _counter : [0,0,0,0], - _cipher : undefined, - _defaultParanoia : 6, - - /* event listener stuff */ - _collectorsStarted : false, - _callbacks : {progress: {}, seeded: {}}, - _callbackI : 0, - - /* constants */ - _NOT_READY : 0, - _READY : 1, - _REQUIRES_RESEED : 2, + _bind: function (func) { + var that = this; + return function () { + func.apply(that, arguments); + }; + }, - _MAX_WORDS_PER_BURST : 65536, - _PARANOIA_LEVELS : [0,48,64,96,128,192,256,384,512,768,1024], - _MILLISECONDS_PER_RESEED : 30000, - _BITS_PER_RESEED : 80, - /** Generate 4 random words, no reseed, no gate. * @private */ @@ -359,42 +392,131 @@ sjcl.random = { this._reseed(reseedData); }, - _mouseCollector: function (ev) { - var x = ev.x || ev.clientX || ev.offsetX, y = ev.y || ev.clientY || ev.offsetY; - sjcl.random.addEntropy([x,y], 2, "mouse"); + _keyboardCollector: function () { + this._addCurrentTimeToEntropy(1); }, - _loadTimeCollector: function (ev) { - sjcl.random.addEntropy((new Date()).valueOf(), 2, "loadtime"); + _mouseCollector: function (ev) { + var x, y; + + try { + x = ev.x || ev.clientX || ev.offsetX || 0; + y = ev.y || ev.clientY || ev.offsetY || 0; + } catch (err) { + // Event originated from a secure element. No mouse position available. + x = 0; + y = 0; + } + + if (x != 0 && y!= 0) { + sjcl.random.addEntropy([x,y], 2, "mouse"); + } + + this._addCurrentTimeToEntropy(0); + }, + + _touchCollector: function(ev) { + var touch = ev.touches[0] || ev.changedTouches[0]; + var x = touch.pageX || touch.clientX, + y = touch.pageY || touch.clientY; + + sjcl.random.addEntropy([x,y],1,"touch"); + + this._addCurrentTimeToEntropy(0); }, + _loadTimeCollector: function () { + this._addCurrentTimeToEntropy(2); + }, + + _addCurrentTimeToEntropy: function (estimatedEntropy) { + if (typeof window !== 'undefined' && window.performance && typeof window.performance.now === "function") { + //how much entropy do we want to add here? + sjcl.random.addEntropy(window.performance.now(), estimatedEntropy, "loadtime"); + } else { + sjcl.random.addEntropy((new Date()).valueOf(), estimatedEntropy, "loadtime"); + } + }, + _accelerometerCollector: function (ev) { + var ac = ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z; + if(window.orientation){ + var or = window.orientation; + if (typeof or === "number") { + sjcl.random.addEntropy(or, 1, "accelerometer"); + } + } + if (ac) { + sjcl.random.addEntropy(ac, 2, "accelerometer"); + } + this._addCurrentTimeToEntropy(0); + }, + _fireEvent: function (name, arg) { var j, cbs=sjcl.random._callbacks[name], cbsTemp=[]; - /* TODO: there is a race condition between removing collectors and firing them */ + /* TODO: there is a race condition between removing collectors and firing them */ /* 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)) { + if (cbs.hasOwnProperty(j)) { cbsTemp.push(cbs[j]); - } + } } - + for (j=0; j<cbsTemp.length; j++) { - cbsTemp[j](arg); + cbsTemp[j](arg); } } }; +/** an instance for the prng. +* @see sjcl.prng +*/ +sjcl.random = new sjcl.prng(6); + (function(){ + // function for getting nodejs crypto module. catches and ignores errors. + function getCryptoModule() { + try { + return require('crypto'); + } + catch (e) { + return null; + } + } + try { - // get cryptographically strong entropy in Webkit - var ab = new Uint32Array(32); - crypto.getRandomValues(ab); - sjcl.random.addEntropy(ab, 1024, "crypto.getRandomValues"); + var buf, crypt, ab; + + // get cryptographically strong entropy depending on runtime environment + if (typeof module !== 'undefined' && module.exports && (crypt = getCryptoModule()) && crypt.randomBytes) { + buf = crypt.randomBytes(1024/8); + buf = new Uint32Array(new Uint8Array(buf).buffer); + sjcl.random.addEntropy(buf, 1024, "crypto.randomBytes"); + + } else if (typeof window !== 'undefined' && typeof Uint32Array !== 'undefined') { + ab = new Uint32Array(32); + if (window.crypto && window.crypto.getRandomValues) { + window.crypto.getRandomValues(ab); + } else if (window.msCrypto && window.msCrypto.getRandomValues) { + window.msCrypto.getRandomValues(ab); + } else { + return; + } + + // get cryptographically strong entropy in Webkit + sjcl.random.addEntropy(ab, 1024, "crypto.getRandomValues"); + + } else { + // no getRandomValues :-( + } } catch (e) { - // no getRandomValues :-( + if (typeof window !== 'undefined' && window.console) { + console.log("There was an error collecting entropy from the browser:"); + console.log(e); + //we do not want the library to fail due to randomness not being maintained. + } } -})(); +}()); diff --git a/core/ripemd160.js b/core/ripemd160.js new file mode 100644 index 0000000..4ee7b92 --- /dev/null +++ b/core/ripemd160.js @@ -0,0 +1,207 @@ +/** @fileOverview Javascript RIPEMD-160 implementation. + * + * @author Artem S Vybornov <vybornov@gmail.com> + */ +(function() { + +/** + * Context for a RIPEMD-160 operation in progress. + * @constructor + * @class RIPEMD, 160 bits. + */ +sjcl.hash.ripemd160 = function (hash) { + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ +sjcl.hash.ripemd160.hash = function (data) { + return (new sjcl.hash.ripemd160()).update(data).finalize(); +}; + +sjcl.hash.ripemd160.prototype = { + /** + * Reset the hash state. + * @return this + */ + reset: function () { + this._h = _h0.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Reset the hash state. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if ( typeof data === "string" ) + data = sjcl.codec.utf8String.toBits(data); + + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 512+ol & -512; i <= nl; i+= 512) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ + finalize: function () { + var b = sjcl.bitArray.concat( this._buffer, [ sjcl.bitArray.partial(1,1) ] ), + l = ( this._length + 1 ) % 512, + z = ( l > 448 ? 512 : 448 ) - l % 448, + zp = z % 32; + + if ( zp > 0 ) + b = sjcl.bitArray.concat( b, [ sjcl.bitArray.partial(zp,0) ] ) + for ( ; z >= 32; z -= 32 ) + b.push(0); + + b.push( _cvt( this._length | 0 ) ); + b.push( _cvt( Math.floor(this._length / 0x100000000) ) ); + + while ( b.length ) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + var h = this._h; + this.reset(); + + for ( var w = 0; w < 5; ++w ) + h[w] = _cvt(h[w]); + + return h; + } +}; + +var _h0 = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + +var _k1 = [ 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e ]; +var _k2 = [ 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000 ]; +for ( var i = 4; i >= 0; --i ) { + for ( var j = 1; j < 16; ++j ) { + _k1.splice(i,0,_k1[i]); + _k2.splice(i,0,_k2[i]); + } +} + +var _r1 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 ]; +var _r2 = [ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 ]; + +var _s1 = [ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]; +var _s2 = [ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]; + +function _f0(x,y,z) { + return x ^ y ^ z; +}; + +function _f1(x,y,z) { + return (x & y) | (~x & z); +}; + +function _f2(x,y,z) { + return (x | ~y) ^ z; +}; + +function _f3(x,y,z) { + return (x & z) | (y & ~z); +}; + +function _f4(x,y,z) { + return x ^ (y | ~z); +}; + +function _rol(n,l) { + return (n << l) | (n >>> (32-l)); +} + +function _cvt(n) { + return ( (n & 0xff << 0) << 24 ) + | ( (n & 0xff << 8) << 8 ) + | ( (n & 0xff << 16) >>> 8 ) + | ( (n & 0xff << 24) >>> 24 ); +} + +function _block(X) { + var A1 = this._h[0], B1 = this._h[1], C1 = this._h[2], D1 = this._h[3], E1 = this._h[4], + A2 = this._h[0], B2 = this._h[1], C2 = this._h[2], D2 = this._h[3], E2 = this._h[4]; + + var j = 0, T; + + for ( ; j < 16; ++j ) { + T = _rol( A1 + _f0(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f4(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 32; ++j ) { + T = _rol( A1 + _f1(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f3(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 48; ++j ) { + T = _rol( A1 + _f2(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f2(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 64; ++j ) { + T = _rol( A1 + _f3(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f1(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 80; ++j ) { + T = _rol( A1 + _f4(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f0(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + + T = this._h[1] + C1 + D2; + this._h[1] = this._h[2] + D1 + E2; + this._h[2] = this._h[3] + E1 + A2; + this._h[3] = this._h[4] + A1 + B2; + this._h[4] = this._h[0] + B1 + C2; + this._h[0] = T; +} + +})(); diff --git a/core/scrypt.js b/core/scrypt.js new file mode 100644 index 0000000..78dbc9d --- /dev/null +++ b/core/scrypt.js @@ -0,0 +1,146 @@ +/** scrypt Password-Based Key-Derivation Function. + * + * @param {bitArray|String} password The password. + * @param {bitArray|String} salt The salt. Should have lots of entropy. + * + * @param {Number} [N=16384] CPU/Memory cost parameter. + * @param {Number} [r=8] Block size parameter. + * @param {Number} [p=1] Parallelization parameter. + * + * @param {Number} [length] The length of the derived key. Defaults to the + * output size of the hash function. + * @param {Object} [Prff=sjcl.misc.hmac] The pseudorandom function family. + * + * @return {bitArray} The derived key. + */ +sjcl.misc.scrypt = function (password, salt, N, r, p, length, Prff) { + var SIZE_MAX = Math.pow(2, 32) - 1, + self = sjcl.misc.scrypt; + + N = N || 16384; + r = r || 8; + p = p || 1; + + if (r * p >= Math.pow(2, 30)) { + throw sjcl.exception.invalid("The parameters r, p must satisfy r * p < 2^30"); + } + + if ((N < 2) || (N & (N - 1) != 0)) { + throw sjcl.exception.invalid("The parameter N must be a power of 2."); + } + + if (N > SIZE_MAX / 128 / r) { + throw sjcl.exception.invalid("N too big."); + } + + if (r > SIZE_MAX / 128 / p) { + throw sjcl.exception.invalid("r too big."); + } + + var blocks = sjcl.misc.pbkdf2(password, salt, 1, p * 128 * r * 8, Prff), + len = blocks.length / p; + + self.reverse(blocks); + + for (var i = 0; i < p; i++) { + var block = blocks.slice(i * len, (i + 1) * len); + self.blockcopy(self.ROMix(block, N), 0, blocks, i * len); + } + + self.reverse(blocks); + + return sjcl.misc.pbkdf2(password, blocks, 1, length, Prff); +} + +sjcl.misc.scrypt.salsa20Core = function (word, rounds) { + var R = function(a, b) { return (a << b) | (a >>> (32 - b)); } + var x = word.slice(0); + + for (var i = rounds; i > 0; i -= 2) { + x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); + x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); + x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); + x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); + x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); + x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); + x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); + x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); + x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); + x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); + x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); + x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); + x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); + x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); + x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); + x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); + } + + for (i = 0; i < 16; i++) word[i] = x[i]+word[i]; +} + +sjcl.misc.scrypt.blockMix = function(blocks) { + var X = blocks.slice(-16), + out = [], + len = blocks.length / 16, + self = sjcl.misc.scrypt; + + for (var i = 0; i < len; i++) { + self.blockxor(blocks, 16 * i, X, 0, 16); + self.salsa20Core(X, 8); + + if ((i & 1) == 0) { + self.blockcopy(X, 0, out, 8 * i); + } else { + self.blockcopy(X, 0, out, 8 * (i^1 + len)); + } + } + + return out; +} + +sjcl.misc.scrypt.ROMix = function(block, N) { + var X = block.slice(0), + V = [], + self = sjcl.misc.scrypt; + + for (var i = 0; i < N; i++) { + V.push(X.slice(0)); + X = self.blockMix(X); + } + + for (i = 0; i < N; i++) { + var j = X[X.length - 16] & (N - 1); + + self.blockxor(V[j], 0, X, 0); + X = self.blockMix(X); + } + + return X; +} + +sjcl.misc.scrypt.reverse = function (words) { // Converts Big <-> Little Endian words + for (var i in words) { + var out = words[i] & 0xFF; + out = (out << 8) | (words[i] >>> 8) & 0xFF; + out = (out << 8) | (words[i] >>> 16) & 0xFF; + out = (out << 8) | (words[i] >>> 24) & 0xFF; + + words[i] = out; + } +} + +sjcl.misc.scrypt.blockcopy = function (S, Si, D, Di, len) { + var i; + + len = len || (S.length - Si); + + for (i = 0; i < len; i++) D[Di + i] = S[Si + i] | 0; +} + +sjcl.misc.scrypt.blockxor = function(S, Si, D, Di, len) { + var i; + + len = len || (S.length - Si); + + for (i = 0; i < len; i++) D[Di + i] = (D[Di + i] ^ S[Si + i]) | 0; +} diff --git a/core/sha1.js b/core/sha1.js index 73d05b1..708e10b 100644 --- a/core/sha1.js +++ b/core/sha1.js @@ -138,8 +138,7 @@ sjcl.hash.sha1.prototype = { _block:function (words) { var t, tmp, a, b, c, d, e, w = words.slice(0), - h = this._h, - k = this._key; + h = this._h; a = h[0]; b = h[1]; c = h[2]; d = h[3]; e = h[4]; diff --git a/core/sha256.js b/core/sha256.js index fd51609..6ff5294 100644 --- a/core/sha256.js +++ b/core/sha256.js @@ -77,7 +77,7 @@ sjcl.hash.sha256.prototype = { /** * Complete hashing and output the hash value. - * @return {bitArray} The hash value, an array of 16 big-endian words. + * @return {bitArray} The hash value, an array of 8 big-endian words. */ finalize:function () { var i, b = this._buffer, h = this._h; diff --git a/core/sha512.js b/core/sha512.js new file mode 100644 index 0000000..6e628c7 --- /dev/null +++ b/core/sha512.js @@ -0,0 +1,348 @@ +/** @fileOverview Javascript SHA-512 implementation. + * + * This implementation was written for CryptoJS by Jeff Mott and adapted for + * SJCL by Stefan Thomas. + * + * CryptoJS (c) 2009–2012 by Jeff Mott. All rights reserved. + * Released with New BSD License + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + * @author Jeff Mott + * @author Stefan Thomas + */ + +/** + * Context for a SHA-512 operation in progress. + * @constructor + * @class Secure Hash Algorithm, 512 bits. + */ +sjcl.hash.sha512 = function (hash) { + if (!this._key[0]) { this._precompute(); } + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 16 big-endian words. + */ +sjcl.hash.sha512.hash = function (data) { + return (new sjcl.hash.sha512()).update(data).finalize(); +}; + +sjcl.hash.sha512.prototype = { + /** + * The hash's block size, in bits. + * @constant + */ + blockSize: 1024, + + /** + * Reset the hash state. + * @return this + */ + reset:function () { + this._h = this._init.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Input several words to the hash. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if (typeof data === "string") { + data = sjcl.codec.utf8String.toBits(data); + } + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 1024+ol & -1024; i <= nl; i+= 1024) { + this._block(b.splice(0,32)); + } + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 16 big-endian words. + */ + finalize:function () { + var i, b = this._buffer, h = this._h; + + // Round out and push the buffer + b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); + + // Round out the buffer to a multiple of 32 words, less the 4 length words. + for (i = b.length + 4; i & 31; i++) { + b.push(0); + } + + // append the length + b.push(0); + b.push(0); + b.push(Math.floor(this._length / 0x100000000)); + b.push(this._length | 0); + + while (b.length) { + this._block(b.splice(0,32)); + } + + this.reset(); + return h; + }, + + /** + * The SHA-512 initialization vector, to be precomputed. + * @private + */ + _init:[], + + /** + * Least significant 24 bits of SHA512 initialization values. + * + * Javascript only has 53 bits of precision, so we compute the 40 most + * significant bits and add the remaining 24 bits as constants. + * + * @private + */ + _initr: [ 0xbcc908, 0xcaa73b, 0x94f82b, 0x1d36f1, 0xe682d1, 0x3e6c1f, 0x41bd6b, 0x7e2179 ], + + /* + _init: + [0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1, + 0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179], + */ + + /** + * The SHA-512 hash key, to be precomputed. + * @private + */ + _key:[], + + /** + * Least significant 24 bits of SHA512 key values. + * @private + */ + _keyr: + [0x28ae22, 0xef65cd, 0x4d3b2f, 0x89dbbc, 0x48b538, 0x05d019, 0x194f9b, 0x6d8118, + 0x030242, 0x706fbe, 0xe4b28c, 0xffb4e2, 0x7b896f, 0x1696b1, 0xc71235, 0x692694, + 0xf14ad2, 0x4f25e3, 0x8cd5b5, 0xac9c65, 0x2b0275, 0xa6e483, 0x41fbd4, 0x1153b5, + 0x66dfab, 0xb43210, 0xfb213f, 0xef0ee4, 0xa88fc2, 0x0aa725, 0x03826f, 0x0e6e70, + 0xd22ffc, 0x26c926, 0xc42aed, 0x95b3df, 0xaf63de, 0x77b2a8, 0xedaee6, 0x82353b, + 0xf10364, 0x423001, 0xf89791, 0x54be30, 0xef5218, 0x65a910, 0x71202a, 0xbbd1b8, + 0xd2d0c8, 0x41ab53, 0x8eeb99, 0x9b48a8, 0xc95a63, 0x418acb, 0x63e373, 0xb2b8a3, + 0xefb2fc, 0x172f60, 0xf0ab72, 0x6439ec, 0x631e28, 0x82bde9, 0xc67915, 0x72532b, + 0x26619c, 0xc0c207, 0xe0eb1e, 0x6ed178, 0x176fba, 0xc898a6, 0xf90dae, 0x1c471b, + 0x047d84, 0xc72493, 0xc9bebc, 0x100d4c, 0x3e42b6, 0x657e2a, 0xd6faec, 0x475817], + + /* + _key: + [0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817], + */ + + /** + * Function to precompute _init and _key. + * @private + */ + _precompute: function () { + // XXX: This code is for precomputing the SHA256 constants, change for + // SHA512 and re-enable. + var i = 0, prime = 2, factor; + + function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; } + function frac2(x) { return (x-Math.floor(x)) * 0x10000000000 & 0xff; } + + outer: for (; i<80; prime++) { + for (factor=2; factor*factor <= prime; factor++) { + if (prime % factor === 0) { + // not a prime + continue outer; + } + } + + if (i<8) { + this._init[i*2] = frac(Math.pow(prime, 1/2)); + this._init[i*2+1] = (frac2(Math.pow(prime, 1/2)) << 24) | this._initr[i]; + } + this._key[i*2] = frac(Math.pow(prime, 1/3)); + this._key[i*2+1] = (frac2(Math.pow(prime, 1/3)) << 24) | this._keyr[i]; + i++; + } + }, + + /** + * Perform one cycle of SHA-512. + * @param {bitArray} words one block of words. + * @private + */ + _block:function (words) { + var i, wrh, wrl, + w = words.slice(0), + h = this._h, + k = this._key, + h0h = h[ 0], h0l = h[ 1], h1h = h[ 2], h1l = h[ 3], + h2h = h[ 4], h2l = h[ 5], h3h = h[ 6], h3l = h[ 7], + h4h = h[ 8], h4l = h[ 9], h5h = h[10], h5l = h[11], + h6h = h[12], h6l = h[13], h7h = h[14], h7l = h[15]; + + // Working variables + var ah = h0h, al = h0l, bh = h1h, bl = h1l, + ch = h2h, cl = h2l, dh = h3h, dl = h3l, + eh = h4h, el = h4l, fh = h5h, fl = h5l, + gh = h6h, gl = h6l, hh = h7h, hl = h7l; + + for (i=0; i<80; i++) { + // load up the input word for this round + if (i<16) { + wrh = w[i * 2]; + wrl = w[i * 2 + 1]; + } else { + // Gamma0 + var gamma0xh = w[(i-15) * 2]; + var gamma0xl = w[(i-15) * 2 + 1]; + var gamma0h = + ((gamma0xl << 31) | (gamma0xh >>> 1)) ^ + ((gamma0xl << 24) | (gamma0xh >>> 8)) ^ + (gamma0xh >>> 7); + var gamma0l = + ((gamma0xh << 31) | (gamma0xl >>> 1)) ^ + ((gamma0xh << 24) | (gamma0xl >>> 8)) ^ + ((gamma0xh << 25) | (gamma0xl >>> 7)); + + // Gamma1 + var gamma1xh = w[(i-2) * 2]; + var gamma1xl = w[(i-2) * 2 + 1]; + var gamma1h = + ((gamma1xl << 13) | (gamma1xh >>> 19)) ^ + ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ + (gamma1xh >>> 6); + var gamma1l = + ((gamma1xh << 13) | (gamma1xl >>> 19)) ^ + ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ + ((gamma1xh << 26) | (gamma1xl >>> 6)); + + // Shortcuts + var wr7h = w[(i-7) * 2]; + var wr7l = w[(i-7) * 2 + 1]; + + var wr16h = w[(i-16) * 2]; + var wr16l = w[(i-16) * 2 + 1]; + + // W(round) = gamma0 + W(round - 7) + gamma1 + W(round - 16) + wrl = gamma0l + wr7l; + wrh = gamma0h + wr7h + ((wrl >>> 0) < (gamma0l >>> 0) ? 1 : 0); + wrl += gamma1l; + wrh += gamma1h + ((wrl >>> 0) < (gamma1l >>> 0) ? 1 : 0); + wrl += wr16l; + wrh += wr16h + ((wrl >>> 0) < (wr16l >>> 0) ? 1 : 0); + } + + w[i*2] = wrh |= 0; + w[i*2 + 1] = wrl |= 0; + + // Ch + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + + // Maj + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + // Sigma0 + var sigma0h = ((al << 4) | (ah >>> 28)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((ah << 4) | (al >>> 28)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + + // Sigma1 + var sigma1h = ((el << 18) | (eh >>> 14)) ^ ((el << 14) | (eh >>> 18)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((eh << 18) | (el >>> 14)) ^ ((eh << 14) | (el >>> 18)) ^ ((el << 23) | (eh >>> 9)); + + // K(round) + var krh = k[i*2]; + var krl = k[i*2+1]; + + // t1 = h + sigma1 + ch + K(round) + W(round) + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + t1l += chl; + t1h += chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + t1l += krl; + t1h += krh + ((t1l >>> 0) < (krl >>> 0) ? 1 : 0); + t1l = t1l + wrl|0; // FF32..FF34 perf issue https://bugzilla.mozilla.org/show_bug.cgi?id=1054972 + t1h += wrh + ((t1l >>> 0) < (wrl >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash + h0l = h[1] = (h0l + al) | 0; + h[0] = (h0h + ah + ((h0l >>> 0) < (al >>> 0) ? 1 : 0)) | 0; + h1l = h[3] = (h1l + bl) | 0; + h[2] = (h1h + bh + ((h1l >>> 0) < (bl >>> 0) ? 1 : 0)) | 0; + h2l = h[5] = (h2l + cl) | 0; + h[4] = (h2h + ch + ((h2l >>> 0) < (cl >>> 0) ? 1 : 0)) | 0; + h3l = h[7] = (h3l + dl) | 0; + h[6] = (h3h + dh + ((h3l >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + h4l = h[9] = (h4l + el) | 0; + h[8] = (h4h + eh + ((h4l >>> 0) < (el >>> 0) ? 1 : 0)) | 0; + h5l = h[11] = (h5l + fl) | 0; + h[10] = (h5h + fh + ((h5l >>> 0) < (fl >>> 0) ? 1 : 0)) | 0; + h6l = h[13] = (h6l + gl) | 0; + h[12] = (h6h + gh + ((h6l >>> 0) < (gl >>> 0) ? 1 : 0)) | 0; + h7l = h[15] = (h7l + hl) | 0; + h[14] = (h7h + hh + ((h7l >>> 0) < (hl >>> 0) ? 1 : 0)) | 0; + } +}; + + diff --git a/core/sjcl.js b/core/sjcl.js index 50e5307..8c9425d 100644 --- a/core/sjcl.js +++ b/core/sjcl.js @@ -10,7 +10,7 @@ "use strict"; /*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */ -/*global document, window, escape, unescape */ +/*global document, window, escape, unescape, module, require, Uint32Array */ /** @namespace The Stanford Javascript Crypto Library, top-level namespace. */ var sjcl = { @@ -42,25 +42,25 @@ var sjcl = { /** @namespace Exceptions. */ exception: { - /** @class Ciphertext is corrupt. */ + /** @constructor Ciphertext is corrupt. */ corrupt: function(message) { this.toString = function() { return "CORRUPT: "+this.message; }; this.message = message; }, - /** @class Invalid parameter. */ + /** @constructor Invalid parameter. */ invalid: function(message) { this.toString = function() { return "INVALID: "+this.message; }; this.message = message; }, - /** @class Bug or missing feature in SJCL. */ + /** @constructor Bug or missing feature in SJCL. @constructor */ bug: function(message) { this.toString = function() { return "BUG: "+this.message; }; this.message = message; }, - /** @class Something isn't ready. */ + /** @constructor Something isn't ready. */ notReady: function(message) { this.toString = function() { return "NOT READY: "+this.message; }; this.message = message; @@ -68,6 +68,11 @@ var sjcl = { } }; -if(typeof module != 'undefined' && module.exports){ +if(typeof module !== 'undefined' && module.exports){ module.exports = sjcl; } +if (typeof define === "function") { + define([], function () { + return sjcl; + }); +} diff --git a/core/srp.js b/core/srp.js index d1dba28..1f92d57 100644 --- a/core/srp.js +++ b/core/srp.js @@ -16,7 +16,7 @@ */ sjcl.keyexchange.srp = { /** - * Calculates SRP v, the verifier. + * Calculates SRP v, the verifier. * v = g^x mod N [RFC 5054] * @param {String} I The username. * @param {String} P The password. @@ -27,7 +27,7 @@ sjcl.keyexchange.srp = { */ makeVerifier: function(I, P, s, group) { var x; - x = this.makeX(I, P, s); + x = sjcl.keyexchange.srp.makeX(I, P, s); x = sjcl.bn.fromBits(x); return group.g.powermod(x, group.N); }, @@ -52,8 +52,8 @@ sjcl.keyexchange.srp = { */ knownGroup:function(i) { if (typeof i !== "string") { i = i.toString(); } - if (!this._didInitKnownGroups) { this._initKnownGroups(); } - return this._knownGroups[i]; + if (!sjcl.keyexchange.srp._didInitKnownGroups) { sjcl.keyexchange.srp._initKnownGroups(); } + return sjcl.keyexchange.srp._knownGroups[i]; }, /** @@ -63,16 +63,16 @@ sjcl.keyexchange.srp = { _didInitKnownGroups: false, _initKnownGroups:function() { var i, size, group; - for (i=0; i < this._knownGroupSizes.length; i++) { - size = this._knownGroupSizes[i].toString(); - group = this._knownGroups[size]; + for (i=0; i < sjcl.keyexchange.srp._knownGroupSizes.length; i++) { + size = sjcl.keyexchange.srp._knownGroupSizes[i].toString(); + group = sjcl.keyexchange.srp._knownGroups[size]; group.N = new sjcl.bn(group.N); group.g = new sjcl.bn(group.g); } - this._didInitKnownGroups = true; + sjcl.keyexchange.srp._didInitKnownGroups = true; }, - _knownGroupSizes: [1024, 1536, 2048], + _knownGroupSizes: [1024, 1536, 2048, 3072, 4096, 6144, 8192], _knownGroups: { 1024: { N: "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" + @@ -106,8 +106,121 @@ sjcl.keyexchange.srp = { "94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" + "9E4AFF73", g: 2 + }, + + 3072: { + N: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + g: 5 + }, + + 4096: { + N: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF", + g: 5 + }, + + 6144: { + N: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DCC4024FFFFFFFFFFFFFFFF", + g: 5 + }, + + 8192: { + N:"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" + + "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" + + "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" + + "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" + + "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" + + "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" + + "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" + + "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + g: 19 } } }; - |