사용자의 비밀번호를 안전하게 저장하려면 어떻게해야합니까?


169

일반 MD5 보다 훨씬 더 안전 합니까? 방금 비밀번호 보안을 조사하기 시작했습니다. 저는 PHP를 처음 접했습니다.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}

참고 PHP는 5.4+이 내장되어
벤자민 Gruenbaum

Openwall의 PHP 암호 해싱 프레임 워크 (PHPass) 도 참조하십시오 . 휴대 성이 뛰어나 사용자 암호에 대한 여러 가지 일반적인 공격에 대비하여 강화되었습니다.
jww

1
오늘날이 질문에 걸려 넘어지는 사람들에게는 의무적 인 " 문자열 보간 대신 PDO 를 사용하십시오 ".
Fund Monica의 소송

답변:


270

암호 저장 체계를 안전하게 유지하는 가장 쉬운 방법 은 표준 라이브러리사용하는 것 입니다.

보안은 대부분의 프로그래머가 단독으로 처리 할 수있는 것보다 훨씬 더 복잡하고 보이지 않는 스크류 업 가능성으로 인해 표준 라이브러리를 사용하는 것이 가장 쉽고 가장 안전한 옵션 일뿐입니다.


새로운 PHP 비밀번호 API (5.5.0+)

PHP 버전 5.5.0 이상을 사용하는 경우 새로운 단순화 된 비밀번호 해싱 API를 사용할 수 있습니다

PHP의 비밀번호 API를 사용하는 코드의 예 :

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(여전히 레거시 5.3.7 이상을 사용하는 경우 내장 기능에 액세스 할 수 있도록 ircmaxell / password_compat 를 설치할 수 있습니다 )


소금에 절인 해시 개선 : 후추 추가

보안을 강화하려면 지금 보안 담당자 (2017)는 소금에 절인 암호 해시에 ' 후추 '를 추가 할 것을 권장 합니다.

Netsilik / PepperedPasswords ( github ) 는이 패턴을 안전하게 구현하는 클래스가 간단하고 좋습니다 .
MIT 라이센스와 함께 제공되므로 독점 프로젝트에서도 원하는대로 사용할 수 있습니다.

다음을 사용하는 코드 예 Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


OLD 표준 라이브러리

참고 : 더 이상 필요하지 않아야합니다! 이것은 역사적인 목적으로 만 여기에 있습니다.

휴대용 PHP 암호 해싱 프레임 워크 : phpass를 살펴보고 CRYPT_BLOWFISH가능한 경우 알고리즘 을 사용해야 합니다.

phpass (v0.2)를 사용하는 코드의 예 :

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass는 잘 알려진 일부 프로젝트에서 구현되었습니다.

  • phpBB3
  • bbPress뿐만 아니라 WordPress 2.5 이상
  • Drupal 7 릴리스 (Drupal 5 및 6에 사용 가능한 모듈)
  • 다른 사람

좋은 점은 세부 사항에 대해 걱정할 필요가 없다는 것입니다. 세부 사항은 경험이 많은 사람들이 프로그래밍했으며 인터넷의 많은 사람들이 검토했습니다.

암호 저장 체계에 대한 자세한 내용은 Jeff 의 블로그 게시물을 참조하십시오. 암호를 잘못 저장했을 수 있습니다

무엇 이건 당신이 '갈 경우 당신이 내가 그것을 자신을 감사합니다 '접근, 사용하지 않는 MD5또는 SHA1더 이상 . 그것들은 좋은 해싱 알고리즘이지만 보안상의 이유로 깨진 것으로 간주 됩니다. .

현재 CRYPT_BLOWFISH와 함께 crypt를 사용 하는 것이 가장 좋습니다.
PHP의 CRYPT_BLOWFISH는 Bcrypt 해시의 구현입니다. Bcrypt는 Blowfish 블록 암호를 기반으로하며 알고리즘을 느리게하기 위해 값 비싼 키 설정을 사용합니다.


29

SQL 문을 연결하는 대신 매개 변수화 된 쿼리를 사용하면 사용자가 훨씬 안전합니다. 그리고 소금은 각 사용자에 대해 고유해야하며, 암호 해시와 함께 보관해야합니다.


1
Nettuts +에서 PHP의 보안에 관한 좋은 기사가 있으며, 암호 소금도 언급되어 있습니다. 어쩌면 당신은 한 번 봐 걸릴해야합니다 net.tutsplus.com/tutorials/php/...을
파 비우 ANTUNES에게

