[php] 비밀번호 분실에 대한 무작위 토큰을 생성하는 모범 사례

비밀번호 분실에 대한 식별자를 생성하고 싶습니다. mt_rand ()와 함께 타임 스탬프를 사용하여 할 수 있다고 읽었지만 일부 사람들은 타임 스탬프가 매번 고유하지 않을 수 있다고 말합니다. 그래서 나는 여기서 약간 혼란 스럽습니다. 타임 스탬프를 사용하여 할 수 있습니까?

질문
사용자 정의 길이의 무작위 / 고유 토큰을 생성하는 가장 좋은 방법은 무엇입니까?

여기에 많은 질문이 있다는 것을 알고 있지만 다른 사람들의 다른 의견을 읽은 후 더 혼란스러워집니다.



답변

PHP에서는 random_bytes(). 이유 : 암호 알림 토큰을 얻을 수있는 방법을 찾고 있으며, 일회성 로그인 자격 증명 인 경우 실제로 보호 할 데이터 (즉, 전체 사용자 계정)가 있습니다.

따라서 코드는 다음과 같습니다.

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

업데이트 : 이 답변의 이전 버전uniqid()고유성뿐만 아니라 보안 문제가 있으면 잘못된 것입니다. uniqid()본질적 microtime()으로 일부 인코딩 만 사용합니다. microtime()서버 에서 에 대한 정확한 예측을 얻는 간단한 방법이 있습니다 . 공격자는 암호 재설정 요청을 발행 한 다음 몇 가지 가능한 토큰을 시도 할 수 있습니다. 추가 엔트로피가 비슷하게 약하기 때문에 more_entropy를 사용하는 경우에도 가능합니다. 이 점을 지적 해 주신 @NikiC@ScottArciszewski 에게 감사드립니다 .

자세한 내용은


답변

이것은 ‘최상의 임의’요청에 응답합니다.

Security.StackExchange 의 Adi의 답변 1 에는 이에 대한 솔루션이 있습니다.

OpenSSL을 지원하는지 확인하세요.이 한 줄짜리로 잘못되지 않을 것입니다.

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Adi, Mon Nov 12 2018, Celeritas, “Generating an unguessable token for confirm e-mails”, Sep 20 ’13 at 7:06, https://security.stackexchange.com/a/40314/


답변

허용 된 답변 ( 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))(여기서는 $n15보다 큰 정수).

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.
}

이러한 코드 스 니펫은 완전한 솔루션은 아니지만 (입력 유효성 검사 및 프레임 워크 통합을 피했습니다) 수행 할 작업의 예가되어야합니다.


답변

DEV_RANDOM을 사용할 수도 있습니다. 여기서 128 = 생성 된 토큰 길이의 1/2입니다. 아래 코드는 256 개의 토큰을 생성합니다.

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


답변

이것은 매우 임의의 토큰이 필요할 때 유용 할 수 있습니다.

<?php
   echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>


답변