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


関連記事

rails

はじめてのrails、まずはローカル環境構築してみる

1 はじめに2 必要なライブラリ・ツールのインストール2.1 homebrew, rbenv2.2 rbenv-communal-gems3 最新安定版のrubyをインストール4 bundler, r ...

laravel logo

Laravelでテストコードを書くには? Featureテスト/Unitテスト

1 はじめに2 FeatureとUnitの使い分け3 テスト用データベースの準備4 Featureテスト4.1 テスト対象のコード4.2 テストコードの実装4.3 テスト結果の検証4.3.1 ステータ ...

Laravelの開発環境をdocker-composeで一から構築してみる

1 はじめに2 nginxでwebサーバを立てる2.1 default.conf作成2.2 index.html作成2.3 nginxコンテナ起動3 nginxでPHP-FPMを動作させる3.1 do ...

laravel logo

LaravelでのCookieファサード利用

1 はじめに2 基本メソッド2.1 付与2.2 取得2.3 削除3 他メソッド3.1 forever3.2 getQueuedCookies3.3 unqueue4 ミドルウェア4.1 AddQueu ...

laravel logo

[Laravel] ログの扱い方 [5.8]

1 はじめに2 環境3 Monologについて4 コード上で設定する方法5 config/logging.phpで設定する方法6 おまけ:ログレベルについて7 さいごに8 おすすめ書籍 はじめに こん ...

フォロー

blog-page_side_responsive

2019年6月
 1
2345678
9101112131415
16171819202122
23242526272829
30  

アプリ情報

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