unutulan şifre için rastgele belirteç oluşturmak için en iyi uygulama


96

Unutulan şifre için tanımlayıcı oluşturmak istiyorum. Mt_rand () ile timestamp kullanarak yapabileceğimi okudum, ancak bazı insanlar zaman damgasının her seferinde benzersiz olmayabileceğini söylüyor. Bu yüzden burada biraz kafam karıştı. Bununla zaman damgası kullanarak yapabilir miyim?

Soru
Özel uzunlukta rastgele / benzersiz tokenlar oluşturmak için en iyi uygulama nedir?

Buralarda sorulan çok soru olduğunu biliyorum ama farklı insanlardan farklı görüşler okuduktan sonra kafam daha da karışıyor.


@AlmaDoMundo: Bir bilgisayar zamanı sınırsız bölemez.
juergen d

@juergend - üzgünüm, bunu anlamayın.
Alma Do

Örneğin bir nano saniye ararsanız, aynı zaman damgasını alırsınız. Örneğin bazı zaman işlevleri, zamanı yalnızca 100ns adımlarla, bazıları yalnızca saniye adımlarıyla döndürebilir.
juergen d

@juergend ah, bu. Evet. Sadece saniyelerle 'klasik' zaman damgasından bahsettim. Ama söylediğiniz gibi davranırsanız - evet (bu bize zaman makinesinde benzersiz olmayan zaman damgası alma seçeneği bırakır)
Alma Do

1
Baş yukarı, kabul edilen cevap CSPRNG'den yararlanmıyor .
Scott Arciszewski

Yanıtlar:


152

PHP'de kullanın random_bytes(). Nedeni: bir parola hatırlatma belirteci almanın yolunu arıyorsunuz ve bu bir kerelik oturum açma kimlik bilgileriyse, aslında korumanız gereken bir veriniz var (yani - tüm kullanıcı hesabı)

Yani kod aşağıdaki gibi olacaktır:

//$length = 78 etc
$token = bin2hex(random_bytes($length));

Güncelleme : Bu cevabın önceki sürümleri atıfta bulunuyordu uniqid()ve bu, yalnızca benzersizlik değil, bir güvenlik meselesi varsa yanlıştır. uniqid()aslında sadece microtime()bazı kodlamalarla. microtime()Sunucunuzla ilgili doğru tahminler almanın basit yolları vardır . Saldırgan bir parola sıfırlama isteğinde bulunabilir ve ardından birkaç olası belirteci deneyebilir. Bu, ek entropi benzer şekilde zayıf olduğundan, more_entropy kullanılırsa da mümkündür. @NikiC ve @ScottArciszewski'ye bunu işaret ettikleri için teşekkürler .

Daha fazla ayrıntı için bkz.


21
Not random_bytes()PHP7 itibaren kullanılabilir. Eski sürümler için @yesitsme'nin cevabı en iyi seçenek gibi görünüyor.
Gerald Schneider

3
@GeraldSchneider veya random_compat , bu özellikler için en çok emsal değerlendirmesi alan çoklu dolgu ;)
Scott Arciszewski

Bu belirteci saklamak için sql veritabanımda bir varchar (64) alanı yaptım. $ Length'i 64 olarak ayarladım, ancak döndürülen dize 128 karakter uzunluğunda. Sabit boyutlu bir dizeyi nasıl elde edebilirim (burada, 64 sonra)?
gordie

2
@gordie Uzunluğu 32 olarak ayarlayın, her bayt 2 onaltılık karakterdir
JohnHoulderUK

Ne olmalı $length? Kullanıcının kimliği? Ya da ne?
yığın

72

Bu, 'en iyi rastgele' isteği yanıtlar:

Adi'nin Security.StackExchange'den gelen cevabı 1'in bunun için bir çözümü var:

OpenSSL desteğine sahip olduğunuzdan emin olun ve bu tek satırlık programla asla yanılmayacaksınız

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Adi, Pzt 12 Kasım 2018, Celeritas, "Onay e-postaları için tanınmaz bir jeton oluşturma", 20 Eylül 2013, 07:06, https://security.stackexchange.com/a/40314/


25
openssl_random_pseudo_bytes($length)- destek: PHP 5> = 5.3.0, ....................................... ................... (PHP 7 ve üstü için kullanın random_bytes($length)) ...................... .................... (
5.3'ün

54

Kabul edilen cevabın ( md5(uniqid(mt_rand(), true))) önceki versiyonu güvensizdir ve yalnızca yaklaşık 2 ^ 60 olası çıktı sunar - düşük bütçeli bir saldırgan için yaklaşık bir hafta içinde bir kaba kuvvet araması aralığı dahilinde:

Bir yana 56 bit DES anahtarı olabilir yaklaşık 24 saat içinde kaba zorla ve ortalama vaka entropi 59 ilgili bit olurdu, 8 gün yaklaşık 2 ^ 59/2 ^ 56 = hesaplayabilir. Bu simge doğrulamasının nasıl uygulandığına bağlı olarak, zamanlama bilgisini pratik olarak sızdırmak ve geçerli bir sıfırlama simgesinin ilk N baytını çıkarmak mümkün olabilir .

Soru "en iyi uygulamalar" ile ilgili olduğu ve şununla açıldığı için ...

Unuttuğum şifre için tanımlayıcı oluşturmak istiyorum

... bu belirtecin örtük güvenlik gereksinimlerine sahip olduğu sonucuna varabiliriz. Ve bir rasgele sayı üretecine güvenlik gereksinimleri eklediğinizde, en iyi uygulama, her zaman kriptografik açıdan güvenli bir sözde rasgele sayı üreteci (kısaltılmış CSPRNG) kullanmaktır.


CSPRNG kullanma

PHP 7'de bin2hex(random_bytes($n))(burada $n15'ten büyük bir tam sayı) kullanabilirsiniz.

