Webシステムのデータ暗号化を調べてみた【OpenSSL】
はじめに
あるカラムのデータを暗号化する必要があり、OpenSSLによる暗号化について調べました。
結論
- 暗号化とは、データを第三者が読めない形式に変換する処理のこと
- ハッシュ化との違い
- ハッシュ化は一方向、暗号化は双方向
- WebシステムではOSSのOpenSSLを使って暗号化するのが一般的
- 大体どの言語/フレームワークにもOpenSSLのラッパーがあるのでそれを使おう
暗号化とは
暗号化とは、データを第三者が読めない形式に変換する処理のことです。
データの盗難や変更、不正使用を防ぐために利用されます。
身近なユースケースとしては、クレジットカード情報の送信や機密データの保管です。
暗号化には共通鍵暗号方式と公開鍵暗号方式があります。その辺りの違いは別の機会に。
ハッシュ化との違い
暗号化とハッシュ化はよく混同されますが、目的と特性が異なります。主な違いは、元のデータに戻せるかどうか(可逆性)です。
項目 | 暗号化 | ハッシュ化 |
---|---|---|
可逆性 | 可逆(復号できる) | 不可逆(元に戻せない) |
主な目的 | データの秘匿、通信の安全性確保 | データの整合性確認、パスワード保存 |
代表例 | AES, RSA | SHA-256, MD5, SHA-3 |
出力結果の長さ | 入力に応じて可変(ブロックサイズによる) | 固定長(アルゴリズムで決まる) |
鍵の有無 | 秘密鍵・公開鍵を利用する | 鍵は不要(ソルトを追加する場合はある) |
元データとの関係 | 復号すれば元のデータと一致 | ハッシュ値から元データを推測できない |
用途の例 | HTTPS通信、ファイル暗号化 | パスワードの検証、改ざん検知 |
OpenSSLとは
OpenSSLとは、暗号化・復号、証明書の生成・検証、SSL/TLS通信などの機能を提供するオープンソースのシステムライブラリです。
C言語で実装されており広く使われているため、多くのプログラミング言語やフレームワークでOpenSSLをラップしたライブラリが提供されています。
キー、初期ベクトル(iv)とは
暗号化において「キー」とは、データを暗号化・復号するための値です。
キーは公開してはいけないものなので、厳重に管理します。
また、「初期ベクトル(IV: Initialization Vector)」とは、暗号化の際に同じ入力データでも異なる出力になるようにするための追加データのことです。
IVは公開しても問題ありません。
IVのおかげで同じパスワードを何度暗号化しても毎回異なる結果が得られ、セキュリティが向上します。
暗号化イメージ(暗号化処理視点)
入力として、平文、鍵、初期ベクトル、暗号モードを渡すと、暗号文が出来上がります。
そして復号する際は、入力として、暗号文、鍵、初期ベクトル、暗号モードを渡すと、平文が出来上がります。
上記図を真逆にすると復号できるというわけですね。
実装イメージ
大体どの言語・フレームワークにもOpenSSLを使った組み込み関数やメソッドが提供されていますので、言語・フレームワークそれぞれ例を挙げます。
PHP
PHPならば、こんな感じ。
<?php
// 暗号化したいデータ
$data = 'This is a secret message.';
// 暗号化方式
$cipher = 'AES-256-CBC';
// 暗号化キー (本来は環境変数などから安全に読み込む)
$key = 'your-super-secret-key-32-bytes';
// IVの長さを取得し、ランダムなIVを生成
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
// 暗号化
$encrypted = openssl_encrypt($data, $cipher, $key, 0, $iv);
// 復号
$decrypted = openssl_decrypt($encrypted, $cipher, $key, 0, $iv);
echo "Original: " . $data . "\n";
echo "Encrypted: " . base64_encode($encrypted) . "\n"; // 表示用にBase64エンコード
echo "Decrypted: " . $decrypted . "\n";
?>
OpenSSL関数群は以下で参照できます。
Laravel
Laravelの場合はこう。Cryptoファザードが窓口になっていて良い感じで使えます。
Laravelコード
<?php
use Illuminate\Support\Facades\Crypt;
// .envファイルに APP_KEY が設定されている必要がある
// 暗号化
$encrypted = Crypt::encryptString('This is a secret message.');
// 復号
$decrypted = Crypt::decryptString($encrypted);
echo "Encrypted: " . $encrypted . "\n";
echo "Decrypted: " . $decrypted . "\n";
?>
Ruby
言語を変えて、Ruby標準なら以下です。
PHP同様、OpenSSLをRuby側でラップしたopensslを利用しているのがわかります。
Rubyコード
require 'openssl'
# 暗号化したいデータ
data = 'This is a secret message.'
# 暗号化方式
cipher = OpenSSL::Cipher.new('AES-256-CBC')
# 暗号化モードに設定
cipher.encrypt
# 暗号化キー (本来は安全な方法で生成・管理する)
key = cipher.random_key
# ランダムなIVを生成
iv = cipher.random_iv
# 暗号化
encrypted = cipher.update(data) + cipher.final
# 復号
decipher = OpenSSL::Cipher.new('AES-256-CBC')
decipher.decrypt
decipher.key = key
decipher.iv = iv
decrypted = decipher.update(encrypted) + decipher.final
puts "Original: #{data}"
puts "Encrypted: #{encrypted.unpack1('H*')}" # 表示用に16進数に変換
puts "Decrypted: #{decrypted}"
Rails
Railsは7系以降ActiveRecordがカバーしているためモデル側で暗号化できます。
詳しくは実装例が解説された以下の記事がおすすめです。
暗号化イメージ(システム全体視点)
視点を変えて図にしてみました。例としてLaravelでの実装を想定しています。
(階層構造は書籍『バックエンドエンジニアのためのインフラクラウド大全』を参考にしました。)
こうしてみると本当に氷山の一角と言いますか、Webシステムにとって暗号化処理実態のほとんどは抽象化され、プログラマとしては暗号化したい内容と素材を渡せばいいんだよね、ということなんだと実感します。
それだけシステム内ではビジネスに近い領域とも言えるので、要件を満たすセキュアで素早い実装を心がけたいです。
終わりに
今までフレームワークが提供するメソッドありきでプログラムを眺めていました。
しかしここ半年ほどレガシーコードと向き合い、フレームワークに頼らない実装方針が求められるようになりました。
フレームワークや言語が変わっても、根底では結局OpenSSLを呼んでいるんだなーと言うことが今回分かり勉強になりました。書籍から学んだシステム階層構造も入れて図で表現することができました。
その他、OpenSSL以外でも暗号化ライブラリがあることや、Goは標準ライブラリで暗号化可能であること(セキュリティを重視し外部のCライブラリへの依存を減らす設計思想、らしい!)も知り、大変興味深いなと思いました。
業務では都合上実装には至らなかったのですが、本記事でまとめることができよかったです。
ここまで読んでいただきありがとうございました。
参考
Goの暗号化が必要になったら以下参照
Discussion