허용 된 답변 ( md5(uniqid(mt_rand(), true))
) 의 이전 버전 은 안전하지 않으며 가능한 출력은 약 2 ^ 60 개뿐입니다. 저예산 공격자에 대한 약 1 주일 내에 무차별 대입 검색 범위 내에 있습니다.
때문에 56 비트 DES 키가 될 수있는 약 24 시간 동안 무차별 강제 하고 평균 케이스 엔트로피 (59)에 대한 비트를 가질 것이며, 우리 팔일 약 2 ^ 2분의 59 ^ 56 = 계산할 수있다. 이 토큰 확인이 구현되는 방법에 따라 실제로 타이밍 정보를 유출하고 유효한 재설정 토큰의 처음 N 바이트를 추론 할 수 있습니다.
질문은 "모범 사례"에 관한 것이며 다음으로 시작됩니다.
비밀번호 분실에 대한 식별자를 생성하고 싶습니다
...이 토큰에 암시 적 보안 요구 사항이 있다고 추론 할 수 있습니다. 그리고 난수 생성기에 보안 요구 사항을 추가 할 때 가장 좋은 방법은 항상 암호화 된 보안 의사 난수 생성기 (약칭 CSPRNG)를 사용하는 것입니다.
CSPRNG 사용
PHP 7에서는 사용할 수 있습니다 bin2hex(random_bytes($n))
(여기서는 $n
15보다 큰 정수).
PHP 5에서는을 사용 random_compat
하여 동일한 API를 노출 할 수 있습니다 .
또한, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
당신이 경우 ext/mcrypt
설치. 또 다른 좋은 한 줄은 bin2hex(openssl_random_pseudo_bytes($n))
.
유효성 검사기에서 조회 분리
PHP의 보안 "remember me"쿠키에 대한 이전 작업에서 가져온 , 앞서 언급 한 타이밍 누출 (일반적으로 데이터베이스 쿼리에 의해 도입 됨)을 완화하는 유일한 효과적인 방법은 조회와 유효성 검사를 분리하는 것입니다.
테이블이 다음과 같은 경우 (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)
);
... 다음 selector
과 같이 열을 하나 더 추가해야합니다 .
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 사용 암호 재설정 토큰이 발급되면 두 값을 모두 사용자에게 보내고 선택기와 임의 토큰의 SHA-256 해시를 데이터베이스에 저장합니다. 선택기를 사용하여 해시 및 사용자 ID를 가져오고 .NET을 사용하여 데이터베이스에 저장된 토큰과 함께 사용자가 제공하는 토큰의 SHA-256 해시를 계산합니다 hash_equals()
.
예제 코드
PDO를 사용하여 PHP 7 (또는 random_compat을 사용하는 5.6)에서 재설정 토큰 생성 :
$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')
]);
사용자 제공 재설정 토큰 확인 :
$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.
}
이러한 코드 스 니펫은 완전한 솔루션은 아니지만 (입력 유효성 검사 및 프레임 워크 통합을 피했습니다) 수행 할 작업의 예가되어야합니다.