PHP: 公開鍵と秘密鍵でデータ通信(OpenSSL)

この記事は次のブログに引っ越しました。

お客さんに提供しているWEBサービスのサーバと、メンテナンス用のサーバ間でデータ同期をRESTで行う必要が出てきた。その際のデータ転送量は少ないものの、ちょいとしたお客さんデータが含まれる可能性が出てきたため、公開鍵・秘密鍵で手軽に暗号化してやりとりをしたかったが、OpenSSL+PHPのまとまった資料がなかなか無い。そこで、自分の整理も含めて、PHPで公開鍵と秘密鍵を利用した暗号化に関する簡易記事を作成してみました。実際のスクリプトこちら(128バイト以上のデータを扱えない問題にも対応しています)。 秘密鍵暗号方式(公開鍵・秘密鍵)の基本的な概念 公開鍵と秘密鍵のペアで動作する。公開鍵・秘密鍵のいずれか片方の鍵でロックをかけた(暗号化した)ものは、もう片方の鍵でないとアンロック(復号)出来ない。(※1) 相手に渡すのは公開鍵のみ。(秘密鍵は渡してはいけない)この点をしっかり把握すれば、公開鍵と秘密鍵の使い方は難しくはないでしょう。※1:本来は暗号化をロック(Lock)ではなくエンクリプト(Encrypt)、復号はアンロック(UnLock)ではなくデクリプト(Decrypt)というのが正しいのですが、この記事では鍵という言葉からイメージしやすいようにロックとアンロックという言い方をしています。一方、ロックをかけた鍵でアンロックもできるタイプの鍵を共通鍵といいます。パスワード付きZIPなどが良い例でしょう。共通鍵方式は暗号化も復号もその1つの鍵で出来るため、鍵を全員で共有するなど、手軽な反面、誰かが鍵を流出すると全員が影響を受けるという危険性を持っています。それをさけるために、都度、別の鍵を作ったとしても、暗号化するファイルが増えれば鍵の数も増えるので、管理上面倒な場合があります。それらの問題を解決する1つの方法が、相手に渡す鍵が流出しても大丈夫という公開鍵・秘密鍵形式による暗号化です。
しかし、公開鍵・秘密鍵の場合でも、秘密鍵が流出してしまっては、同じく元も子もありませんので、注意しましょう。 公開鍵・秘密鍵を使ったデータのやりとり 相手からデータを受け取る場合 あなたの公開鍵でロック(暗号化)してもらったデータを、相手からもらう。あなたの秘密鍵でデータをアンロック(復号)する。 相手にデータを渡したい場合(その1)相手の公開鍵でデータをロック(暗号化)して、相手に渡す。相手は、相手の秘密鍵でデータをアンロック(復号)できる。相手にデータを渡したい場合(その2)あなたの秘密鍵でデータをロック(暗号化)して、相手に渡す。相手は、あなたの公開鍵でデータをアンロック(復号)できる。相手にデータを渡したい場合、もちろん(その1)の「相手の公開鍵で暗号化して渡す」のがベストですが、複数人いたら各々の公開鍵で暗号化して、それぞれに渡すのは大変なので、複数人にデータを渡す場合は(その2)の方法が一般的です。ただ、(その2)の場合、公開鍵がわかれば誰でも復号できるので、公開鍵をWEB上で公開している場合などは、新しいペアを作ったり、ファイル共有サービスを使ったりなど、T.P.O.で。そもそも鍵って何?一旦理解すると何てことないのですが、秘密鍵や公開鍵と言われても何かピンとこない。一言で言うと、暗号化・復号するために必要な、覚えられないくらい長いパスワードのような文字列、もしくはそれを保存したファイルと言えると思います。イメージとしては、「ドラクエ復活の呪文=鍵」みたいな感じかしら。■秘密鍵の例 鍵はメモ帳などで開けるテキスト形式になっていて中身は下記のようになっています。※容量確保のためにバイナリ形式で保存される場合もありますが、テキスト形式で保存できるものをPEM形式といいます。■公開鍵の例 一方、公開鍵もテキスト形式になっていて、下記のようになっています。十年くらい前だったか、たぶんSTUDIO VOICEだと思うのですが、PGPという言葉が話題になった頃、坂本龍一がサラリとメールのフッターに、この公開鍵を添えていたのを見て「スゲー!カッコイイ」と思いました。まだ「何のことやら」といった時代に、「わかるやつは問題意識のある同志だ」といった沈黙のメッセージのようなものを感じました。自分は使ってなかったけど。(苦笑) 復号関数と暗号化関数の基本的な使い方 さて、ここまでファイルのロック、アン・ロックという表現を使っていましたが、正しくは「ロックをする」ことを「暗号化する」、「アン・ロック」することを「復号する」と言います。(復号化するという言い方は日本語的に正しくないそうですが、頭痛が痛いでも通じるのと同じで、違和感も同じ程度だと思います。)下記は、関数にデータ・処理の種類・鍵のデータを渡せば、暗号化・復号処理ができるという関数の使い方のイメージです。実際の関数はこちら。■暗号化関数・復号関数の概念暗号化データ = 暗号化関数(元データ, 暗号化の種類, 鍵のデータ);元データ   = 復号関数(暗号化データ, 復号の種類, 鍵のデータ);※「暗号化の種類」には、[公開鍵による暗号化]か[秘密鍵による暗号化]のいずれかを指定します。
※「復号の種類」も、同じく復号に、[公開鍵を使用する]か[秘密鍵を使用する]かを指定します。PHPとOpenSSLを使った公開鍵と秘密鍵の暗号化・復号サンプル・スクリプト 新しい公開鍵と秘密鍵のペアを作る 下記では、公開鍵と秘密鍵を作成し、テキストデータ(PEM形式の文字列)として配列"$aPairKey"に格納しています。//パスワードの設定 $sPassword = "HimitsuNoPassword"; //秘密鍵のリソースを取得 $rPrivateKey = openssl_pkey_new(); //秘密鍵を取得。$sPrivateKeyへ。パスワード保護付。 openssl_pkey_export( $rPrivateKey, $sPrivateKey, $sPassword ); //公開鍵情報を取得。配列として$aPublicKeyへ。 $aPublicKey = openssl_pkey_get_details( $rPrivateKey ); //秘密鍵・公開鍵のペアを配列に入れる。(使いやすいように) $aPairKey = array( 'PRIVATE_KEY' => $sPrivateKey, // 秘密鍵 'PUBLIC_KEY' => $aPublicKey[ 'key' ], // 公開鍵 ); 公開鍵でデータを暗号化する 上記で作成した鍵のペアのうち、公開鍵でデータを暗号化するスクリプト。 //暗号化するデータ $sSampleText = "hogehoge"; //暗号化されたデータ $binResult = ""; //公開鍵の用意 $sPublicKey = $aPairKey['PUBLIC_KEY'];//ここでは上記サンプルの公開鍵を使っています。 //暗号化処理 $bResultCrypt = openssl_public_encrypt($sSampleText, $binResult, $sPublicKey); if( $bResultCrypt ){ echo "暗号化されたデータは{$binResult}です。(生のバイナリデータ)
"; //暗号化されたデータはバイナリデータです。テキストで処理する場合はbase64するといいかも。 $sResult = base64_encode( $binResult ); echo "暗号化されたデータは{$sResult}です。(Base64でテキスト化したもの)
"; } openssl_public_encrypt()は、1回につき128バイト以上のデータは暗号化できないので、大きいデータは、複数回に分けないといけません。ここでは基本的な仕組みだけ説明しています。大きいデータ用の関数は下記を参照してください。 秘密鍵で暗号データを複合化する暗号化されたバイナリデータ($binResult)を渡します。 //鍵のペアを作成した際に使ったパスワード $sPassword = "HimitsuNoPassword"; //秘密鍵 $sPrivateKey = $aPairKey[ 'PRIVATE_KEY' ]; //ここでは上記で作成した秘密鍵を使っています。 //秘密鍵のリソース $rPrivateKey = openssl_get_privatekey( $sPrivateKey, $sPassword ); //復号処理 $bResultDecrypt = openssl_private_decrypt( $binResult, $sResult, $sPrivateKey ); echo "復号されたデータは{$sResult}です。"; }128バイト以上のデータを扱うPHPのOpenSSL暗号化・復号関数 上記でも述べたように、openssl_private_decryptなどは128バイト以上のデータを処理できないため、データを細かくわけたものを暗号化し、また、それらを復号する関数が必要になるわけですが、以下を参考にしてください。※下記スクリプトにバグがありました。修正しました。m(_ _;)m 2012/01/15 新しい秘密鍵(および公開鍵)のペアを作成します function ssl_create_pairkey( $sPassword ){ $rPrivateKey = openssl_pkey_new(); openssl_pkey_export( $rPrivateKey, $sPrivateKey, $sPassword ); $aPublicKey = openssl_pkey_get_details( $rPrivateKey ); $aResponce = array( 'PRIVATE_KEY' => $sPrivateKey, 'PUBLIC_KEY' => $aPublicKey[ 'key' ], ); return $aResponce;} データを暗号化します function ssl_encrypt( $source, $type, $key ){
//Assumes 1024 bit key and encrypts in chunks. $maxlength = 117; $output=''; while( $source ){ $input = substr( $source, 0, $maxlength ); $source = substr( $source, $maxlength ); if( $type == 'private' ){ $ok = openssl_private_encrypt( $input, $encrypted, $key ); } else { $ok = openssl_public_encrypt( $input, $encrypted, $key ); } $output .= $encrypted; } return $output; } データを復号します function ssl_decrypt( $source, $type, $key ){ // The raw PHP decryption functions appear to work // on 128 Byte chunks. So this decrypts long text // encrypted with ssl_encrypt(). $maxlength = 128; $output = ''; while( $source ){ $input = substr( $source, 0, $maxlength ); $source = substr( $source, $maxlength ); if( $type == 'private' ){ $ok = openssl_private_decrypt( $input, $out, $key ); } else { $ok = openssl_public_decrypt( $input, $out, $key ); } $output .= $out; } return $output;} 使い方のサンプル //秘密鍵のパスワード $sPassword = "samplepassword"; //暗号化したいファイル $sSampleData = file_get_contents( "./sample.txt" ); //秘密鍵・公開鍵の作成 $aPairKey = ssl_create_pairkey( $sPassword ); //処理前のデータ表示 echo "▼元のデータ
{$sSampleData}
"; データの暗号化 //公開鍵 $sPublicKey = $aPairKey[ 'PUBLIC_KEY' ]; //秘密鍵 $sPrivateKey = $aPairKey[ 'PRIVATE_KEY' ]; //公開鍵(public)で暗号化 $binResult_encript = ssl_encrypt( $sSampleData, 'public', $sPublicKey ); //表示用暗号化データ(バイナリをASCIIに) $sResult_encript = base64_encode( $binResult_encript ); //処理結果表示 echo "▼暗号化されたデータ
{$binResult_encript}
"; /* ==== データの復号 ==== */ //秘密鍵 $sPrivateKey = $aPairKey[ 'PRIVATE_KEY' ]; //秘密鍵のリソース $rPrivateKey = openssl_get_privatekey( $sPrivateKey, $sPassword ); //秘密鍵(private)で復号 $sResult_decript = ssl_decrypt( $binResult_encript, 'private', $rPrivateKey ); //処理結果表示 echo "▼複合されたデータ
{$sResult_decript}
"; 参考文献: php-opensslの使い方|とげおの仕事日記 phpのプログラムで公開鍵方式の暗号化を実装したいと考えています。- 人力検索はてな