[解決済み】「ログインしたままにする」 - 最適な方法とは?
質問
私のウェブアプリケーションでは、ユーザーがログインした後にその情報を保存し、アプリ内のページを移動する際にもその情報を維持するためにセッションを使用しています。この特定のアプリケーションでは、私は
user_id
,
first_name
と
last_name
の人です。
ログイン時に、ユーザーのマシンに2週間クッキーを保存し、ユーザーがアプリに戻ったときに同じ内容でセッションを再開する、「ログイン状態を保持する」オプションを提供したいのですが、可能ですか?
このような場合、どのような方法があるのでしょうか?私は
user_id
というのも、あるユーザーが他のユーザーの身元を偽造しようとすることが容易になるように思われるからです。
解決方法は?
この目的のために、ユーザーデータ、またはユーザーデータから派生したものをクッキーに入れるのであれば、何か間違ったことをしていることになります。
ほらね。私はそれを言った。では、実際の答えに移りましょう。
ユーザーデータをハッシュ化することの何が問題なのか、あなたは尋ねたでしょうか?それは、「露出の表面」と「無防備さによるセキュリティ」に尽きます。
あなたが攻撃者だと想像してください。あなたのセッションで、remember-meに設定された暗号クッキーを見ます。それは32文字の幅です。おや。これはMD5かもしれない...
また、あなたが使用したアルゴリズムを相手が知っていることをちょっと想像してみましょう。例えば
md5(salt+username+ip+salt)
これで、攻撃者は、quot;salt"をブルートフォース(実際はソルトではありませんが、これについては後述します)すれば、自分のIPアドレスの任意のユーザ名で好きなだけ偽トークンを生成できるようになりました!このように、攻撃者は、自分のIPアドレスの任意のユーザ名で好きなだけ偽トークンを生成できます。しかし、ソルトをブルートフォースするのは難しいでしょう? もちろんです。しかし、現代のGPUはそれを非常に得意としています。そして、ソルトに十分なランダム性を持たせない限り(十分に大きくしない限り)、ソルトはすぐに破壊され、あなたの城の鍵も一緒に破壊されてしまうでしょう。
要するに、あなたを守ってくれるのは塩だけで、実はあなたが思っているほど守ってくれてはいないのです。
でも、待てよ!?
これらはすべて、攻撃者がアルゴリズムを知っていることを前提にしています。秘密で紛らわしいなら、安全でしょう? 間違い . その考え方には、名前がついています。 無防備さによるセキュリティ ということです。 決して に依存することになります。
より良い方法
より良い方法は、id以外のユーザーの情報をサーバーから絶対に出さないことです。
ユーザーがログインしたときに、大きな(128から256ビット)ランダムなトークンを生成します。そのトークンをユーザーIDにマップするデータベース・テーブルに追加し、クッキーでクライアントに送信します。
もし、攻撃者が他のユーザーのランダムトークンを推測してしまったら?
さて、ここで少し計算してみましょう。私たちは128ビットのランダムトークンを生成しています。ということは、次のようなものがあるということです。
possibilities = 2^128
possibilities = 3.4 * 10^38
では、この数字がいかに途方もなく大きいかを示すために、インターネット上のすべてのサーバー(今日は50,000,000とします)が、それぞれ毎秒1,000,000,000の割合でこの数字をブルートフォースしようとするとどうでしょう?現実には、このような負荷がかかるとサーバーは溶けてしまいますが、このような状況を再現してみましょう。
guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000
つまり、1秒間に50兆回の推測が可能です。速いですねー。そうでしょ?
time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000
time_to_guess = 6,800,000,000,000,000,000,000
だから6.8垓秒...。
もっと親しみやすい数字にしてみよう。
215,626,585,489,599 years
あるいはもっといい。
47917 times the age of the universe
そう、宇宙の年齢の47917倍もあるんだ...。
基本的に割れることはないです。
では、まとめると。
私が推奨するベターなアプローチは、3つのパーツでクッキーを保存することです。
function onLogin($user) {
$token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
storeTokenForUser($user, $token);
$cookie = $user . ':' . $token;
$mac = hash_hmac('sha256', $cookie, SECRET_KEY);
$cookie .= ':' . $mac;
setcookie('rememberme', $cookie);
}
そして、バリデーションへ。
function rememberMe() {
$cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
if ($cookie) {
list ($user, $token, $mac) = explode(':', $cookie);
if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
return false;
}
$usertoken = fetchTokenByUserName($user);
if (hash_equals($usertoken, $token)) {
logUserIn($user);
}
}
}
注意:トークン、またはユーザーとトークンの組み合わせを使って、データベースのレコードを検索しないでください。必ずユーザーに基づいてレコードを取得し、取得したトークンを後で比較するために、タイミングセーフな比較関数を使用してください。 タイミングアタックの詳細 .
さて、それは
非常に
が重要です。
SECRET_KEY
は暗号化された秘密である(たとえば
/dev/urandom
および/または高エントロピーな入力に由来する)。また
GenerateRandomToken()
は強力なランダムソースである必要があります (
mt_rand()
は十分に強いとは言えません。のようなライブラリを使用します。
RandomLib
または
ランダムコンパット
または
mcrypt_create_iv()
と
DEV_URANDOM
)...
は
hash_equals()
を防ぐためのものです。
タイミングアタック
.
PHP 5.6 未満のバージョンを使用している場合、関数
hash_equals()
はサポートされていません。この場合
hash_equals()
をtimingSafeCompare関数に置き換えます。
/**
* A timing safe equals comparison
*
* To prevent leaking length information, it is important
* that user input is always used as the second parameter.
*
* @param string $safe The internal (safe) value to be checked
* @param string $user The user submitted (unsafe) value
*
* @return boolean True if the two strings are identical.
*/
function timingSafeCompare($safe, $user) {
if (function_exists('hash_equals')) {
return hash_equals($safe, $user); // PHP 5.6
}
// Prevent issues if string length is 0
$safe .= chr(0);
$user .= chr(0);
// mbstring.func_overload can make strlen() return invalid numbers
// when operating on raw binary strings; force an 8bit charset here:
if (function_exists('mb_strlen')) {
$safeLen = mb_strlen($safe, '8bit');
$userLen = mb_strlen($user, '8bit');
} else {
$safeLen = strlen($safe);
$userLen = strlen($user);
}
// Set the result to the difference between the lengths
$result = $safeLen - $userLen;
// Note that we ALWAYS iterate over the user-supplied length
// This is to prevent leaking length information
for ($i = 0; $i < $userLen; $i++) {
// Using % here is a trick to prevent notices
// It's safe, since if the lengths are different
// $result is already non-0
$result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
}
// They are only identical strings if $result is exactly 0...
return $result === 0;
}
関連
-
[解決済み】phpのob_start()の使い道は?
-
phpのAllowed memory size of 134217728 bytes枯渇問題の解決法
-
[解決済み】警告: file_get_contents(): https:// ラッパーがサーバー構成ですべて無効になっています。
-
[解決済み] YouTube APIからYouTubeビデオのサムネイルを取得する方法を教えてください。
-
[解決済み] 後で平文を取り出すためのユーザーパスワードの保管について、倫理的にどのように取り組むべきでしょうか?
-
[解決済み] JWT(JSONウェブトークン)の有効期限を自動的に延長する機能
-
[解決済み] JSONウェブトークンの無効化
-
[解決済み] セッションとは何ですか?どのように機能するのですか?
-
[解決済み】XKCDコミック「Bobby Tables」のSQLインジェクションはどのように動作するのでしょうか?
-
[解決済み】クッキーとセッションの比較
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】phpのob_start()の使い道は?
-
[解決済み】Apache + PHPで「ヘッダの前にスクリプトの出力が終了する」件
-
[解決済み】Xampp ローカルホスト/ダッシュボード
-
[解決済み】foreach()に与えられた引数が無効です。)
-
[解決済み] 警告:mysqli_fetch_array()は、パラメータ1がmysqli_resultであることを期待する、オブジェクトはで指定された。
-
[解決済み】PDOException SQLSTATE[HY000] [2002] そのようなファイルまたはディレクトリがありません。
-
[解決済み] Uncaught SyntaxError: JSON の位置 1 に予期しないトークン o があります。
-
phpのAllowed memory size of 134217728 bytes枯渇問題の解決法
-
[解決済み] mysqli_fetch_assoc() は、パラメータ 1 が mysqli_result であることを期待し、boolean が与えられる [重複] 。
-
[解決済み] mysql_field_nameを新しいmysqliに変更します。