3
Nettuts +는 모델로 사용하기에는 매우 나쁜 기사입니다. 여기에는 소금으로도 쉽게 무차별 적으로 강제 할 수있는 MD5가 포함됩니다. 대신 튜토리얼 사이트에서 찾을 수있는 코드보다 훨씬 더 나은 PHPass 라이브러리를 사용하십시오 (예 : stackoverflow.com/questions/1581610/…
RichVel

11

더 나은 방법은 각 사용자가 고유 한 소금을 먹는 것입니다.

솔트를 사용하면 공격자가 모든 사전 단어의 MD5 서명을 미리 생성하기가 더 어렵다는 이점이 있습니다. 그러나 공격자가 고정 소금이 있다는 것을 알게되면 고정 소금이 접두사로 추가 된 모든 사전 단어의 MD5 서명을 미리 생성 할 수 있습니다.

더 좋은 방법은 사용자가 비밀번호를 변경할 때마다 시스템이 임의의 소금을 생성하고 해당 소금을 사용자 레코드와 함께 저장하는 것입니다. MD5 서명을 생성하기 전에 소금을 찾아야하므로 암호를 확인하는 것이 약간 더 비싸지 만 공격자가 MD5를 미리 생성하기가 훨씬 어렵습니다.


3
솔트는 일반적으로 비밀번호 해시와 함께 저장됩니다 (예 : crypt()함수 출력 ). 어쨌든 암호 해시를 검색해야하기 때문에 사용자 별 소금을 사용하면 절차가 더 비싸지 않습니다. (또는 새로운 무작위 소금을 만드는 것이 비싸다는 것을 의미 했습니까? 나는 그렇게 생각하지 않습니다.) 그렇지 않으면 +1.
Inshallah

보안을 위해 스토어드 프로 시저를 통해서만 테이블에 대한 액세스를 제공하고 해시가 리턴되지 않도록 할 수 있습니다. 대신 클라이언트는 해시라고 생각하는 것을 전달하고 성공 또는 실패 플래그를 얻습니다. 이를 통해 저장된 proc은 시도를 기록하고 세션을 생성 할 수 있습니다.
Steven Sudit

@Inshallah-모든 사용자가 동일한 소금을 가지고 있다면 user1에 대해 사용하는 사전 공격을 user2에 대해 재사용 할 수 있습니다. 그러나 각 사용자에게 고유 한 소금이있는 경우 공격하려는 각 사용자에 대해 새 사전을 생성해야합니다.
R Samuel Klatchko

@R Samuel-이것이 바로 귀하의 답변에 투표 한 이유입니다. 이러한 공격을 피하기위한 최선의 전략을 권장하기 때문입니다. 내 의견은 전혀 이해하지 못한 사용자 당 소금의 추가 비용에 대해 말한 것에 대한 내 당황을 표현하기위한 것입니다. "소금은 일반적으로 암호 해시와 함께 저장되기 때문에"사용자 별 소금에 대한 추가 스토리지 및 CPU 요구 사항은 매우 미세하여 언급 할 필요도 없습니다 ...)
Inshallah

@Inshallah-해시 된 암호가 올바른지 데이터베이스를 확인 한 경우에 대해 생각하고있었습니다 (그런 다음 소금을 얻기 위해 하나의 db 검색과 해시 된 암호를 확인하기 위해 두 번째 db 액세스가 있음). 단일 검색으로 솔트 / 해시 암호를 다운로드 한 다음 클라이언트에서 비교를 수행하는 경우가 맞습니다. 혼란을 드려 죄송합니다.
R Samuel Klatchko

11

PHP 5.5 나는 새로운, 내장의 솔루션을 사용하는 것이 좋습니다 싶습니다 모퉁이 (내가 무엇을 설명하는 것은 그 이전 버전에서 사용할 수, 아래 참조) : password_hash()password_verify(). 필요한 암호 보안 수준을 달성하기 위해 몇 가지 옵션을 제공합니다 (예 : $options어레이를 통해 "cost"매개 변수를 지정 )

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

돌아올 것이다

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

보시다시피, 문자열에는 소금과 옵션에 지정된 비용이 포함되어 있습니다. 사용 된 알고리즘도 포함되어 있습니다.

따라서 암호를 확인할 때 (예 : 사용자가 로그인 할 때) 무료를 사용할 때 password_verify() 기능을 비밀번호 해시 자체에서 필요한 암호화 매개 변수를 추출합니다.

솔트를 지정하지 않으면 생성 된 비밀번호 해시는 호출 할 때마다 다릅니다. password_hash() 솔트가 무작위로 생성되므로 . 따라서 올바른 암호라도 이전 해시와 새로 생성 된 해시를 비교할 수 없습니다.

확인은 다음과 같이 작동합니다.

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

이 내장 함수를 제공하면 데이터 도난시 더 나은 암호 보안을 제공 할 수 있기를 바랍니다. 프로그래머가 적절한 구현에 대해 생각해야하는 양이 줄어들 기 때문입니다.

