BackEnd

[Laravel]データベースの暗号化について考えてみる

投稿日:2019年6月27日 更新日:

はじめに

こんにちは。現在関わっているプロジェクトで、パスワードに限らずメールアドレスや電話番号・住所などの個人情報は暗号化してデータベースに格納してほしい、という要件がありました。
Laravelでは認証時どのようにパスワードを扱っているのか、暗号化と復号はどのように実装するか、パフォーマンスにどのくらい影響はあるのか…など、いざ考えてみると、きちんと把握できていない点が多かったことに気付かされます。
コードを追ったり実際に書いてみたりして、Laravelにおける自分なりの暗号化の実装パターンを考察してみました。

環境

  • PHP 7.3.6
  • Laravel 5.8.26
  • MariaDB 10.4.6

Laravelでの暗号化

チュートリアルでおなじみ(?)のmake:authで生成されるユーザ登録処理は以下のような実装になっています。

パスワード暗号化はHash::make()ですね。LaravelのFacadeによる呼び出し方なので、Hashというクラスは存在するものの、makeというstaticメソッドはありません。
実際に呼び出されているのはIlluminate/Hashing/BcryptHashermake()メソッドで、こんな実装になっています。

PHP推奨のハッシュ関数であるpassword_hash()が使われていて、アルゴリズムはCRYPT_BLOWFISHであることが分かります。
なお$optionscostというオプションが設定されており、configのデフォルト値「10」が渡されています。
password_hash()の挙動について詳しくは公式ドキュメントをご覧ください。
ハッシュ関数に渡す設定値はLaravel内のconfig/hashing.phpに記述されていますので、気になる方はご覧になってみてください。

実際に登録されるレコードはこのようになります。

暗号化されたカラムは、そのままではLIKE検索ができない

問題点

今回あれこれと調べたり考えたりしたきっかけはこの点です。パスワードはデータの性格上、文字列の完全一致検索ができれば問題ありません。…が、メールアドレスや住所はそうもいかず、部分一致(LIKE)検索をする必要が出てきます。
上述したHash::make()では常に60文字のハッシュを生成します。例えば「東京都中央区日本橋」でも「東京」でも異なる60文字の、しかも毎回異なるハッシュ値を返します。これではLIKE検索のしようがありません。

なので$users = User::where('email', 'LIKE', "%{Hash::make($email)}%")->get();みたいな検索はできません。

解決案1:全件取得してPHP側でがんばって検索する

ボツ案です。正確には「DBからいったん全件取得してPHP側で復号・文字列の部分検索処理を実装する」という方法でした。
当然ながら、毎回全件取得してたらパフォーマンス劣化が激しいです。件数が増えるほど激しく劣化するでしょう。データベース-PHP間の通信コストも、PHPの処理コストも恐らく結構なことになると思います。

解決案2:暗号化も復号もMySQL側が担う

もっとスマートなやり方があるんじゃないかとは思うのですが、ひとまずこの方法に落ち着いています。
MySQLには暗号化用のAES_ENCRYPT()、復号用のAES_DECRYPTがありますので、これらを使用して、実装例としては以下のようになります。

  • 登録時はemailの値をSQL文として評価させるため\DB::raw()を使っています。
  • 検索時にもSQL文のまま評価させるためにwhereRaw()を使います。

名前が16進数の文字列で格納されています。

補足1:HEX/UNHEX

AES_ENCRYPT()は通常の文字列ではなくバイナリを返すため、そのままではvarchar型のカラムに格納できません。そのため何かしらの文字列に変換する必要があるので、ここではHEXを使っています。カラムがVARBINARY型になっていれば、文字列化は不要です。
検索時はまずUNHEXでバイナリに戻してからAES_DECRYPTで復号することでLIKE検索が可能になります。

補足2:CONVERT

検索はAES_DECRYPT(UNHEX())で可能ですが、表示時はそのままだと16進数表記になってしまいます(マルチバイト文字限定)ので、CONVERTを噛ませる必要があります。メールアドレスなどの半角英数であれば不要となります。

AES_DECRYPTの第2引数で渡しているハッシュ値は、Laravelの.envファイルに保存されているAPP_KEYです。

さいごに

フレームワークに任せず、アプリケーションに生SQLを書くことになってしまうので、あまりスマートな解決方法ではない気がしています。また、検索時は複合処理を毎回行うことになりますので、パフォーマンス面でも若干不安です。
大量のクエリをさばく必要がある場合はまた別の方法を模索することになると思いますが「性能より安全面」という要件であれば、今回紹介した方法も、実装例のひとつになるかと考えています。

おすすめ書籍

PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応 PHPフレームワーク Laravel入門

blog-page_footer_336




blog-page_footer_336




-BackEnd
-, ,

執筆者:


comment

メールアドレスが公開されることはありません。

CAPTCHA


関連記事

Docker上のLaravelのログをFluentdに出力する

1 はじめに2 環境3 Fluentdについて4 目的5 Fluentd本体はdocker-composeで導入5.1 fluentd5.2 db5.3 app5.4 web6 Laravelからログ ...

rails

RailsでERBからJavaScriptにhashを渡す方法

1 はじめに2 カスタムデータ属性とは3 実装例3.1 コントローラの実装3.2 ビューの実装3.3 実行結果4 さいごに はじめに 以前、選択したプルダウンメニューに応じて別のプルダウンメニューの内 ...

js

TypeScriptでJavaScriptのライブラリを使用するには?

1 はじめに2 対応方法2.1 npmで@typesからインストールする2.2 自分で型定義ファイルを作る3 Declaration Space3.1 Type Declaration Space3. ...

rails

Rails Developer Meetup に参加してきました【2日目】

1 はじめに2 Rails Developer Meetup3 テストのないレガシーなRailsアプリをリファクタした話3.1 なぜリファクタリングしたのか3.2 コードを3種類に分類する3.3 モデ ...

laravel logo

Laravelの開発環境構築

1 はじめに2 開発環境構築2.1 Homestead2.2 Laradock3 Laradockで開発環境構築3.1 Laradockのダウンロード3.2 コンテナの設定ファイルを作成3.3 コンテ ...

フォロー

follow us in feedly

blog-page_side_responsive

東京改造計画(NewsPicks Book)
2019年6月
 1
2345678
9101112131415
16171819202122
23242526272829
30 

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。