WebEngine

だらだらと綴る技術系メモ

パスワードをデータベースに保存するときは、一緒に塩を入れよう

セキュアに保存する -> 漏れた場合を想定する

データベース(以下DB)にパスワードを保存するとき、そのまま (平文で)保存してはいけません。データが漏れた場合に、パスワードが 見た瞬間わかってしまうからです。
パスワードを保存する場合、暗号化してからDBへインサートします。

実装例

手法は多種多様ですが、ここではSALTを使って暗号化 してみたいと思います。PHP + MySQLでの想定です。(ちなみにPDO)
SALTはランダムな文字列で、各ユーザごとにそれぞれ異なる ものを用意します。

<?php

/* 暗号化関数 */
function saltAngo($password, $salt) {
    $saltPassword = crypt($password, $salt);
    return $saltPassword;
}

/* ランダムな文字列を生成 */
function mkSalt($length = 8) {
    return substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', $length)), 0, $length);
}


<?php
/* 上記ファイルの関数をrequire_onceなどで使えるようになっているとします。 */

$user = 'hoge';
$password = 'hogehoge';

$stmt = $pdo->prepare('INSERT INTO userdata SET user = ?, password = ?, salt = ?');

// トランザクション開始
$pdo->beginTransaction();

try {
    $stmt->bindValue(1, $user);
    $salt = mkSalt(); // ランダムな文字列作成
    // パスワードはsaltAngo関数で暗号化
    $password = saltAngo($password, $salt);
    $stmt->bindValue(2, $password);
    $stmt->bindValue(3, $salt); // 使ったSALTもDBへ保存
    $stmt->execute();

    // コミット 
    $pdo->commit();
} catch (PDOException $e) {
    // エラーでロールバック 
    $pdo->rollBack();
    throw $e;
}


ログイン時は、DBに保存された同じSALTを使います。同じ暗号化のロジックを使用すれば、同じ結果が 戻ってくることを利用するのです。

<?php
/* 実際はちゃんと無毒化しましょう */
$user = $_POST['user'];
$password = $_POST['password'];

$stmt = $pdo->prepare('SELECT * FROM userdata WHERE user = ?');
$stmt->bindValue(1, $user);
$stmt->execute();

$row = $stmt->fetch(PDO::FETCH_ASSOC);

if (!empty($row)) {
    $salt = $row['salt'];
    $password = crypt($password, $salt); // 保存時と同じSALTで復号化

    // パスワードが一致しているか
    if ($row['password'] === $password) {
        echo 'ログイン完了';
    } else {
        echo 'ユーザ名かパスワードが違います';
    }
} else {
    echo 'そのようなユーザは登録されていません';
}


ここでは説明を簡単にするため、弱い暗号化で済ませています。実際には、もっと 強いロジックを考えましょう。