password_hashPHP 5.3.7 이상에서 PHP 5.5를 제공하는 작은 라이브러리 (하나의 PHP 파일)가 있습니다 : https://github.com/ircmaxell/password_compat


2
대부분의 경우 소금 매개 변수를 생략하는 것이 좋습니다. 이 기능은 운영 체제의 임의 소스에서 소금을 생성하므로 더 나은 소금을 직접 제공 할 가능성은 거의 없습니다.
martinstoeckli 2016

1
그게 내가 쓴 것이지? "소금을 지정하지 않으면 무작위로 생성되므로 소금을 지정하지 않는 것이 좋습니다"
akirk

대부분의 예제는 소금을 추가하지 않는 경우에도 두 매개 변수를 모두 추가하는 방법을 보여줍니다. 왜 그런지 궁금합니다. 그리고 솔직히 말해서, 나는 다음 줄이 아니라 코드 뒤에있는 주석 만 읽었습니다. 어쨌든 예제가 함수를 가장 잘 사용하는 방법을 보여 주면 더 좋지 않을까요?
martinstoeckli

당신 말이 맞아요 이에 따라 답변을 변경하고 해당 내용을 주석 처리했습니다. 감사합니다
akirk

저장된 비밀번호와 입력 한 비밀번호가 동일한 지 어떻게 확인해야합니까? 사용중인 비밀번호 password_hash()password_verify어떤 비밀번호 (올바른지 여부)에 상관없이 올바른 비밀번호로 끝납니다
Brownman Revival

0

저도 괜찮습니다. Atwood는 레인보우 테이블에 대한 MD5의 강도에 대해 썼으며 기본적으로 당신이 꽤 앉아있는 것과 같은 긴 소금을 사용했습니다 (임의의 구두점 / 숫자는 향상시킬 수 있음).

요즘 인기가 높아지고있는 SHA-1도 볼 수 있습니다.


6
Atwood의 게시물 맨 아래 (빨간색)는 MD5, SHA1 및 기타 빠른 해시를 사용하여 암호를 저장하는 보안 실무자의 다른 게시물로 연결됩니다.
sipwiz

2
@ Matthew Scharley : 비싼 암호 해싱 알고리즘으로 인한 추가 노력이 잘못된 보안이라는 데 동의하지 않습니다. 쉽게 추측 할 수있는 암호의 무차별 강제 실행을 방지해야합니다. 로그인 시도를 제한하는 경우 동일한 것을 방지합니다 (약간 더 효과적 임). 그러나 공격자가 DB에 저장된 해시에 액세스 할 수 있으면 추측하기 쉬운 암호에 따라 이러한 암호를 쉽게 추측 할 수 있습니다. SHA-256 암호화 알고리즘의 기본값은 10000 라운드이므로 10000 배 더 어려워집니다.
Inshallah

3
느린 해시는 실제로 빠른 것을 매우 많이 반복하고 각 반복 사이에 데이터를 섞어서 만들어집니다. 목표는 악의적 인 사용자가 비밀번호 해시 사본을 가져 오더라도 해시에 대해 사전을 테스트하기 위해 상당한 양의 CPU 시간을 소모해야한다는 것입니다.
caf

4
@ caf : 나는 bcrypt 알고리즘이 Eksblowfish 키 스케줄링의 매개 변수화 가능한 고가를 사용한다고 생각합니다. 이것이 어떻게 작동하는지 완전히 확신 할 수는 없지만, 키 스케줄링은 암호화가 완료되기 전에 암호 컨텍스트 오브젝트를 초기화하는 동안 수행되는 매우 비싼 작업입니다.
Inshallah

3
Inshallah : 이것은 사실입니다-bcrypt 알고리즘은 다른 디자인입니다. 기본 암호화 기본 요소는 해시 함수가 아닌 블록 암호입니다. PHK의 MD5 crypt ()와 같은 해시 함수를 기반으로 한 체계를 언급하고있었습니다.
caf

0

추가하고 싶습니다 :

  • 사용자 비밀번호를 길이로 제한하지 마십시오

기존 시스템과의 호환성을 위해 종종 최대 암호 길이 제한을 설정합니다. 이는 잘못된 보안 정책입니다. 제한을 설정 한 경우 최소 암호 길이로만 설정하십시오.

  • 이메일을 통해 사용자 비밀번호를 보내지 마십시오

잊어 버린 비밀번호를 복구하려면 사용자가 비밀번호를 변경할 수있는 주소를 보내야합니다.

  • 사용자 비밀번호의 해시 업데이트

비밀번호 해시가 오래되었을 수 있습니다 (알고리즘의 매개 변수가 업데이트 될 수 있음). 이 기능 password_needs_rehash()을 사용하면 체크 아웃 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.