From 514276afc8cb8d0a19a4945cb846695371521dce Mon Sep 17 00:00:00 2001 From: Mike Hamburg Date: Mon, 23 May 2011 13:56:45 -0700 Subject: [PATCH] restore ecc, cbc --- Makefile | 4 +- configure | 4 +- core/cbc.js | 115 +++++++++++ core/ecc.js | 380 +++++++++++++++++++++++++++++++++++ test/cbc_test.js | 33 +++ test/cbc_vectors.js | 413 ++++++++++++++++++++++++++++++++++++++ test/ecdh_test.js | 18 ++ test/ecdsa_test.js | 29 +++ test/run_tests_browser.js | 4 + 9 files changed, 998 insertions(+), 2 deletions(-) create mode 100644 core/cbc.js create mode 100644 core/ecc.js create mode 100644 test/cbc_test.js create mode 100644 test/cbc_vectors.js create mode 100644 test/ecdh_test.js create mode 100644 test/ecdsa_test.js diff --git a/Makefile b/Makefile index 56a439a2..5f7ab8d4 100644 --- a/Makefile +++ b/Makefile @@ -60,12 +60,14 @@ TEST_SCRIPTS= $(TEST_COMMON) \ test/aes_vectors.js test/aes_test.js \ test/ocb2_vectors.js test/ocb2_test.js \ test/ccm_vectors.js test/ccm_test.js \ + test/cbc_vectors.js test/cbc_test.js \ test/sha256_vectors.js test/sha256_test.js \ test/sha256_test_brute_force.js \ test/sha1_vectors.js test/sha1_test.js \ test/hmac_vectors.js test/hmac_test.js \ test/pbkdf2_test.js \ - test/bn_vectors.js test/bn_test.js + test/bn_vectors.js test/bn_test.js \ + test/ecdsa_test.js test/ecdh_test.js TEST_SCRIPTS_OPT= $(TEST_COMMON) \ test/srp_vectors.js test/srp_test.js diff --git a/configure b/configure index b8e5e297..3222da23 100755 --- a/configure +++ b/configure @@ -4,7 +4,7 @@ use strict; my ($arg, $i, $j, $targ); -my @targets = qw/sjcl aes bitArray codecString codecHex codecBase64 codecBytes bn sha256 sha1 ccm ocb2 hmac pbkdf2 srp random convenience/; +my @targets = qw/sjcl aes bitArray codecString codecHex codecBase64 codecBytes bn sha256 sha1 ccm cbc ecc ocb2 hmac pbkdf2 srp random convenience/; my %deps = ('aes'=>'sjcl', 'bitArray'=>'sjcl', 'codecString'=>'bitArray', @@ -19,6 +19,8 @@ my %deps = ('aes'=>'sjcl', 'pbkdf2'=>'hmac', 'srp'=>'sha1,bn,bitArray', 'bn'=>'bitArray,random', + 'ecc'=>'bn', + 'cbc'=>'bitArray,aes', 'random'=>'sha256,aes', 'convenience'=>'ccm,pbkdf2,random'); diff --git a/core/cbc.js b/core/cbc.js new file mode 100644 index 00000000..9a98ed8f --- /dev/null +++ b/core/cbc.js @@ -0,0 +1,115 @@ +/** @fileOverview CBC mode implementation + * + * @author Emily Stark + * @author Mike Hamburg + * @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() { + sjcl.mode.cbc = { + /** The name of the mode. + * @constant + */ + name: "cbc", + + /** Encrypt in CBC mode with PKCS#5 padding. + * @param {Object} prp The block cipher. 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. Must be empty. + * @return The encrypted data, an array of bytes. + * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified. + */ + encrypt: function(prp, plaintext, iv, adata) { + if (adata && adata.length) { + throw new sjcl.exception.invalid("cbc can't authenticate data"); + } + if (sjcl.bitArray.bitLength(iv) !== 128) { + throw new sjcl.exception.invalid("cbc iv must be 128 bits"); + } + var i, + w = sjcl.bitArray, + xor = w._xor4, + bl = w.bitLength(plaintext), + bp = 0, + output = []; + + if (bl&7) { + throw new sjcl.exception.invalid("pkcs#5 padding only works for multiples of a byte"); + } + + for (i=0; bp+128 <= bl; i+=4, bp+=128) { + /* Encrypt a non-final block */ + iv = prp.encrypt(xor(iv, plaintext.slice(i,i+4))); + output.splice(i,0,iv[0],iv[1],iv[2],iv[3]); + } + + /* Construct the pad. */ + bl = (16 - ((bl >> 3) & 15)) * 0x1010101; + + /* Pad and encrypt. */ + iv = prp.encrypt(xor(iv,w.concat(plaintext,[bl,bl,bl,bl]).slice(i,i+4))); + output.splice(i,0,iv[0],iv[1],iv[2],iv[3]); + return output; + }, + + /** Decrypt in CBC mode. + * @param {Object} prp The block cipher. 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. It must be empty. + * @return The decrypted data, an array of bytes. + * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified. + * @throws {sjcl.exception.corrupt} if if the message is corrupt. + */ + decrypt: function(prp, ciphertext, iv, adata) { + if (adata && adata.length) { + throw new sjcl.exception.invalid("cbc can't authenticate data"); + } + if (sjcl.bitArray.bitLength(iv) !== 128) { + throw new sjcl.exception.invalid("cbc iv must be 128 bits"); + } + if ((sjcl.bitArray.bitLength(ciphertext) & 127) || !ciphertext.length) { + throw new sjcl.exception.corrupt("cbc ciphertext must be a positive multiple of the block size"); + } + var i, + w = sjcl.bitArray, + xor = w._xor4, + bi, bo, + output = []; + + adata = adata || []; + + for (i=0; i 16) { + throw new sjcl.exception.corrupt("pkcs#5 padding corrupt"); + } + bo = bi * 0x1010101; + if (!w.equal(w.bitSlice([bo,bo,bo,bo], 0, bi*8), + w.bitSlice(output, output.length*32 - bi*8, output.length*32))) { + throw new sjcl.exception.corrupt("pkcs#5 padding corrupt"); + } + + return w.bitSlice(output, 0, output.length*32 - bi*8); + } + }; +}; diff --git a/core/ecc.js b/core/ecc.js new file mode 100644 index 00000000..504288fd --- /dev/null +++ b/core/ecc.js @@ -0,0 +1,380 @@ +sjcl.ecc = {}; + +/** + * Represents a point on a curve in affine coordinates. + * @constructor + * @param {sjcl.ecc.curve} curve The curve that this point lies on. + * @param {bigInt} x The x coordinate. + * @param {bigInt} y The y coordinate. + */ +sjcl.ecc.point = function(curve,x,y) { + if (x === undefined) { + this.isIdentity = true; + } else { + this.x = x; + this.y = y; + this.isIdentity = false; + } + this.curve = curve; +}; + + + +sjcl.ecc.point.prototype = { + toJac: function() { + return new sjcl.ecc.pointJac(this.curve, this.x, this.y, new this.curve.field(1)); + }, + + 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. + * @param {bigInt} k2 The coefficient to multiply affine2 this by. + * @param {sjcl.ecc.point} affine The other point in affine coordinates. + * @return {sjcl.ecc.pointJac} The result of the multiplication and addition, in Jacobian coordinates. + */ + mult2: function(k, k2, affine2) { + return this.toJac().mult2(k, this, k2, affine2).toAffine(); + }, + + multiples: function() { + var m, i, j; + if (this._multiples === undefined) { + j = this.toJac().doubl(); + m = this._multiples = [new sjcl.ecc.point(this.curve), this, j.toAffine()]; + for (i=3; i<16; i++) { + j = j.add(this); + m.push(j.toAffine()); + } + } + return this._multiples; + }, + + isValid: function() { + return this.y.square().equals(this.curve.b.add(this.x.mul(this.curve.a.add(this.x.square())))); + }, + + toBits: function() { + return sjcl.bitArray.concat(this.x.toBits(), this.y.toBits()); + } +}; + +/** + * Represents a point on a curve in Jacobian coordinates. Coordinates can be specified as bigInts or strings (which + * will be converted to bigInts). + * + * @constructor + * @param {bigInt/string} x The x coordinate. + * @param {bigInt/string} y The y coordinate. + * @param {bigInt/string} z The z coordinate. + * @param {sjcl.ecc.curve} curve The curve that this point lies on. + */ +sjcl.ecc.pointJac = function(curve, x, y, z) { + if (x === undefined) { + this.isIdentity = true; + } else { + this.x = x; + this.y = y; + this.z = z; + this.isIdentity = false; + } + this.curve = curve; +}; + +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. + */ + add: function(T) { + var S = this, sz2, c, d, c2, x1, x2, x, y1, y2, y, z; + if (S.curve !== T.curve) { + throw("sjcl.ecc.add(): Points must be on the same curve to add them!"); + } + + if (S.isIdentity) { + return T.toJac(); + } else if (T.isIdentity) { + return S; + } + + sz2 = S.z.square(); + c = T.x.mul(sz2).subM(S.x); + + if (c.equals(0)) { + if (S.y.equals(T.y.mul(sz2.mul(S.z)))) { + // same point + return S.doubl(); + } else { + // inverses + return new sjcl.ecc.pointJac(S.curve); + } + } + + d = T.y.mul(sz2.mul(S.z)).subM(S.y); + c2 = c.square(); + + x1 = d.square(); + x2 = c.square().mul(c).addM( S.x.add(S.x).mul(c2) ); + x = x1.subM(x2); + + y1 = S.x.mul(c2).subM(x).mul(d); + y2 = S.y.mul(c.square().mul(c)); + y = y1.subM(y2); + + z = S.z.mul(c); + + return new sjcl.ecc.pointJac(this.curve,x,y,z); + }, + + /** + * doubles this point. + * @return {sjcl.ecc.pointJac} The doubled point. + */ + doubl: function() { + if (this.isIdentity) { return this; } + + var + y2 = this.y.square(), + 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)), + x = c.square().subM(a).subM(a), + y = a.sub(x).mul(c).subM(b), + z = this.y.add(this.y).mul(this.z); + return new sjcl.ecc.pointJac(this.curve, x, y, z); + }, + + /** + * 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); + } + 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. + * @param {sjcl.ecc.point} affine This point in affine coordinates. + * @return {sjcl.ecc.pointJac} The result of the multiplication, in Jacobian coordinates. + */ + mult: function(k, affine) { + if (typeof(k) === "number") { + k = [k]; + } 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--) { + for (j=sjcl.bn.prototype.radix-4; j>=0; j-=4) { + 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. + * @param {sjcl.ecc.point} affine This point in affine coordinates. + * @param {bigInt} k2 The coefficient to multiply affine2 this by. + * @param {sjcl.ecc.point} affine The other point in affine coordinates. + * @return {sjcl.ecc.pointJac} The result of the multiplication and addition, in Jacobian coordinates. + */ + mult2: function(k1, affine, k2, affine2) { + if (typeof(k1) === "number") { + k1 = [k1]; + } 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; + + for (i=Math.max(k1.length,k2.length)-1; i>=0; i--) { + l1 = k1[i] | 0; + l2 = k2[i] | 0; + for (j=sjcl.bn.prototype.radix-4; j>=0; j-=4) { + out = out.doubl().doubl().doubl().doubl().add(m1[l1>>j & 0xF]).add(m2[l2>>j & 0xF]); + } + } + + return out; + }, + + isValid: function() { + var z2 = this.z.square(), z4 = z2.square(), z6 = z4.mul(z2); + return this.y.square().equals( + this.curve.b.mul(z6).add(this.x.mul( + this.curve.a.mul(z4).add(this.x.square())))); + } +}; + +/** + * Construct an elliptic curve. Most users will not use this and instead start with one of the NIST curves defined below. + * + * @constructor + * @param {bigInt} p The prime modulus. + * @param {bigInt} r The prime order of the curve. + * @param {bigInt} a The constant a in the equation of the curve y^2 = x^3 + ax + b (for NIST curves, a is always -3). + * @param {bigInt} x The x coordinate of a base point of the curve. + * @param {bigInt} y The y coordinate of a base point of the curve. + */ +sjcl.ecc.curve = function(Field, r, a, b, x, y) { + this.field = Field; + this.r = Field.prototype.modulus.sub(r); + this.a = new Field(a); + this.b = new Field(b); + this.G = new sjcl.ecc.point(this, new Field(x), new Field(y)); +}; + +sjcl.ecc.curve.prototype.fromBits = function (bits) { + var w = sjcl.bitArray, l = this.field.prototype.exponent + 7 & -8, + p = new sjcl.ecc.point(this, this.field.fromBits(w.bitSlice(bits, 0, l)), + this.field.fromBits(w.bitSlice(bits, l, 2*l))); + if (!p.isValid()) { + throw new sjcl.exception.corrupt("not on the curve!"); + } + return p; +}; + +sjcl.ecc.curves = { + c192: new sjcl.ecc.curve( + sjcl.bn.prime.p192, + "0x662107c8eb94364e4b2dd7ce", + -3, + "0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", + "0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012", + "0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811"), + + c224: new sjcl.ecc.curve( + sjcl.bn.prime.p224, + "0xe95c1f470fc1ec22d6baa3a3d5c4", + -3, + "0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4", + "0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21", + "0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"), + + c256: new sjcl.ecc.curve( + sjcl.bn.prime.p256, + "0x4319055358e8617b0c46353d039cdaae", + -3, + "0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", + "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"), + + c384: new sjcl.ecc.curve( + sjcl.bn.prime.p384, + "0x389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68c", + -3, + "0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef", + "0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", + "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f") +}; + + +/* 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; + } + }, + + secretKey: function(curve, exponent) { + this._curve = curve; + this._exponent = exponent; + }, + + generateKeys: function(curve, paranoia) { + 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"); + } + } + 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) }; + } + }; +}; + +sjcl.ecc._dh("elGamal"); + +sjcl.ecc.elGamal.publicKey.prototype = { + kem: function(paranoia) { + var sec = sjcl.bn.random(this._curve.r, paranoia), + tag = this._curve.G.mult(sec).toBits(), + key = sjcl.hash.sha256.hash(this._point.mult(sec).toBits()); + return { key: key, tag: tag }; + } +}; + +sjcl.ecc.elGamal.secretKey.prototype = { + unkem: function(tag) { + return sjcl.hash.sha256.hash(this._curve.fromBits(tag).mult(this._exponent).toBits()); + }, + + dh: function(pk) { + return sjcl.hash.sha256.hash(pk._point.mult(this._exponent).toBits()); + } +}; + +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); + return sjcl.bitArray.concat(r.toBits(l), s.toBits(l)); + } +}; + +sjcl.ecc.ecdsa.publicKey.prototype = { + verify: function(hash, rs) { + var w = sjcl.bitArray, + R = this._curve.r, + l = R.bitLength(), + r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)), + s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)), + 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")); + } + return true; + } +}; diff --git a/test/cbc_test.js b/test/cbc_test.js new file mode 100644 index 00000000..b57bb8cc --- /dev/null +++ b/test/cbc_test.js @@ -0,0 +1,33 @@ +new sjcl.test.TestCase("CBC mode tests", function (cb) { + ((sjcl.beware && + sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]) || + function(){})(); + + if (!sjcl.cipher.aes || !sjcl.mode.cbc) { + this.unimplemented(); + cb && cb(); + return; + } + + var i, kat = sjcl.test.vector.cbc, tv, iv, ct, aes, len, thiz=this, w=sjcl.bitArray, pt, h=sjcl.codec.hex; + browserUtil.cpsIterate(function (j, cbb) { + for (i=100*j; i