Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kevburnsjr committed Nov 25, 2011
0 parents commit c7f75ac
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 0 deletions.
37 changes: 37 additions & 0 deletions README.md
@@ -0,0 +1,37 @@

Generating RSA keys is a characteristically CPU intensive operation. This presents problems when operating on weak devices (such as the iPhone 3G) in environments under strict computation restrictions (such as safari mobile's 10 second javascript execution timeout).

What is needed is an RSA key generation library that operates asynchronously in order to chug through the 2+ minutes of computation time required to generate a 512 RSA key on a weak device without bumping against the computation restrictions enforced by the safari mobile execution environment.

Some nearly suitable libraries do exist, but all of them fall short in some fashion.

* Probably the closest is this **asynchronous keygen** from Atsushi Oka
http://ats.oka.nu/titaniumcore/js/crypto/readme.txt
However, the interface for Ats Oka's library is not simple and the architecture of the code leaves something to be desired.

* **Cryptico** is another library featuring RSA key generation which is also touted as a sort of all-in-one solution
http://code.google.com/p/cryptico/
However, this library really just glues together a bunch of already-available libraries and packages them as a unit.

* **jsbn** is the underlying RSA key generator packaged with Cryptico and is available on its own
http://www-cs-students.stanford.edu/~tjw/jsbn/
This library has a fairly simple interface and is relatively fast and compact meeting most of my requirements.
However, *this library doesn't do asynchronous key generation*.

But *we can fix that*.

jsbn RSA keygen times out after 11 seconds on the iPhone 3G for even a 256 bit key but with a little fenangling and a lot of setTimeouts, we can get it to handle a key of virtually any size for which the user has the patience to wait.

Here's an example of the new async interface:

```javascript
key = new RSAKey();
key.generateAsync(512, "03", function(){
var pubKey = hex2b64(key.n.toString(16));
alert(pubKey);
});
```

This was a great exercise in how to turn synchronous javascript into asynchronous javascript. Taking procedural code and breaking those for loops into recursive functions was a mind bender but once I figured out how it generally ought to work, each function became easier to port.

Originally I did it all inline, but later I ripped it all out into a separate file which extends Tom Wu's jsbn.
202 changes: 202 additions & 0 deletions rsasync.js
@@ -0,0 +1,202 @@
// Copyright (c) 2011 Kevin M Burns Jr.
// All Rights Reserved.
// See "LICENSE" for details.
//
// Extension to jsbn which adds facilities for asynchronous RSA key generation
// Primarily created to avoid execution timeout on mobile devices
//
// http://www-cs-students.stanford.edu/~tjw/jsbn/
//
// ---

(function(){

// Generate a new random private key B bits long, using public expt E
var RSAGenerateAsync = function (B, E, callback) {
//var rng = new SeededRandom();
var rng = new SecureRandom();
var qs = B >> 1;
this.e = parseInt(E, 16);
var ee = new BigInteger(E, 16);
var rsa = this;
// These functions have non-descript names because they were originally for(;;) loops.
// I don't know about cryptography to give them better names than loop1-4.
var loop1 = function() {
var loop4 = function() {
if (rsa.p.compareTo(rsa.q) <= 0) {
var t = rsa.p;
rsa.p = rsa.q;
rsa.q = t;
}
var p1 = rsa.p.subtract(BigInteger.ONE);
var q1 = rsa.q.subtract(BigInteger.ONE);
var phi = p1.multiply(q1);
if (phi.gcd(ee).compareTo(BigInteger.ONE) == 0) {
rsa.n = rsa.p.multiply(rsa.q);
rsa.d = ee.modInverse(phi);
rsa.dmp1 = rsa.d.mod(p1);
rsa.dmq1 = rsa.d.mod(q1);
rsa.coeff = rsa.q.modInverse(rsa.p);
setTimeout(function(){callback(rsa)},0); // escape
} else {
setTimeout(loop1,0);
}
};
var loop3 = function() {
rsa.q = nbi();
rsa.q.fromNumberAsync(qs, 1, rng, function(){
rsa.q.subtract(BigInteger.ONE).gcda(ee, function(r){
if (r.compareTo(BigInteger.ONE) == 0 && rsa.q.isProbablePrime(10)) {
setTimeout(loop4,0);
} else {
setTimeout(loop3,0);
}
});
});
};
var loop2 = function() {
rsa.p = nbi();
rsa.p.fromNumberAsync(B - qs, 1, rng, function(){
rsa.p.subtract(BigInteger.ONE).gcda(ee, function(r){
if (r.compareTo(BigInteger.ONE) == 0 && rsa.p.isProbablePrime(10)) {
setTimeout(loop3,0);
} else {
setTimeout(loop2,0);
}
});
});
};
setTimeout(loop2,0);
};
setTimeout(loop1,0);
};
RSAKey.prototype.generateAsync = RSAGenerateAsync;

// Public API method
var bnGCDAsync = function (a, callback) {
var x = (this.s < 0) ? this.negate() : this.clone();
var y = (a.s < 0) ? a.negate() : a.clone();
if (x.compareTo(y) < 0) {
var t = x;
x = y;
y = t;
}
var i = x.getLowestSetBit(),
g = y.getLowestSetBit();
if (g < 0) {
callback(x);
return;
}
if (i < g) g = i;
if (g > 0) {
x.rShiftTo(g, x);
y.rShiftTo(g, y);
}
// Workhorse of the algorithm, gets called 200 - 800 times per 512 bit keygen.
var gcda1 = function() {
if ((i = x.getLowestSetBit()) > 0){ x.rShiftTo(i, x); }
if ((i = y.getLowestSetBit()) > 0){ y.rShiftTo(i, y); }
if (x.compareTo(y) >= 0) {
x.subTo(y, x);
x.rShiftTo(1, x);
} else {
y.subTo(x, y);
y.rShiftTo(1, y);
}
if(!(x.signum() > 0)) {
if (g > 0) y.lShiftTo(g, y);
setTimeout(function(){callback(y)},0); // escape
} else {
setTimeout(gcda1,0);
}
};
setTimeout(gcda1,10);
};
BigInteger.prototype.gcda = bnGCDAsync;

// (protected) alternate constructor
var bnpFromNumberAsync = function (a,b,c,callback) {
if("number" == typeof b) {
if(a < 2) {
this.fromInt(1);
} else {
this.fromNumber(a,c);
if(!this.testBit(a-1)){
this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);
}
if(this.isEven()) {
this.dAddOffset(1,0);
}
var bnp = this;
var bnpfn1 = function(){
bnp.dAddOffset(2,0);
if(bnp.bitLength() > a) bnp.subTo(BigInteger.ONE.shiftLeft(a-1),bnp);
if(bnp.isProbablePrime(b)) {
setTimeout(function(){callback()},0);
} else {
setTimeout(bnpfn1,0);
}
};
setTimeout(bnpfn1,0);
}
} else {
var x = new Array(), t = a&7;
x.length = (a>>3)+1;
b.nextBytes(x);
if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0;
this.fromString(x,256);
}
};
BigInteger.prototype.fromNumberAsync = bnpFromNumberAsync;

// Cast to private Transport Object
var RSAPrivTPO = function () {
return {
'n' : hex2b64(this.n.toString(16)),
'e' : hex2b64(this.e.toString(16)),
'd' : hex2b64(this.d.toString(16)),
'p' : hex2b64(this.p.toString(16)),
'q' : hex2b64(this.q.toString(16)),
'dmp1' : hex2b64(this.dmp1.toString(16)),
'dmq1' : hex2b64(this.dmq1.toString(16)),
'coeff' : hex2b64(this.coeff.toString(16))
}
}
RSAKey.prototype.privTPO = RSAPrivTPO;

// Hydrate from private Transport Object
var RSAFromPrivTPO = function (tpo) {
this.setPrivateEx(
b64tohex(tpo.n),
b64tohex(tpo.e),
b64tohex(tpo.d),
b64tohex(tpo.p),
b64tohex(tpo.q),
b64tohex(tpo.dmp1),
b64tohex(tpo.dmq1),
b64tohex(tpo.coeff)
);
return this;
};
RSAKey.prototype.fromPrivTPO = RSAFromPrivTPO;

// Cast to public Transport Object
var RSAPubTPO = function () {
return {
'n' : hex2b64(this.n.toString(16)),
'e' : hex2b64(this.e.toString(16))
}
};
RSAKey.prototype.pubTPO = RSAPubTPO;

// Hydrate from public Transport Object
var RSAFromPubTPO = function (tpo) {
this.setPublic(
b64tohex(tpo.n),
b64tohex(tpo.e)
);
return this;
};
RSAKey.prototype.fromPubTPO = RSAFromPubTPO;

})();

0 comments on commit c7f75ac

Please sign in to comment.