From b72b329abb890eec65a6867f1372d476d28b139c Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Thu, 4 Apr 2013 16:39:13 +0200 Subject: [PATCH] updated sjcl and tested ecc --- src/js/crypto/util.js | 1 + src/lib/sjcl/bn.js | 28 +++++++++++--- src/lib/sjcl/convenience.js | 8 +++- src/lib/sjcl/ecc.js | 60 ++++++++++++++++++++++------- src/lib/sjcl/random.js | 76 ++++++++++++++++++++----------------- src/lib/sjcl/sjcl.js | 8 ++-- src/lib/sjcl/srp.js | 14 +++---- src/test/ecc-test.js | 21 ---------- src/test/unit/ecc-test.js | 34 +++++++++++++++++ src/test/unit/index.html | 4 ++ 10 files changed, 167 insertions(+), 87 deletions(-) delete mode 100644 src/test/ecc-test.js create mode 100644 src/test/unit/ecc-test.js diff --git a/src/js/crypto/util.js b/src/js/crypto/util.js index 7791ac2..c9f141e 100644 --- a/src/js/crypto/util.js +++ b/src/js/crypto/util.js @@ -24,6 +24,7 @@ app.crypto.Util = function(window, uuid) { window.crypto.getRandomValues(keyBuf); keyBase64 = window.btoa(this.uint8Arr2BinStr(keyBuf)); } else { + // add an additional peace of entropy to the pot and stir with the sjcl prng sjcl.random.addEntropy((new Date()).valueOf(), 2, "calltime"); keyBuf = sjcl.random.randomWords(keySize / 32, 0); keyBase64 = sjcl.codec.base64.fromBits(keyBuf); diff --git a/src/lib/sjcl/bn.js b/src/lib/sjcl/bn.js index 672fd08..d5a6b63 100644 --- a/src/lib/sjcl/bn.js +++ b/src/lib/sjcl/bn.js @@ -1,4 +1,5 @@ /** + * @constructor * Constructs a new bignum from another bignum, a number or a hex string. */ sjcl.bn = function(it) { @@ -156,12 +157,19 @@ sjcl.bn.prototype = { }, 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)) { @@ -340,11 +348,12 @@ sjcl.bn.prototype = { /** 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; }, @@ -354,13 +363,14 @@ sjcl.bn.prototype = { 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; } }; +/** @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; @@ -384,6 +394,7 @@ 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 */ function p(it) { this.initWith(it); /*if (this.limbs[this.modOffset]) { @@ -415,7 +426,9 @@ 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. + * @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; @@ -439,6 +452,7 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { return this; }; + /** @this {sjcl.bn} */ ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() { var limbs = this.limbs, i = limbs.length - 1, k, l; this.reduce(); @@ -452,7 +466,9 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { } }; - /** mostly constant-time, very expensive full reduction. */ + /** mostly constant-time, very expensive full reduction. + * @this {sjcl.bn} + */ ppr.fullReduce = function() { var greater, i; // massively above the modulus, may be negative @@ -484,6 +500,8 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { return this; }; + + /** @this {sjcl.bn} */ ppr.inverse = function() { return (this.power(this.modulus.sub(2))); }; diff --git a/src/lib/sjcl/convenience.js b/src/lib/sjcl/convenience.js index a1b15a6..dca0cef 100644 --- a/src/lib/sjcl/convenience.js +++ b/src/lib/sjcl/convenience.js @@ -46,6 +46,10 @@ 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); @@ -101,6 +105,8 @@ 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); @@ -130,7 +136,7 @@ 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]) { diff --git a/src/lib/sjcl/ecc.js b/src/lib/sjcl/ecc.js index 504288f..b36e722 100644 --- a/src/lib/sjcl/ecc.js +++ b/src/lib/sjcl/ecc.js @@ -296,21 +296,38 @@ sjcl.ecc.curves = { /* Diffie-Hellman-like public-key system */ sjcl.ecc._dh = function(cn) { sjcl.ecc[cn] = { + /** @constructor */ 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; } + + 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 }; + } }, + /** @constructor */ secretKey: function(curve, exponent) { this._curve = curve; + this._curveBitLength = curve.r.bitLength(); this._exponent = exponent; + + this.get = function() { + return this._exponent.toBits(); + } }, - generateKeys: function(curve, paranoia) { + /** @constructor */ + generateKeys: function(curve, paranoia, sec) { if (curve === undefined) { curve = 256; } @@ -320,7 +337,10 @@ sjcl.ecc._dh = function(cn) { throw new sjcl.exception.invalid("no such curve"); } } - var sec = sjcl.bn.random(curve.r, paranoia), pub = curve.G.mult(sec); + if (sec === undefined) { + var 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) }; } @@ -351,29 +371,41 @@ sjcl.ecc.elGamal.secretKey.prototype = { sjcl.ecc._dh("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); + 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)); } }; sjcl.ecc.ecdsa.publicKey.prototype = { - verify: function(hash, rs) { + 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; } diff --git a/src/lib/sjcl/random.js b/src/lib/sjcl/random.js index 6facb1b..e8de144 100644 --- a/src/lib/sjcl/random.js +++ b/src/lib/sjcl/random.js @@ -5,7 +5,8 @@ * @author Dan Boneh */ -/** @namespace Random number generator +/** @constructor + * @class Random number generator * * @description *

@@ -39,7 +40,43 @@ * look for improvements in future versions. *

*/ -sjcl.random = { +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 * @param {Number} nwords The number of words to generate. */ @@ -255,39 +292,6 @@ sjcl.random = { } }, - /* 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, - - _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 */ @@ -388,6 +392,8 @@ sjcl.random = { } }; +sjcl.random = new sjcl.prng(6); + (function(){ try { // get cryptographically strong entropy in Webkit diff --git a/src/lib/sjcl/sjcl.js b/src/lib/sjcl/sjcl.js index 50e5307..d2da8e9 100644 --- a/src/lib/sjcl/sjcl.js +++ b/src/lib/sjcl/sjcl.js @@ -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; diff --git a/src/lib/sjcl/srp.js b/src/lib/sjcl/srp.js index d1dba28..1b5a4b8 100644 --- a/src/lib/sjcl/srp.js +++ b/src/lib/sjcl/srp.js @@ -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,13 +63,13 @@ 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], diff --git a/src/test/ecc-test.js b/src/test/ecc-test.js deleted file mode 100644 index dc32b55..0000000 --- a/src/test/ecc-test.js +++ /dev/null @@ -1,21 +0,0 @@ -module("ECC Crypto"); - -var keys, - ciphertext, - plaintext = 'Hello, World!'; - -test("Generate Keys", function() { - // generate keypair - keys = sjcl.ecc.elGamal.generateKeys(384, 1); - ok(keys); -}); - -test("Encrypt", function() { - ciphertext = sjcl.encrypt(keys.pub, plaintext); - ok(ciphertext); -}); - -test("Decrypt", function() { - var decrypted = sjcl.decrypt(keys.sec, ciphertext); - equal(plaintext, decrypted); -}); \ No newline at end of file diff --git a/src/test/unit/ecc-test.js b/src/test/unit/ecc-test.js new file mode 100644 index 0000000..99c1c17 --- /dev/null +++ b/src/test/unit/ecc-test.js @@ -0,0 +1,34 @@ +module("ECC Crypto"); + +var ecc_test = { + keySize: 384, + plaintext: 'Hello, World!' +}; + +test("Generate Keys", function() { + // generate keypair + ecc_test.keys = sjcl.ecc.elGamal.generateKeys(ecc_test.keySize, 0); + ok(ecc_test.keys); +}); + +test("Encrypt", function() { + // var tmp = ecc_test.keys.pub.kem(0); + + // var password = tmp.key.slice(0, ecc_test.keySize / 32); + // var prp = new sjcl.cipher.ecc(password); + + + // var iv = aes_test.util.random(ecc_test.keySize); + // var ivWords = sjcl.codec.base64.toBits(iv); + + // sjcl.mode.ecc.encrypt(prp, ecc_test.plaintext, ivWords); + + sjcl.random.setDefaultParanoia(0); + ecc_test.ciphertext = sjcl.encrypt(ecc_test.keys.pub, ecc_test.plaintext); + ok(ecc_test.ciphertext); +}); + +test("Decrypt", function() { + var decrypted = sjcl.decrypt(ecc_test.keys.sec, ecc_test.ciphertext); + equal(ecc_test.plaintext, decrypted); +}); \ No newline at end of file diff --git a/src/test/unit/index.html b/src/test/unit/index.html index 1769833..820215d 100644 --- a/src/test/unit/index.html +++ b/src/test/unit/index.html @@ -40,6 +40,9 @@ + + + @@ -71,6 +74,7 @@ +