BlogのトラックバックURL発行時の認証をreCAPTCHA v2に切り替えたということを紹介した。
このときMailhideは古いreCAPTCHAのAPIを使ってるから、将来使えなくなるかもと書いた。
実際どうなるかはわからないが、自分で相当する物を作ってもよいのでは? ということで作ってみた。
ここで「私はロボットではありません」にチェックを入れると、メールアドレスが出てくる。
チェックが入ると、data-callbackに指定したJavaScriptの関数を呼び出す機能があるので、
自動的に認証データをAjaxでサーバーに送って、サーバー側で確認が取れたらメールアドレスのリンクを返してもらうと。
チェックを入れる→送信ボタンの2ステップではなく、チェックを入れるの1ステップだけで出来た方がいいんじゃないかという発想だ。
Invisible reCAPTCHAで無操作で表示させるという方法もあるかも知れないが、1ステップはあったほうがいいかなと。
reCAPTCHA以外にも工夫がある。それがURLにメールアドレスを暗号化して埋め込んでいること。
サーバー側ではメールアドレスを保持していなくて、メールアドレスを解読するのに必要なデータはURLにあると。
共通鍵暗号方式のうちCBCモードでは、暗号化・復号化のときにキーと初期化ベクトル(IV)を使う。
暗号化はブロックごとに行われるが、CBCモードでは前ブロックの内容に依存して暗号化結果が変わる。
すなわち同じ内容のブロックがあったとしても前のブロックが違えば、暗号化結果は同じにならないということで、攻撃に強くなる。
最初のブロックにとって前のブロックに相当するデータがIVだ。
IVが違えば、同じデータ、同じキーでも暗号化結果は変わる。なのでランダムなIVを最初に決めると、攻撃に強くなると。
URLのパラメータ、encが暗号化去れたデータで、ivはIV、これと秘密のキーをつかって復号化して、メールアドレスを表示している。
具体的なやり方だが、まず暗号化はこんな具合。
$iv=openssl_random_pseudo_bytes(16);
$enc=openssl_encrypt($addr,'aes-128-cbc',$key,0,$iv);
$iv_b64=rtrim(strtr(base64_encode($iv),'+/','-_'),'=');
$enc_b64=rtrim(strtr(base64_encode($enc),'+/','-_'),'=');
PHPで書いたけど、他の言語でOpenSSLを使う場合も似たようなもんだろう。
まず、128bit=16bytesのランダムなデータを作成して$ivに格納する。
それで暗号化対象のデータ$addr、キー$key,IV $iv と 暗号化方式としてAES-128-CBC を指定して暗号化する。
暗号化されたデータ$enc、IV $ivはバイナリなので、URLにするにはBase64で変換して、なおかつ +,/,= の3文字を除去する必要がある。
+,/は-,_に置き換え、パディングの=は除去してもさほど問題はないので、単純に消す。
そうやって変換した $enc_b64と$iv_b64をURLに埋め込んでいる。
復号化は単純にこの逆をやればよいだけのこと。
$iv =base64_decode( strtr($_GET['iv'],'-_','+/') );
$enc=base64_decode( strtr($_GET['enc'],'-_','+/') );
$dec=openssl_decrypt($enc,'aes-128-cbc',$key,0,$iv);
E-mailアドレス以外にも電話番号の保護にも使えるかなぁとか思いつつ、自分以外も使えるようにしてみた。
動作は保証しないけど、ご自由にご利用ください。
使う人がいるか知らないけどAPIも公開してある。
E-mailアドレスと電話番号に対応しているが、表示方法だけの差で本質的なところに差はない。mailto:をtel:に置き換えたぐらい。
実はE-mailアドレス・電話番号以外の形式のデータの保護もできてしまうと。(特に内容は制限していない)
そもそもE-mailアドレスをBOTだけに隠すというのがよい選択肢かという話ではあるんだけど。
画像化したり、@を他の文字に置き換えたりする方法に比べれば優れた方法だとは思うんだけど、
そもそもE-mailアドレスというものを公開するというのがどうなのよという話がある。
問い合わせフォームのような形にして、E-mailアドレスを公開しないという選択肢もあるから。
その問い合わせフォームにreCAPTCHAを組み込んでBOTよけをするということもできるわけだし。
というわけで、これは本当にニーズがあるのかとは思ったのだが、
作ってておもしろかったので、せめて自分は使うと言うことで。