序文
テーブル定義から始めます。
- UserID
- Fname
- Lname
- Email
- Password
- IV
変更点は次のとおりです。
- フィールド
Fname
、Lname
およびOpenSSLがEmail
提供する対称暗号を使用して暗号化されます。
IV
フィールドが格納されます初期ベクトル暗号化に使用します。ストレージ要件は、使用する暗号とモードによって異なります。これについては後で詳しく説明します。
Password
フィールドには、使用してハッシュされる一方向パスワードハッシュを、
暗号化
暗号とモード
最適な暗号化方式とモードを選択することはこの回答の範囲を超えていますが、最後の選択は暗号化キーと初期化ベクトルの両方のサイズに影響します。この投稿では、16バイトの固定ブロックサイズと16、24、または32バイトのキーサイズを持つAES-256-CBCを使用します。
暗号化キー
適切な暗号化キーは、信頼性の高い乱数ジェネレータから生成されるバイナリBLOBです。次の例が推奨されます(> = 5.3):
$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe
これは1回または複数回実行できます(暗号化キーのチェーンを作成する場合)。これらはできるだけプライベートにしてください。
IV
初期化ベクトルは、暗号化にランダム性を追加し、CBCモードに必要です。これらの値は、理想的には1回だけ(技術的には暗号化キーごとに1回)使用する必要があるため、行の任意の部分を更新すると、再生成されます。
IVの生成に役立つ関数が提供されています。
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
例
以前の$encryption_key
とを使用して、名前フィールドを暗号化しましょう$iv
。これを行うには、データをブロックサイズに埋め込む必要があります。
function pkcs7_pad($data, $size)
{
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
$name = 'Jack';
$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
ストレージ要件
IVと同様に、暗号化された出力はバイナリです。これらの値をデータベースに保存するには、BINARY
またはなどの指定された列タイプを使用しますVARBINARY
。
IVと同様に、出力値はバイナリです。これらの値をMySQLに保存するには、BINARY
またはVARBINARY
列の使用を検討してください。これがオプションでない場合は、base64_encode()
またはを使用してバイナリデータをテキスト表現に変換することもできます。これをbin2hex()
行うには、33%〜100%の追加の記憶域が必要です。
解読
保存された値の復号化も同様です。
function pkcs7_unpad($data)
{
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];
$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
認証された暗号化
秘密キー(暗号化キーとは異なる)と暗号テキストから生成された署名を追加することにより、生成された暗号テキストの整合性をさらに向上させることができます。暗号文が復号化される前に、まず署名が検証されます(できれば、一定時間の比較方法を使用)。
例
// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);
// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;
// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);
if (hash_equals($auth, $actual_auth)) {
// perform decryption
}
以下も参照してください。 hash_equals()
ハッシュ
リバーシブルなパスワードをデータベースに保存することはできる限り避けなければなりません。内容を知るのではなく、パスワードを確認したいだけです。ユーザーがパスワードを紛失した場合は、元のパスワードを送信するのではなく、リセットできるようにすることをお勧めします(パスワードのリセットは限られた時間でのみ実行できることを確認してください)。
ハッシュ関数の適用は一方向の操作です。その後、元のデータを明かさずに安全に検証に使用できます。パスワードの場合、ブルートフォース方式は、長さが比較的短く、多くの人のパスワードの選択が不十分であるため、それを明らかにするための実行可能なアプローチです。
MD5やSHA1などのハッシュアルゴリズムは、既知のハッシュ値に対してファイルの内容を検証するために作成されました。これらは非常に最適化されており、この検証を可能な限り高速にしながら、正確性を保ちます。出力スペースが比較的限られているため、既知のパスワードとそれぞれのハッシュ出力であるレインボーテーブルでデータベースを構築するのは簡単でした。
ハッシュする前にパスワードにソルトを追加すると、レインボーテーブルが役に立たなくなりますが、最近のハードウェアの進歩により、ブルートフォースルックアップが実行可能なアプローチになりました。故に、故意に遅く、最適化が単純に不可能であるハッシュアルゴリズムが必要なのはそのためです。また、既存のパスワードハッシュを検証して将来の証拠とする機能に影響を与えることなく、より高速なハードウェアの負荷を増やすことができます。
現在利用可能な2つの人気のある選択肢があります。
- PBKDF2(パスワードベースのキー派生関数v2)
- bcrypt(別名Blowfish)
この回答では、bcryptの例を使用します。
世代
パスワードハッシュは次のように生成できます。
$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);
$hash = crypt($password, $salt);
塩を用いて生成されopenssl_random_pseudo_bytes()
、次いで介して実行されるデータのランダムブロブを形成するbase64_encode()
とstrtr()
、必要なアルファベットと一致します[A-Za-z0-9/.]
。
このcrypt()
関数は、アルゴリズム($2y$
Blowfishの場合)、コスト係数(13の係数は3GHzマシンで約0.40秒かかります)、および22文字のソルトに基づいてハッシュを実行します。
検証
ユーザー情報を含む行をフェッチしたら、次の方法でパスワードを検証します。
$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash
$given_hash = crypt($given_password, $db_hash);
if (isEqual($given_hash, $db_hash)) {
// user password verified
}
// constant time string compare
function isEqual($str1, $str2)
{
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;
}
パスワードを検証するには、crypt()
もう一度呼び出しますが、以前に計算したハッシュをソルト値として渡します。指定されたパスワードがハッシュと一致する場合、戻り値は同じハッシュを生成します。ハッシュを検証するには、一定時間の比較関数を使用してタイミング攻撃を回避することがしばしば推奨されます。
PHP 5.5でのパスワードハッシュ
PHP 5.5では、上記のハッシュ方法を簡略化するために使用できるパスワードハッシュ関数が導入されました。
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
そして確認:
if (password_verify($given_password, $db_hash)) {
// password valid
}
も参照してください:password_hash()
、password_verify()