RSA-SHA1署名をFlash(actionscript3)で検証する方法

久しぶりの日記です。

ここのところ業務でも趣味でもPHPしか触っていなかったのですが、久しぶりにactionscriptを触ったりしてました。

お遊びの趣味プロで、セキュリティの勉強も兼ねて署名検証なんかをやってみました。

で、RSA署名検証についてKYUCON*BLOGさんに記述があったのですが、同じような感じでRSA-SHA1署名検証をas3で実装したFlash(すなわちクライアント側)で行おうとしてつまずいたのでその解決方法をば。

  • sha1自体の脆弱性等の問題はここでは考えないでおきます
  • セキュリティはまだまだ勉強中なので、「それはちげーよ」等ツッコミありましたらお願いいたします

そもそも、RSA-SHA1形式の署名検証は、

  1. 秘密鍵により暗号化された署名が、公開鍵を用いて復号出来る
  2. 復号化した署名が、署名生成時に使用したデータのsha1ハッシュである

という2つの検証により、それぞれ、「1.送信元の妥当性」「2.署名の中身の妥当性」を検証しています。


署名アルゴリズムやハッシュ化関数を用いるためのactionscript3の代表的なライブラリである、as3cryptoライブラリのRSA署名検証用の RSAKey.verify メソッドは、実は単に署名を公開鍵で復号してその結果を第二引数のByteArrayに入れているだけです。

なので上述のKYUCON*BLOGさんでも以下のようにverify後に、==演算子で元データとの比較を行なっています。(KYUCON*BLOGさんから抜粋)

var rsa_verify : RSAKey= RSAKey.parsePublicKey(public_modulus, public_exponent);
var srcDecryptBA:ByteArray = Base64.decodeToByteArray(encrypted_str);
//復号したデータを格納するためのByteArray
var dstDecryptBA:ByteArray = new ByteArray();
//復号実行
try{
    rsa_verify.verify(srcDecryptBA, dstDecryptBA, srcDecryptBA.length); // ここで「1.送信元の妥当性」を検証
    trace("復号した文字列:" + dstDecryptBA.toString());
    if(dstDecryptBA.toString()==original_str){ // ここで「2.署名の中身の妥当性」を検証
        trace("改竄なし");
    }else{
        trace("改竄あり");
    }
}catch (e) {
    trace("verify失敗。不正なデータ。");
}

単なるRSA暗号化の場合はこれで良いですが、サーバサイドのphpでopenssl_signメソッドなどを使って署名をした場合はハッシュ計算にSHA1が使われるため、元データとの単純比較では当然動作しません。

これを行う場合、以下のような実装が必要になります。

try{
    // as3cryptoのPEMモジュールを使ってopensslで生成できる.pemファイル公開鍵からRSAKeyオブジェクトを生成します
    var rsa:RSAKey = PEM.readRSAPublicKey(publicKey);
    var src:ByteArray = Base64.decodeToByteArray(rsasha1_signature); // RSA-SHA1形式の署名をByteArrayへ変換
    var dst:ByteArray = new ByteArray();
    rsa.verify(src, dst, src.length); // ここで「1.送信元の妥当性」を検証

    var signature:String = Hex.fromArray(dst); // 復号化した署名を16進文字列へ変換

    var prefixIndex:int = signature.indexOf('3021300906052b0e03021a05000414');

    // 署名生成時の元データをsha1ハッシュ化
    var ba:ByteArray = new ByteArray();
    ba.writeMultiByte(original_str, 'utf-8');
    var hashAlg:IHash = Crypto.getHash('sha1');
    var hashBa:ByteArray=hashAlg.hash(ba);
    var original_sha1 = Hex.fromArray(hashBa);

    var hashIndex:int = signature.indexOf(original_sha1);

    if(prefixIndex != 0 || hashIndex != 30){ // ここで「2.署名の中身の妥当性」を検証
    	trace('検証失敗');
    }else{
        trace('検証成功');
    }
}catch(e){
    trace('検証失敗');
}

実は、RSA-SHA1形式の署名をRSAKey.verifyメソッドで処理した場合、その第二引数(コード中で言うdst)には以下のような70byteのデータが入ります。(16進表記)

  • 3021300906052b0e03021a05000414{sha1(original_str)}

ここで前半30byteの「3021300906052b0e03021a05000414」とはRFC3447で規定されたsha1ハッシュのプレフィックスです。

そしてこのプレフィックスよりも後ろの値40byteが元データのsha1ハッシュ値になっています。

そのため、復号化して16進表記に直した値からプレフィックスハッシュ値を探し出し、検証を行う必要があります。

僕はこれを知るのに2,3日ほど時間を費やしてしまったので、他にも同様なことをしたいという人がいるときに参考にでもなればと思います。

ちなみに、as3cryptoライブラリですが、google codeのas3cryptoページのTOPなどからダウンロード出来るファイルは古くて正常に動作しません。

その他にも、結構なバグが修正されているのでsvn上のファイルを用いることをおすすめします。


なにかご意見等ありましたらTwitterかコメント欄までお願いします。