JavaScriptで「ふつう」に扱える整数は 253 までである。単純に考えると52ビットまでの鍵を使える。しかし途中計算で積をとったりするので、その半分以下にしたほうが簡単だ。ここでは大きな数の計算を独自に実装することが目的でなく、あくまでRSAをJavaScript上で構築してみるのが目的だからだ(巨大整数の計算については別記事「JavaScriptで1000桁電卓」を参照)。以下では暗号強度を24ビットとする。
最初にいくつかの一般的な関数などを定義しとく。
function _debug( message ) {
document.write( "<p>Debug: " + message + ".<\/p>" );
}
function isPrime( number ) {
if( number < 2 || number != Math.floor(number) ) {
_debug("[ERROR] Unexpected input " + number + " at function isPrime");
return false;
}
for( var i=3; i <= Math.sqrt(number); i+=2 ) {
if( number % i ==0 ) {
_debug( number + " divisible by " + i );
return false;
}
}
_debug( "* " + number + " is prime" );
return true;
}
function log2( x ) {
return Math.log( x ) / Math.log( 2 );
}
var MAX_STEPS = 10000;
鍵を生成するために、2つの素数 p, q を見つける。ミリ秒単位のUnix秒から種を得る。種は一定の値の範囲の奇数になるようにする。素数が見つかるまで2ずつ増やしながら検索をつづける。この方法だと p, q は必ず隣同士の素数になり、n = pq の因数分解が容易になってしまう。しかし24ビットではどっちにせよもともと容易なので、気にしないことにしよう。
var strength = 24;
if(strength<24 || strength%2!=0 ) strength = 24;
_debug( strength + "-bit RSA");
var max = Math.pow( 2, (strength/2) ), min = Math.pow( 2, (strength/2 - 1) );
var objDate = new Date();
var seed = ( objDate.getTime() ) % max;
if( seed < min ) seed += min;
if( seed % 2 == 0 ) seed += 1;
_debug( "seed = " + seed );
var number, p = 0, q = 0, n;
for( var i=0; i<MAX_STEPS; i++ ) {
number = seed + i*2;
if( isPrime(number) ) {
if( p==0 ) p = number;
else {
q = number;
n = p * q;
break;
}
}
}
_debug("p = " + p );
_debug("q = " + q );
_debug("n = " + n );
_debug( "log<sub>2</sub>n = " + log2( n ) );
//isPrime( n );
次に公開指数αを決定する。αは u = (p-1)(q-1) と互いに素でなければならない。そのことを保証するためにあらかじめ (p-1)(q-1) の素因数を調べあげても良いが、ユークリッドの互除法を使ってαとuの最大公約数が1であることを確認したほうが手っ取り早い。
function gcd( a, b ) {
if( a<1 || b<1 || a!=Math.floor(a) || b!=Math.floor(b) ) {
_debug("[Error] a=" + a + ", b=" + b + " at function gcd(a, b)");
return 2;
}
var r0=a, r1=b, r=1;
for( var i=0; i<MAX_STEPS; i++) {
r = r0 % r1;
if(r==0) break;
r0 = r1, r1 = r;
}
_debug( "gcd(" + a + ", " + b + ") = " + r1 );
return r1;
}
var u = (p-1)*(q-1);
_debug("u = " + u );
var v = ( objDate.getTime() ) % max;
if( v < min*1.5 ) v += min*1.5;
if(v%2==0) v+=1;
_debug("v = " + v );
for( var i=0; i<MAX_STEPS; i++ ) {
if( gcd(u, v) == 1) break;
else v += 2;
}
var alpha = v;
_debug("alpha = " + alpha);
法 n と公開指数α が求まったので、秘密鍵βを決定することができる。
u=(p-1)*(q-1);
var u0 = u;
var a0 = alpha;
var t0 = 0, t = 1;
var r = u0 % a0;
var q = (u0-r)/a0;
for(var i=0; r>0 && i<MAX_STEPS; i++) {
var tmp = t0 - q * t;
if( tmp >= 0 ) tmp = tmp % u;
else tmp = u - ( (-tmp) % u );
t0 = t, t = tmp, u0 = a0, a0 = r;
r = u0 % a0;
q = (u0-r)/a0;
}
var beta = t % u;
if( a0 == 1 ) _debug("beta = " + beta);
else _debug("[ERROR] beta not exists");
_debug("log2(alpha*beta) = " + log2(alpha*beta) );
_debug("alpha*beta (mod u) = " + alpha*beta%u);
document.write("<dl>");
document.write("<dt>Public Key<\/dt>");
document.write("<dd>n = " + n + " , alpha = " + alpha + "<\/dd>");
document.write("<dt>Secret Key<\/dt>");
document.write("<dd>beta = " + beta + "<\/dd>");
document.write("<\/dl>");
生成された公開鍵を使って3つのサンプルを暗号化する。
暗号化指数αは大きいから、累乗は分けて計算する必要がある。
var sample1 = 123, sample2 = 7743, sample3 = n-5;
/*
n=17441,
alpha=7,
beta=14719;
sample1=4980;
sample2=1234;
*/
_debug("sample1 = " + sample1 );
_debug("sample2 = " + sample2 );
_debug("sample3 = " + sample3 );
var alpha_bin = alpha.toString(2);
var D_alpha = new Array();
for(var i=0; i<alpha_bin.length; i++) {
D_alpha[i] = alpha_bin.substr( alpha_bin.length-1 - i , 1 );
}
function encrypt( the_plain ) {
var Work = new Array();
var result = 1;
for(var i=0; i<alpha_bin.length; i++ ) {
if(i==0) Work[i] = the_plain;
else Work[i] = Work[i-1]*Work[i-1]%n;
if( D_alpha[i] == 1 ) {
result *= Work[i];
result %= n;
}
}
return result;
}
var e1 = encrypt( sample1 );
_debug( "sample1 encrypted: " + e1 );
var e2 = encrypt( sample2 );
_debug( "sample2 encrypted: " + e2 );
var e3 = encrypt( sample3 );
_debug( "sample3 encrypted: " + e3 );
秘密指数βを使って暗号を解除する。
var beta_bin = beta.toString(2);
var D_beta = new Array();
for(var i=0; i<beta_bin.length; i++) {
D_beta[i] = beta_bin.substr( beta_bin.length-1 - i , 1 );
}
function decrypt( the_encrypted ) {
var Work = new Array();
var result = 1;
for(var i=0; i<beta_bin.length; i++ ) {
if(i==0) Work[i] = the_encrypted;
else Work[i] = Work[i-1] * Work[i-1]%n;
if( D_beta[i] == 1 ) {
result *= Work[i];
result %= n;
}
}
return result;
}
var d1 = decrypt( e1 );
_debug( "sample1 decrypted: " + d1 );
var d2 = decrypt( e2 );
_debug( "sample2 decrypted: " + d2 );
var d3 = decrypt( e3 );
_debug( "sample2 decrypted: " + d3 );
以上のリストを実際に走らせた結果(rsa.jsを動的に実行):