PHP 5'te, random_compataynı API'yi açığa çıkarmak için kullanabilirsiniz .

Alternatif olarak, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))sen varsa ext/mcryptyüklü. Bir başka iyi tek astar bin2hex(openssl_random_pseudo_bytes($n)).

Aramayı Doğrulayıcıdan Ayırma

PHP'de güvenli "beni hatırla" tanımlama bilgileriyle ilgili önceki çalışmamdan yola çıkarak, yukarıda belirtilen zamanlama sızıntısını azaltmanın tek etkili yolu (genellikle veritabanı sorgusu tarafından ortaya konur) aramayı doğrulamadan ayırmaktır.

Tablonuz böyle görünüyorsa (MySQL) ...

CREATE TABLE account_recovery (
    id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT 
    userid INTEGER(11) UNSIGNED NOT NULL,
    token CHAR(64),
    expires DATETIME,
    PRIMARY KEY(id)
);

... bunun selectorgibi bir sütun daha eklemeniz gerekiyor :

CREATE TABLE account_recovery (
    id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT 
    userid INTEGER(11) UNSIGNED NOT NULL,
    selector CHAR(16),
    token CHAR(64),
    expires DATETIME,
    PRIMARY KEY(id),
    KEY(selector)
);

CSPRNG kullanma Bir parola sıfırlama belirteci verildiğinde, kullanıcıya her iki değeri de gönderin, seçiciyi ve rastgele belirtecin SHA-256 karmasını veritabanında saklayın. Karma ve Kullanıcı Kimliğini almak için seçiciyi kullanın, kullanıcının kullanarak veritabanında depolanan jetonun SHA-256 karmasını hesaplayın hash_equals().

Örnek Kod

PDO ile PHP 7'de (veya random_compat ile 5.6'da) bir sıfırlama belirteci oluşturmak:

$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);

$urlToEmail = 'http://example.com/reset.php?'.http_build_query([
    'selector' => $selector,
    'validator' => bin2hex($token)
]);

$expires = new DateTime('NOW');
$expires->add(new DateInterval('PT01H')); // 1 hour

$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
    'userid' => $userId, // define this elsewhere!
    'selector' => $selector,
    'token' => hash('sha256', $token),
    'expires' => $expires->format('Y-m-d\TH:i:s')
]);

Kullanıcı tarafından sağlanan sıfırlama jetonunu doğrulama:

$stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()");
$stmt->execute([$selector]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
    $calc = hash('sha256', hex2bin($validator));
    if (hash_equals($calc, $results[0]['token'])) {
        // The reset token is valid. Authenticate the user.
    }
    // Remove the token from the DB regardless of success or failure.
}

Bu kod parçacıkları eksiksiz çözümler değildir (girdi doğrulama ve çerçeve entegrasyonlarından kaçındım), ancak ne yapılması gerektiğine dair bir örnek olarak hizmet etmeleri gerekir.


Kullanıcı tarafından sağlanan sıfırlama belirtecini doğruladığınızda, rasgele belirtecin ikili gösterimini neden kullanıyorsunuz? Aşağıdakileri yapmanın mümkün (ve güvenli?) Olacağını düşünüyor musunuz? 1) belirtecin karma onaltılık değerini DB'de depolamak hash('sha256', bin2hex($token)), 2) ile doğrulamak if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Teşekkürler!
Guicara

Evet, onaltılık dizeleri karşılaştırmak da güvenlidir. Bu gerçekten bir tercih meselesi. Tüm kripto işlemlerini ham ikili üzerinde yapmayı ve iletim veya depolama için yalnızca hex / base64'e dönüştürmeyi tercih ediyorum.
Scott Arciszewski

Merhaba Scott, bu temelde sadece cevabınız için değil, "Beni Hatırla" özelliği hakkındaki makalenin tamamı için bir sorudur. Neden benzersiz idolanı seçici olarak kullanmayalım ? Demek istediğim, account_recoverytablonun birincil anahtarı . Seçici için ek güvenlik katmanına ihtiyacımız yok, değil mi? Teşekkürler!
Andre Polykanine

id:secrettamam. selector:secrettamam. secretkendisi değildir. Amaç, veritabanı sorgusunu (zamanlama sızdıran) kimlik doğrulama protokolünden (sabit zaman olmalıdır) ayırmaktır.
Scott Arciszewski

PHP 5.6 çalıştırılıyorsa openssl_random_pseudo_bytesbunun yerine kullanmanın herhangi bir zararı var mı random_bytes? Ayrıca, bağlantının sorgu dizesine doğrulayıcıyı değil, yalnızca seçiciyi eklemeniz gerekmez mi?
greg

7

Ayrıca DEV_RANDOM'u da kullanabilirsiniz, burada 128 = 1/2 oluşturulan simge uzunluğudur. Aşağıdaki kod, 256 jeton üretir.

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));

4
Öneririm MCRYPT_DEV_URANDOMüzerinde MCRYPT_DEV_RANDOM.
Scott Arciszewski

2

Bu, çok çok rastgele bir jetona ihtiyacınız olduğunda yardımcı olabilir

<?php
   echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.