2010年9月19日日曜日

Java RSA 暗号化 と PHP復号化

Javaで暗号化したデータを PHPスクリプトに渡して復号化するには、
http://blog.local.ch/archive/2007/10/29/openssl-php-to-java.html 
で紹介されている方法と逆をすればできるはずです。


1.ライブラリインストール
必要なライブラリをインストールします。
http://www.bouncycastle.org/
インストール方法
http://www.langedge.jp/blog/index.php?itemid=150


残念ながら、openssl-php-to-java.html に掲載のJavaプログラムを実行するとキャストエラーが発生します。プログラムを変更しなければなりませんが、なかかな解決が難しそうなエラーです。


そこで、プログラムを変更することはしないで、PEM形式の公開鍵、秘密鍵をJavaが直接扱える形式を変換することにします。 形式を変換することで外部ライブラリを使う必要がなくなります。ただし、PHP では公開鍵、秘密鍵はPEM形式である必要がありますので、同じ鍵が複数のフォーマットで存在することになります。


2.JavaによるデータのRC4暗号化と RC4暗号キーの公開鍵暗号化


//データをRC4暗号化する
//  plainKey = RC4暗号鍵
//  plainText = 暗号化するデータ
private static byte[] encryptRC4(byte[] plainKey, byte[] plainText) throws Exception 

SecretKey skeySpec = new SecretKeySpec(plainKey, "RC4");  
Cipher cipher = Cipher.getInstance("RC4");  
cipher.init(Cipher.ENCRYPT_MODE, skeySpec); 
return cipher.doFinal(plainText);
}


// RC4暗号キーを公開鍵で暗号化する
//  plainKey = RC4暗号キー
//  path      = 公開鍵ファイル
private static byte[] encryptRSA(byte[] plainKey, String path) throws Exception

{
File keyFile = new File(path);
byte[] encodedKey = new byte[(int)keyFile.length()];  
FileInputStream fis = new FileInputStream(keyFile); 
fis.read(encodedKey); fis.close();
X509EncodedKeySpec publicKeySpec = 
new X509EncodedKeySpec(encodedKey);
KeyFactory kf = KeyFactory.getInstance("RSA" );
PublicKey pubKey = kf.generatePublic(publicKeySpec);
Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.ENCRYPT_MODE, pubKey);
return rsa.doFinal(plainKey); 
}


byte[] ciphertext = encryptRC4(plainKey.getBytes(), plainText.getBytes());
byte[] cipherKey = encryptRSA(plainKey.getBytes(), path);
 
PHPでデータを公開鍵で暗号化するには以下のようにします。

$public_key = openssl_pkey_get_public(file_get_contents( <PEM形式公開鍵ファイル> ));
openssl_seal($data, $encrypted_data, $env_key, array($public_key));

プレーンデータ $dataが $public_key で RC4暗号化されます。RC4の暗号鍵は自動生成されます。結果として $encrypted_data に暗号化されたデータが、$env_key[0]に RC4暗号鍵が暗号化されたものがセットされます。

つまり、$encrypted_data = ciphertext, $enc_key[0] = cipherKey です。ただし、ciphertext と cipherKeyはバイナリですので、BASE64に変換後、文字列にしてから PHP に渡すことで、PHP側で復号化できます。

String ciphertextBase64String = new String ( Base64.encodeBase64(ciphertext), "UTF-8");

String cipherKeyBase64String = new String( Base64.encodeBase64(cihperKey), "UTF-8");

上記コードのBase64変換は Apache Commons ライブラリの場合です。

3.PHP側での暗号されたデータと鍵の復号化
$private_key = openssl_pkey_get_private(file_get_contents(<PEM形式秘密鍵パス>));

$encrypted_data = base64_decode(<base64化されたciphertext>);
$env_key[0]     = base64_decode(<base64化されたcipherKey >);

// 復号処理
if (!openssl_open($encrypted_data, $decrypted_data, $env_key[0], $private_key))
 {
die(openssl_error_string() . "\n");
 }

// 鍵リソースの解放
openssl_free_key($private_key);

$decrypted_dataが復号化されたプレーンテキストになります。



4.結果
Windows 上のJavaで暗号化したものは PHP で復号化できますが、Linux上のJavaで暗号化したものはPHPで復号エラーが発生します。Javaで復号化する限り問題はまったくありません。

Windows 上でRC4鍵を暗号化すると、毎回異なる暗号データが生成されるがLinux上では毎回同じ暗号データが生成されてしまいます。 WindowsとLinuxでは異なるバージョンの Java を使っているための相違だと考え、両者で同じバージョンを使ってみましたが問題は解決しませんでした。.


5. Linux対応
Linux上の PHPでは復号化処理中に PCKS1パッディングエラーとなっていましたので、それに関連のエラーであることは予想できましたが、対応策が分かりませんでした。Webを検索しているとRSA暗号のパディング指定ができることがわかりました。 


encryptRSA(byte[] plainKey, String path) 関数の1行を以下のように変更します。


変更前 Cipher rsa = Cipher.getInstance("RSA");
変更後 Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");


パディングを指定すると Linuxでも Javaで暗号化したものをPHPで復号化できるようになりました。




6. OpenSSL vs mcrypt
PHPと Javaの連携では PHPの mcrypt を利用する方法があるようです。



レンタルサーバでは OpenSSLはほとんど利用できるようですが、mcryptパッケージは利用できないことがあります。レンタルサーバを使うのであれば、PHPでの暗号化はOpenSSLを前提とした方が可搬性があるように思われます。


Java RSA 暗号化(鍵のフォーマット変換)

JavaではPEM形式を扱えないため DER形式に変換してから使います。秘密鍵は PKCS8フォーマットにします。以下 PEM 形式の変換方法です。

1. 秘密鍵作成 (PEM形式)
openssl genrsa -out private_key.pem

2. 公開鍵作成(PEM形式)
openssl rsa -pubout -in private_key.pem -out public_key.pem

3. 公開鍵のPEMからDER形式へ変換 
openssl rsa -inform pem -outform der –pubin -in public_key.pem  -out public_key.der  
openssl rsa -inform pem -outform der  -in public_key.pem -pubin -out public_key.der

(-pubin オプションの指定場所が Linux/Windows で異なるため注意要)

4. 秘密鍵のPEMからDER形式(PKCS8)へ変換

openssl pkcs8 -topk8 -in private_key.pem -inform pem –nocrypt -out private_key.der -outform der
openssl pkcs8 -topk8 -in private_key.pem -inform pem -out private_key.der -outform der -nocrypt 
(-nocrypt オプションの指定場所が Linux/Windows で異なるため注意要. パスワードを尋ねられたら -nocypt オプションは無効状態)



1.Java公開鍵暗号化
//path -> 公開鍵のパス
File keyFile = new File(path);        
byte[] encodedKey = new byte[(int)keyFile.length()];

FileInputStream in = new FileInputStream(keyFile);
in.read(encodedKey);
in.close();
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedKey);

KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(publicKeySpec);

Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] cipherText = rsa.doFinal(plainTest);


2.Java秘密鍵復号化
//path -> 秘密鍵のパス
File keyFile = new File(path);       

byte[] encodedKey = new byte[(int)keyFile.length()];
new FileInputStream(keyFile).read(encodedKey);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");

PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedKey);

PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plainText = rsa.doFinal(cipherText);