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


関連記事

laravel logo

Laravelのコントローラの基礎

1 はじめに2 ルーティング2.1 使用可能なメソッド2.2 リダイレクト2.3 Viewの返却2.4 ルートパラメータ2.5 ミドルウェア2.6 プレフィックス3 バリデーション3.1 コントローラ ...

aws

ALB+EC2な環境でhttpをhttpsにリダイレクトする

1 はじめに1.1 前提条件2 ALBの設定3 Nginxの設定3.1 注意点4 さいごに はじめに httpsに対応済みのWebサイトの場合、httpでアクセスされた時にhttpsでリダイレクトする ...

rails

【Puma】アプリサーバのチューニング

1 はじめに2 チューニングで注意する項目3 子プロセスの数3.1 最低3つの子プロセスを割り当てる3.2 最大子プロセス数3.3 CPUコア数と子プロセス数3.4 なぜPumaの子プロセス数を増やす ...

rails

Ruby、Railsの時間に関するメソッドを使用してみた

はじめに 普段PHPのお仕事をしています、tonnyです。 半年程前からRuby on Railsの勉強を始めました。 今回はよく使う時間に関するメソッドついてまとめたいと思います。 目次 1 はじめ ...

rails

RailsでSidekiqを使ってみた

1 はじめに1.1 実行環境2 前準備2.1 Redisのインストール3 Railsの準備3.1 プロジェクト作成3.2 新規登録時にAPIを叩く4 Sidekiqの準備4.1 Workerを作る5 ...

フォロー

follow us in feedly

blog-page_side_responsive

2019年6月
« 5月 7月 »
 1
2345678
9101112131415
16171819202122
23242526272829
30 

アプリ情報

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