Tamam, bana açık açık koyalım: Eğer kullanıcı verileri ya da bu amaç için bir çerez içine kullanıcı verilerinden türetilen bir şey koyarak yapıyorsanız, yanlış bir şey yapıyoruz.
Orada. Bunu söyledi. Şimdi gerçek cevap için taşıyabilirsiniz.
Kullanıcı verileri karma nesi var, sorabilir miyim? Peki, bu bilinmezlik yoluyla maruziyet yüzeye ve güvenlik aşağı gelir.
Eğer bir saldırgan olduğunuzu bir an için düşünün. Sen bir şifreleme çerez seti görmek hatırlıyorum-Beni senin oturum. Geniş 32 karakter bulunuyor. Gee. Bu bir MD5 olabilir ...
Kullanıcının da onlar kullanılan algoritma biliyoruz ki bir saniye için hayal edelim. Örneğin:
md5(salt+username+ip+salt)
Şimdi, bütün bir saldırgan, kaba kuvvet (gerçekten bir tuz değil ki, ama daha sonra) "tuz" yapması gereken, ve o şimdi onun IP adresi için herhangi bir kullanıcı adı ile istediği tüm sahte belirteçleri üretebilirsiniz! Ama tabi brute-zorlayan bir tuz doğru, zor? Kesinlikle. Ama modern GPU'lar bunda fazlasıyla iyi. Eğer (yeterince büyük yapmak) bunun yeterli rastgeleliğine kullanmadığınız sürece, bu sizin kaleye anahtarları hızla düşecek, ve onunla oluyor.
Kısacası, sizi koruyan tek şey, gerçekten size düşünmek kadar korumak değil tuzdur.
But Wait!
Bütün bunlar saldırgan algoritmayı bildiği esas oldu! Bu sır ve kafa karıştırıcı ise, o zaman doğru, güvenli mi? WRONG. Security Through Obscurity, NEVER üzerine kuruluydu edilmelidir: Bu düşünce şekli bir adı vardır.
The Better Way
Daha iyi bir yolu, bir kullanıcının bilgisi kimliği dışında, sunucuyu terk etmesine asla izin etmektir.
Kullanıcı oturum açtığında, bir büyük (128-256 bit) rasgele belirteç oluşturmak. Kullanıcı kimliği belirteci eşleyen bir veritabanı tablosuna eklemek, ve sonra çerez istemciye göndermek.
Ne saldırganın başka bir kullanıcı rastgele belirteci Bilirse?
Peki, burada bazı matematik yapalım. Biz 128 bit rastgele bir belirteci üreten ediyoruz. O olduğu anlamına gelir:
possibilities = 2^128
possibilities = 3.4 * 10^38
Şimdi, bu sayı ne kadar saçma sapan büyük göstermek için, her bir saniyede 1000000000 oranında bu sayı kaba-kuvvet çalışıyor (en 50,000,000 bugün diyelim) adlı internet üzerindeki her bir sunucu düşünelim. Gerçekte sizin sunucuları, yük altında eriyip, ama en bu dışarı oynayalım istiyorum.
guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000
Saniyede 50 katrilyon Yani tahmin. Bu hızlı! Değil mi?
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
Yani 6.8 sekstilyon saniye ...
En fazla dostu numaralara aşağı getirmek için çalışalım.
215,626,585,489,599 years
Veya daha da iyisi:
47917
Evet, bu evrenin 47917 kez yaşı ...
Temelde, kırık olacak değil.
Yani özetlemek için:
Ben tavsiye iyi bir yaklaşım üç parça ile çerez saklamaktır.
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);
}
Ardından, doğrulamak için:
function rememberMe() {
$cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
if ($cookie) {
list ($user, $token, $mac) = explode(':', $cookie);
if ($mac !== hash_hmac('sha256', $user . ':' . $token, SECRET_KEY)) {
return false;
}
$usertoken = fetchTokenByUserName($user);
if (timingSafeCompare($usertoken, $token)) {
logUserIn($user);
}
}
}
Şimdi, SECRET_KEY
(/dev/random
ve / veya yüksek-entropi girişten türetilen gibi bir şey tarafından oluşturulan) bir kriptografik gizli olması very önemlidir. Ayrıca, GenerateRandomToken()
güçlü bir rasgele bir kaynak olması gerekir (mt_rand()
yeteri kadar güçlü değil. DEV_URANDOM ile bir kütüphane veya mcrypt kullanın) ...
Ve timingSafeCompare önlemektir timing attacks. Böyle bir şey:
/**
* 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) {
// Prevent issues if string length is 0
$safe .= chr(0);
$user .= chr(0);
$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;
}