2011年2月28日月曜日

AndroidでRSAを使ってみた

RSAでの暗号化と復号化のシンプルなサンプルコードを書いてみた。

    public static byte[] encrypt(byte[] data)
            throws
                InvalidKeySpecException,
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final Key publicKey =
            KeyFactory
            .getInstance("RSA")
            .generatePublic(new X509EncodedKeySpec(PUBLIC_KEY_BYTES));
        final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }
    public static byte[] decrypt(byte[] data)
            throws
                InvalidKeySpecException,
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final Key privateKey =
            KeyFactory
            .getInstance("RSA")
            .generatePrivate(new PKCS8EncodedKeySpec(PRIVATE_KEY_BYTES));
        final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

ポイントは、Cipherのインスタンスを取得するときに、
フィードバックモードとパディングをちゃんと指定すること。

Cipher.getInstance("RSA") // これは悪い例!

同じJavaVM環境なら、こうやって書いても期待通りに動いてしまう。

フィードバックモードおよびパディングを省略すると、
プロバイダ固有のデフォルト値が使われると書いてある

同じJavaVM環境ならプロバイダーが同じだからデフォルト値も同じなので問題がないというわけ。

ところが、
Sun(Oracle)のJREやJDKにバンドルされているJCEプロバイダのデフォルトはSunJCEで、
Androidの場合はBouncyCastleのようだ。

SunJCEの場合
"RSA"は"RSA/ECB/PKCS1Padding"
がデフォルトみたい。

BouncyCastleの場合は
"RSA"は"RSA/None/NoPadding"
がデフォルトみたい。

これらはたまたま手元の環境で動かしたらそうなっただけで、
デフォルト値が未来永劫変わらない保証はない。

というわけで、フィードバックモードとパディングを省略すると
サーバとAndroid端末の間で渡したデータを復号化できなくなると思った方がよい。

0 件のコメント:

コメントを投稿