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

View File

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

View File

@ -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]) {

View File

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

View File

@ -5,7 +5,8 @@
* @author Dan Boneh
*/
/** @namespace Random number generator
/** @constructor
* @class Random number generator
*
* @description
* <p>
@ -39,7 +40,43 @@
* look for improvements in future versions.
* </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
* @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

View File

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

View File

@ -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],

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/aes.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/openpgp.min.js"></script>
@ -71,6 +74,7 @@
<script src="../test-data.js"></script>
<script src="util-test.js"></script>
<script src="aes-test.js"></script>
<script src="ecc-test.js"></script>
<script src="crypto-test.js"></script>
<script src="localstorage-dao-test.js"></script>
<script src="lawnchair-dao-test.js"></script>