4 * An ActionScript 3 implementation of RSA + PKCS#1 (light version)
\r
5 * Copyright (c) 2007 Henri Torgemane
\r
8 * The jsbn library, Copyright (c) 2003-2005 Tom Wu
\r
10 * See LICENSE.txt for full license information.
\r
12 package com.hurlant.crypto.rsa
\r
14 import com.hurlant.crypto.prng.Random;
\r
15 import com.hurlant.math.BigInteger;
\r
16 import com.hurlant.util.Memory;
\r
18 import flash.utils.ByteArray;
\r
19 import com.hurlant.crypto.hash.IHash;
\r
20 import com.hurlant.util.Hex;
\r
21 import com.hurlant.util.der.DER;
\r
22 import com.hurlant.util.der.OID;
\r
23 import com.hurlant.util.ArrayUtil;
\r
24 import com.hurlant.util.der.Type;
\r
25 import com.hurlant.util.der.Sequence;
\r
26 import com.hurlant.util.der.ObjectIdentifier;
\r
27 import com.hurlant.util.der.ByteString;
28 import com.hurlant.crypto.tls.TLSError;
\r
31 * Current limitations:
\r
32 * exponent must be smaller than 2^31.
\r
37 public var e:int; // public exponent. must be <2^31
\r
38 public var n:BigInteger; // modulus
\r
40 public var d:BigInteger;
\r
41 // extended private key
\r
42 public var p:BigInteger;
\r
43 public var q:BigInteger;
\r
44 public var dmp1:BigInteger
\r
45 public var dmq1:BigInteger;
\r
46 public var coeff:BigInteger;
\r
47 // flags. flags are cool.
\r
48 protected var canDecrypt:Boolean;
\r
49 protected var canEncrypt:Boolean;
\r
51 public function RSAKey(N:BigInteger, E:int,
\r
53 P:BigInteger = null, Q:BigInteger=null,
\r
54 DP:BigInteger=null, DQ:BigInteger=null,
\r
55 C:BigInteger=null) {
\r
66 // adjust a few flags.
\r
67 canEncrypt = (n!=null&&e!=0);
\r
68 canDecrypt = (canEncrypt&&d!=null);
\r
73 public static function parsePublicKey(N:String, E:String):RSAKey {
\r
74 return new RSAKey(new BigInteger(N, 16, true), parseInt(E,16));
\r
76 public static function parsePrivateKey(N:String, E:String, D:String,
\r
77 P:String=null,Q:String=null, DMP1:String=null, DMQ1:String=null, IQMP:String=null):RSAKey {
\r
79 return new RSAKey(new BigInteger(N,16, true), parseInt(E,16), new BigInteger(D,16, true));
\r
81 return new RSAKey(new BigInteger(N,16, true), parseInt(E,16), new BigInteger(D,16, true),
\r
82 new BigInteger(P,16, true), new BigInteger(Q,16, true),
\r
83 new BigInteger(DMP1,16, true), new BigInteger(DMQ1, 16, true),
\r
84 new BigInteger(IQMP, 16, true));
\r
88 public function getBlockSize():uint {
\r
89 return (n.bitLength()+7)/8;
\r
91 public function dispose():void {
\r
98 public function encrypt(src:ByteArray, dst:ByteArray, length:uint, pad:Function=null):void {
\r
99 _encrypt(doPublic, src, dst, length, pad, 0x02);
\r
101 public function decrypt(src:ByteArray, dst:ByteArray, length:uint, pad:Function=null):void {
\r
102 _decrypt(doPrivate2, src, dst, length, pad, 0x02);
\r
105 public function sign(src:ByteArray, dst:ByteArray, length:uint, pad:Function = null):void {
\r
106 _encrypt(doPrivate2, src, dst, length, pad, 0x01);
\r
108 public function verify(src:ByteArray, dst:ByteArray, length:uint, pad:Function = null):void {
\r
109 _decrypt(doPublic, src, dst, length, pad, 0x01);
\r
113 private function _encrypt(op:Function, src:ByteArray, dst:ByteArray, length:uint, pad:Function, padType:int):void {
\r
114 // adjust pad if needed
\r
115 if (pad==null) pad = pkcs1pad;
\r
116 // convert src to BigInteger
\r
117 if (src.position >= src.length) {
\r
120 var bl:uint = getBlockSize();
\r
121 var end:int = src.position + length;
\r
122 while (src.position<end) {
\r
123 var block:BigInteger = new BigInteger(pad(src, end, bl, padType), bl, true);
\r
124 var chunk:BigInteger = op(block);
\r
125 chunk.toArray(dst);
\r
128 private function _decrypt(op:Function, src:ByteArray, dst:ByteArray, length:uint, pad:Function, padType:int):void {
\r
129 // adjust pad if needed
\r
130 if (pad==null) pad = pkcs1unpad;
\r
132 // convert src to BigInteger
\r
133 if (src.position >= src.length) {
\r
136 var bl:uint = getBlockSize();
\r
137 var end:int = src.position + length;
\r
138 while (src.position<end) {
\r
139 var block:BigInteger = new BigInteger(src, bl, true);
\r
140 var chunk:BigInteger = op(block);
\r
141 var b:ByteArray = pad(chunk, bl, padType);
\r
143 throw new TLSError( "Decrypt error - padding function returned null!", TLSError.decode_error );
\r
150 * PKCS#1 pad. type 1 (0xff) or 2, random.
\r
151 * puts as much data from src into it, leaves what doesn't fit alone.
\r
153 private function pkcs1pad(src:ByteArray, end:int, n:uint, type:uint = 0x02):ByteArray {
\r
154 var out:ByteArray = new ByteArray;
\r
155 var p:uint = src.position;
\r
156 end = Math.min(end, src.length, p+n-11);
\r
157 src.position = end;
\r
159 while (i>=p && n>11) {
\r
160 out[--n] = src[i--];
\r
163 if (type==0x02) { // type 2
\r
164 var rng:Random = new Random;
\r
168 x = rng.nextByte();
\r
186 * @param type Not used.
\r
190 private function pkcs1unpad(src:BigInteger, n:uint, type:uint = 0x02):ByteArray {
\r
191 var b:ByteArray = src.toByteArray();
\r
192 var out:ByteArray = new ByteArray;
\r
196 while (i<b.length && b[i]==0) ++i;
\r
197 if (b.length-i != n-1 || b[i]!=type) {
\r
198 trace("PKCS#1 unpad: i="+i+", expected b[i]=="+type+", got b[i]="+b[i].toString(16));
\r
203 if (++i>=b.length) {
\r
204 trace("PKCS#1 unpad: i="+i+", b[i-1]!=0 (="+b[i-1].toString(16)+")");
\r
208 while (++i < b.length) {
\r
209 out.writeByte(b[i]);
\r
217 public function rawpad(src:ByteArray, end:int, n:uint, type:uint = 0):ByteArray {
\r
220 public function rawunpad(src:BigInteger, n:uint, type:uint = 0):ByteArray {
\r
221 return src.toByteArray();
\r
224 public function toString():String {
\r
228 public function dump():String {
\r
229 var s:String= "N="+n.toString(16)+"\n"+
\r
230 "E="+e.toString(16)+"\n";
\r
232 s+="D="+d.toString(16)+"\n";
\r
233 if (p!=null && q!=null) {
\r
234 s+="P="+p.toString(16)+"\n";
\r
235 s+="Q="+q.toString(16)+"\n";
\r
236 s+="DMP1="+dmp1.toString(16)+"\n";
\r
237 s+="DMQ1="+dmq1.toString(16)+"\n";
\r
238 s+="IQMP="+coeff.toString(16)+"\n";
\r
247 * note: We should have a "nice" variant of this function that takes a callback,
\r
248 * and perform the computation is small fragments, to keep the web browser
\r
253 * @return a new random private key B bits long, using public expt E
\r
256 public static function generate(B:uint, E:String):RSAKey {
\r
257 var rng:Random = new Random;
\r
258 var qs:uint = B>>1;
\r
259 var key:RSAKey = new RSAKey(null,0,null);
\r
260 key.e = parseInt(E, 16);
\r
261 var ee:BigInteger = new BigInteger(E,16, true);
\r
264 key.p = bigRandom(B-qs, rng);
\r
265 if (key.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE)==0 &&
\r
266 key.p.isProbablePrime(10)) break;
\r
269 key.q = bigRandom(qs, rng);
\r
270 if (key.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE)==0 &&
\r
271 key.q.isProbablePrime(10)) break;
\r
273 if (key.p.compareTo(key.q)<=0) {
\r
274 var t:BigInteger = key.p;
\r
278 var p1:BigInteger = key.p.subtract(BigInteger.ONE);
\r
279 var q1:BigInteger = key.q.subtract(BigInteger.ONE);
\r
280 var phi:BigInteger = p1.multiply(q1);
\r
281 if (phi.gcd(ee).compareTo(BigInteger.ONE)==0) {
\r
282 key.n = key.p.multiply(key.q);
\r
283 key.d = ee.modInverse(phi);
\r
284 key.dmp1 = key.d.mod(p1);
\r
285 key.dmq1 = key.d.mod(q1);
\r
286 key.coeff = key.q.modInverse(key.p);
\r
293 protected static function bigRandom(bits:int, rnd:Random):BigInteger {
\r
294 if (bits<2) return BigInteger.nbv(1);
\r
295 var x:ByteArray = new ByteArray;
\r
296 rnd.nextBytes(x, (bits>>3));
\r
298 var b:BigInteger = new BigInteger(x,0,true);
\r
299 b.primify(bits, 1);
\r
303 protected function doPublic(x:BigInteger):BigInteger {
\r
304 return x.modPowInt(e, n);
\r
307 protected function doPrivate2(x:BigInteger):BigInteger {
\r
308 if (p==null && q==null) {
\r
309 return x.modPow(d,n);
\r
312 var xp:BigInteger = x.mod(p).modPow(dmp1, p);
\r
313 var xq:BigInteger = x.mod(q).modPow(dmq1, q);
\r
315 while (xp.compareTo(xq)<0) {
\r
318 var r:BigInteger = xp.subtract(xq).multiply(coeff).mod(p).multiply(q).add(xq);
\r
323 protected function doPrivate(x:BigInteger):BigInteger {
\r
324 if (p==null || q==null) {
\r
325 return x.modPow(d, n);
\r
327 // TODO: re-calculate any missing CRT params
\r
328 var xp:BigInteger = x.mod(p).modPow(dmp1, p);
\r
329 var xq:BigInteger = x.mod(q).modPow(dmq1, q);
\r
331 while (xp.compareTo(xq)<0) {
\r
334 return xp.subtract(xq).multiply(coeff).mod(p).multiply(q).add(xq);
\r