updated sjcl and tested ecc

This commit is contained in:
Tankred Hase 2013-04-04 16:39:13 +02:00
parent ef54dc3aae
commit b72b329abb
10 changed files with 167 additions and 87 deletions

View File

@ -24,6 +24,7 @@ app.crypto.Util = function(window, uuid) {
window.crypto.getRandomValues(keyBuf); window.crypto.getRandomValues(keyBuf);
keyBase64 = window.btoa(this.uint8Arr2BinStr(keyBuf)); keyBase64 = window.btoa(this.uint8Arr2BinStr(keyBuf));
} else { } else {
// add an additional peace of entropy to the pot and stir with the sjcl prng
sjcl.random.addEntropy((new Date()).valueOf(), 2, "calltime"); sjcl.random.addEntropy((new Date()).valueOf(), 2, "calltime");
keyBuf = sjcl.random.randomWords(keySize / 32, 0); keyBuf = sjcl.random.randomWords(keySize / 32, 0);
keyBase64 = sjcl.codec.base64.fromBits(keyBuf); keyBase64 = sjcl.codec.base64.fromBits(keyBuf);

View File

@ -1,4 +1,5 @@
/** /**
* @constructor
* Constructs a new bignum from another bignum, a number or a hex string. * Constructs a new bignum from another bignum, a number or a hex string.
*/ */
sjcl.bn = function(it) { sjcl.bn = function(it) {
@ -156,12 +157,19 @@ sjcl.bn.prototype = {
}, },
mod: function(that) { mod: function(that) {
var neg = !this.greaterEquals(new sjcl.bn(0));
that = new sjcl.bn(that).normalize(); // copy before we begin that = new sjcl.bn(that).normalize(); // copy before we begin
var out = new sjcl.bn(this).normalize(), ci=0; var out = new sjcl.bn(this).normalize(), ci=0;
if (neg) out = (new sjcl.bn(0)).subM(out).normalize();
for (; out.greaterEquals(that); ci++) { for (; out.greaterEquals(that); ci++) {
that.doubleM(); that.doubleM();
} }
if (neg) out = that.sub(out).normalize();
for (; ci > 0; ci--) { for (; ci > 0; ci--) {
that.halveM(); that.halveM();
if (out.greaterEquals(that)) { if (out.greaterEquals(that)) {
@ -340,11 +348,12 @@ sjcl.bn.prototype = {
/** Serialize to a bit array */ /** Serialize to a bit array */
toBits: function(len) { toBits: function(len) {
this.fullReduce(); 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, 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))]; out = [w.partial(e, this.getLimb(i))];
for (i--; i >= 0; 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 out;
}, },
@ -354,13 +363,14 @@ sjcl.bn.prototype = {
this.fullReduce(); this.fullReduce();
var out = this.radix * (this.limbs.length - 1), var out = this.radix * (this.limbs.length - 1),
b = this.limbs[this.limbs.length - 1]; b = this.limbs[this.limbs.length - 1];
for (; b; b >>= 1) { for (; b; b >>>= 1) {
out ++; out ++;
} }
return out+7 & -8; return out+7 & -8;
} }
}; };
/** @this { sjcl.bn } */
sjcl.bn.fromBits = function(bits) { sjcl.bn.fromBits = function(bits) {
var Class = this, out = new Class(), words=[], w=sjcl.bitArray, t = this.prototype, 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; 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. * 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) { sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
/** @constructor */
function p(it) { function p(it) {
this.initWith(it); this.initWith(it);
/*if (this.limbs[this.modOffset]) { /*if (this.limbs[this.modOffset]) {
@ -415,7 +426,9 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
ppr._class = p; ppr._class = p;
ppr.modulus.cnormalize(); 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() { 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, aff, off = this.offset, ol = this.offset.length, fac = this.factor, ll;
@ -439,6 +452,7 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
return this; return this;
}; };
/** @this {sjcl.bn} */
ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() { ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() {
var limbs = this.limbs, i = limbs.length - 1, k, l; var limbs = this.limbs, i = limbs.length - 1, k, l;
this.reduce(); 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() { ppr.fullReduce = function() {
var greater, i; var greater, i;
// massively above the modulus, may be negative // massively above the modulus, may be negative
@ -484,6 +500,8 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
return this; return this;
}; };
/** @this {sjcl.bn} */
ppr.inverse = function() { ppr.inverse = function() {
return (this.power(this.modulus.sub(2))); return (this.power(this.modulus.sub(2)));
}; };

View File

@ -46,6 +46,10 @@
tmp = sjcl.misc.cachedPbkdf2(password, p); tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32); password = tmp.key.slice(0,p.ks/32);
p.salt = tmp.salt; 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") { if (typeof plaintext === "string") {
plaintext = sjcl.codec.utf8String.toBits(plaintext); plaintext = sjcl.codec.utf8String.toBits(plaintext);
@ -101,6 +105,8 @@
tmp = sjcl.misc.cachedPbkdf2(password, p); tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32); password = tmp.key.slice(0,p.ks/32);
p.salt = tmp.salt; 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") { if (typeof adata === "string") {
adata = sjcl.codec.utf8String.toBits(adata); adata = sjcl.codec.utf8String.toBits(adata);
@ -130,7 +136,7 @@
if (!i.match(/^[a-z0-9]+$/i)) { if (!i.match(/^[a-z0-9]+$/i)) {
throw new sjcl.exception.invalid("json encode: invalid property name"); throw new sjcl.exception.invalid("json encode: invalid property name");
} }
out += comma + '"' + i + '"' + ':'; out += comma + '"' + i + '":';
comma = ','; comma = ',';
switch (typeof obj[i]) { switch (typeof obj[i]) {

View File

@ -296,21 +296,38 @@ sjcl.ecc.curves = {
/* Diffie-Hellman-like public-key system */ /* Diffie-Hellman-like public-key system */
sjcl.ecc._dh = function(cn) { sjcl.ecc._dh = function(cn) {
sjcl.ecc[cn] = { sjcl.ecc[cn] = {
/** @constructor */
publicKey: function(curve, point) { publicKey: function(curve, point) {
this._curve = curve; this._curve = curve;
this._curveBitLength = curve.r.bitLength();
if (point instanceof Array) { if (point instanceof Array) {
this._point = curve.fromBits(point); this._point = curve.fromBits(point);
} else { } else {
this._point = point; 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) { secretKey: function(curve, exponent) {
this._curve = curve; this._curve = curve;
this._curveBitLength = curve.r.bitLength();
this._exponent = exponent; this._exponent = exponent;
this.get = function() {
return this._exponent.toBits();
}
}, },
generateKeys: function(curve, paranoia) { /** @constructor */
generateKeys: function(curve, paranoia, sec) {
if (curve === undefined) { if (curve === undefined) {
curve = 256; curve = 256;
} }
@ -320,7 +337,10 @@ sjcl.ecc._dh = function(cn) {
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); 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), return { pub: new sjcl.ecc[cn].publicKey(curve, pub),
sec: new sjcl.ecc[cn].secretKey(curve, sec) }; sec: new sjcl.ecc[cn].secretKey(curve, sec) };
} }
@ -351,29 +371,41 @@ sjcl.ecc.elGamal.secretKey.prototype = {
sjcl.ecc._dh("ecdsa"); sjcl.ecc._dh("ecdsa");
sjcl.ecc.ecdsa.secretKey.prototype = { sjcl.ecc.ecdsa.secretKey.prototype = {
sign: function(hash, paranoia) { sign: function(hash, paranoia, fakeLegacyVersion, fixedKForTesting) {
var R = this._curve.r, if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) {
l = R.bitLength(), hash = sjcl.bitArray.clamp(hash, this._curveBitLength);
k = sjcl.bn.random(R.sub(1), paranoia).add(1), }
r = this._curve.G.mult(k).x.mod(R), var R = this._curve.r,
s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).inverseMod(R).mul(k).mod(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)); return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
} }
}; };
sjcl.ecc.ecdsa.publicKey.prototype = { 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, var w = sjcl.bitArray,
R = this._curve.r, R = this._curve.r,
l = R.bitLength(), l = this._curveBitLength,
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)), 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), hG = sjcl.bn.fromBits(hash).mul(s).mod(R),
hA = r.mul(s).mod(R), hA = r.mul(s).mod(R),
r2 = this._curve.G.mult2(hG, hA, this._point).x; r2 = this._curve.G.mult2(hG, hA, this._point).x;
if (r.equals(0) || ss.equals(0) || r.greaterEquals(R) || ss.greaterEquals(R) || !r2.equals(r)) {
if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) { if (fakeLegacyVersion === undefined) {
throw (new sjcl.exception.corrupt("signature didn't check out")); return this.verify(hash, rs, true);
} else {
throw (new sjcl.exception.corrupt("signature didn't check out"));
}
} }
return true; return true;
} }

View File

@ -5,7 +5,8 @@
* @author Dan Boneh * @author Dan Boneh
*/ */
/** @namespace Random number generator /** @constructor
* @class Random number generator
* *
* @description * @description
* <p> * <p>
@ -39,7 +40,43 @@
* look for improvements in future versions. * look for improvements in future versions.
* </p> * </p>
*/ */
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 /** Generate several random words, and return them in an array
* @param {Number} nwords The number of words to generate. * @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. /** Generate 4 random words, no reseed, no gate.
* @private * @private
*/ */
@ -388,6 +392,8 @@ sjcl.random = {
} }
}; };
sjcl.random = new sjcl.prng(6);
(function(){ (function(){
try { try {
// get cryptographically strong entropy in Webkit // get cryptographically strong entropy in Webkit

View File

@ -42,25 +42,25 @@ var sjcl = {
/** @namespace Exceptions. */ /** @namespace Exceptions. */
exception: { exception: {
/** @class Ciphertext is corrupt. */ /** @constructor Ciphertext is corrupt. */
corrupt: function(message) { corrupt: function(message) {
this.toString = function() { return "CORRUPT: "+this.message; }; this.toString = function() { return "CORRUPT: "+this.message; };
this.message = message; this.message = message;
}, },
/** @class Invalid parameter. */ /** @constructor Invalid parameter. */
invalid: function(message) { invalid: function(message) {
this.toString = function() { return "INVALID: "+this.message; }; this.toString = function() { return "INVALID: "+this.message; };
this.message = message; this.message = message;
}, },
/** @class Bug or missing feature in SJCL. */ /** @constructor Bug or missing feature in SJCL. @constructor */
bug: function(message) { bug: function(message) {
this.toString = function() { return "BUG: "+this.message; }; this.toString = function() { return "BUG: "+this.message; };
this.message = message; this.message = message;
}, },
/** @class Something isn't ready. */ /** @constructor Something isn't ready. */
notReady: function(message) { notReady: function(message) {
this.toString = function() { return "NOT READY: "+this.message; }; this.toString = function() { return "NOT READY: "+this.message; };
this.message = message; this.message = message;

View File

@ -27,7 +27,7 @@ sjcl.keyexchange.srp = {
*/ */
makeVerifier: function(I, P, s, group) { makeVerifier: function(I, P, s, group) {
var x; var x;
x = this.makeX(I, P, s); x = sjcl.keyexchange.srp.makeX(I, P, s);
x = sjcl.bn.fromBits(x); x = sjcl.bn.fromBits(x);
return group.g.powermod(x, group.N); return group.g.powermod(x, group.N);
}, },
@ -52,8 +52,8 @@ sjcl.keyexchange.srp = {
*/ */
knownGroup:function(i) { knownGroup:function(i) {
if (typeof i !== "string") { i = i.toString(); } if (typeof i !== "string") { i = i.toString(); }
if (!this._didInitKnownGroups) { this._initKnownGroups(); } if (!sjcl.keyexchange.srp._didInitKnownGroups) { sjcl.keyexchange.srp._initKnownGroups(); }
return this._knownGroups[i]; return sjcl.keyexchange.srp._knownGroups[i];
}, },
/** /**
@ -63,13 +63,13 @@ sjcl.keyexchange.srp = {
_didInitKnownGroups: false, _didInitKnownGroups: false,
_initKnownGroups:function() { _initKnownGroups:function() {
var i, size, group; var i, size, group;
for (i=0; i < this._knownGroupSizes.length; i++) { for (i=0; i < sjcl.keyexchange.srp._knownGroupSizes.length; i++) {
size = this._knownGroupSizes[i].toString(); size = sjcl.keyexchange.srp._knownGroupSizes[i].toString();
group = this._knownGroups[size]; group = sjcl.keyexchange.srp._knownGroups[size];
group.N = new sjcl.bn(group.N); group.N = new sjcl.bn(group.N);
group.g = new sjcl.bn(group.g); group.g = new sjcl.bn(group.g);
} }
this._didInitKnownGroups = true; sjcl.keyexchange.srp._didInitKnownGroups = true;
}, },
_knownGroupSizes: [1024, 1536, 2048], _knownGroupSizes: [1024, 1536, 2048],

View File

@ -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);
});

34
src/test/unit/ecc-test.js Normal file
View File

@ -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);
});

View File

@ -40,6 +40,9 @@
<script src="../../lib/sjcl/codecString.js"></script> <script src="../../lib/sjcl/codecString.js"></script>
<script src="../../lib/sjcl/aes.js"></script> <script src="../../lib/sjcl/aes.js"></script>
<script src="../../lib/sjcl/ccm.js"></script> <script src="../../lib/sjcl/ccm.js"></script>
<script src="../../lib/sjcl/bn.js"></script>
<script src="../../lib/sjcl/ecc.js"></script>
<script src="../../lib/sjcl/convenience.js"></script>
<script src="../../lib/uuid.js"></script> <script src="../../lib/uuid.js"></script>
<script src="../../lib/openpgp.min.js"></script> <script src="../../lib/openpgp.min.js"></script>
@ -71,6 +74,7 @@
<script src="../test-data.js"></script> <script src="../test-data.js"></script>
<script src="util-test.js"></script> <script src="util-test.js"></script>
<script src="aes-test.js"></script> <script src="aes-test.js"></script>
<script src="ecc-test.js"></script>
<script src="crypto-test.js"></script> <script src="crypto-test.js"></script>
<script src="localstorage-dao-test.js"></script> <script src="localstorage-dao-test.js"></script>
<script src="lawnchair-dao-test.js"></script> <script src="lawnchair-dao-test.js"></script>