PHP에서 해싱 암호를 위해 bcrypt를 어떻게 사용합니까?


1255

때때로 나는 "PHP에 암호를 저장하기 위해 bcrypt를 사용하고, bcrypt 규칙"이라는 조언을 듣는다.

그러나 무엇 bcrypt입니까? PHP는 그러한 기능을 제공하지 않으며, Wikipedia는 파일 암호화 유틸리티에 대해 이야기하고 웹 검색 은 다른 언어로 된 Blowfish 의 몇 가지 구현을 보여줍니다 . 이제 Blowfish는 PHP를 통해 사용할 수도 mcrypt있지만 암호 저장에 어떤 도움이됩니까? 복어는 범용 암호이며 두 가지 방식으로 작동합니다. 암호화 할 수 있으면 해독 할 수 있습니다. 암호는 단방향 해싱 기능이 필요합니다.

설명은 무엇입니까?


13
이 질문은 이전해결 되었으며 표준 라이브러리 사용에 대한 제안은 훌륭합니다. 보안은 복잡한 문제이며 자신이하는 일이 무엇인지 아는 사람이 설계 한 패키지를 사용하면 도움이됩니다.
eykanal

59
@eykanal-해당 페이지는 bcrypt를 언급하지도 않고, 그것이 무엇인지 설명 하는 것은 훨씬 적습니다 .
Vilx-

8
@eykanal-작동 방식에 대해서는 설명하지 않습니다. 나는 그것이 무엇인지 알고 싶습니다 . 키워드 "bcrypt"로 인터넷에서 찾을 수있는 것은 암호 해싱에 사용될 수 없습니다. 어쨌든 직접은 아니고 PHP도 아닙니다. OK, 지금은 실제로 "phpass"패키지라는 것을 이해합니다. 복어를 사용하여 암호에서 파생 된 키로 암호를 암호화합니다 (본질적으로 암호 자체를 암호화 함). 그러나 그것을 "암호화"라고 언급하는 것은 심각하게 오도되며, 이것이이 질문에서 명확히하고 싶었습니다.
Vilx-

3
@Vilx : 왜 bcrypt단방향 해싱 알고리즘 이 내 대답 의 암호화 체계 보다 더 많은 정보를 추가했습니다 . bcrypt실제로는 완전히 다른 키 스케줄이있을 때 복어 라는 이 오해 가 있습니다. 이는 암호의 초기 상태 (소금, 반올림, 키)를 몰라도 암호문에서 일반 텍스트를 복구 할 수 없도록합니다.
Andrew Moore

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

답변:


1065

bcrypt하드웨어로 확장 가능한 해싱 알고리즘입니다 (구성 가능한 라운드 수를 통해). 속도가 느리고 여러 번 진행되므로 공격자는 암호를 해독 할 수 있도록 막대한 자금과 하드웨어를 배포해야합니다. 암호 당 소금 ( 소금이bcrypt 필요)에 추가하면 돈이나 하드웨어없이 어마 어마한 공격을 수행 할 수 없습니다.

bcryptEksblowfish 알고리즘을 사용하여 비밀번호를 해시합니다. 의 암호화 단계 동안 Eksblowfish복어가 정확히 동일의 주요 일정 단계 Eksblowfish는 이후의 상태가 모두 소금과 키 (사용자 암호)에 의존한다는 것을 보장하고, 어떤 상태는 모두의 지식없이 미리 계산 될 수 없다. 이 주요 차이점으로 인해 bcrypt단방향 해싱 알고리즘이 있습니다. 솔트, 라운드 및 키 (암호)를 모르면 일반 텍스트 비밀번호를 검색 할 수 없습니다 . [ 출처 ]

bcrypt를 사용하는 방법 :

PHP 사용> = 5.5-DEV

패스워드 해싱 함수 는 이제 PHP> = 5.5에 직접 내장되었습니다 . 이제 비밀번호 password_hash()bcrypt해시 를 만드는 데 사용할 수 있습니다 .

<?php
// Usage 1:
echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."\n";
// $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// For example:
// $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

// Usage 2:
$options = [
  'cost' => 11
];
echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n";
// $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C

기존 해시에 대해 사용자가 제공 한 비밀번호를 확인하려면 다음 password_verify()과 같이 사용할 수 있습니다 .

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

PHP 사용> = 5.3.7, <5.5-DEV (또한 RedHat PHP> = 5.3.3)

GitHub 에는 원래 C로 작성된 위 함수의 소스 코드를 기반으로 생성 된 호환성 라이브러리 가 있으며 동일한 기능을 제공합니다. 호환성 라이브러리가 설치되면 사용법은 위와 동일합니다 (여전히 5.3.x 분기에있는 경우 속기 배열 표기법 제외).

PHP 사용 <5.3.7 (DEPRECATED)

crypt()함수를 사용 하여 입력 문자열의 암호화 해시를 생성 할 수 있습니다 . 이 클래스는 솔트를 자동으로 생성하고 입력에 대해 기존 해시를 확인할 수 있습니다. 5.3.7 이상의 PHP 버전을 사용하는 경우 내장 함수 또는 compat 라이브러리를 사용하는 것이 좋습니다 . 이 대안은 역사적인 목적으로 만 제공됩니다.

class Bcrypt{
  private $rounds;

  public function __construct($rounds = 12) {
    if (CRYPT_BLOWFISH != 1) {
      throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
    }

    $this->rounds = $rounds;
  }

  public function hash($input){
    $hash = crypt($input, $this->getSalt());

    if (strlen($hash) > 13)
      return $hash;

    return false;
  }

  public function verify($input, $existingHash){
    $hash = crypt($input, $existingHash);

    return $hash === $existingHash;
  }

  private function getSalt(){
    $salt = sprintf('$2a$%02d$', $this->rounds);

    $bytes = $this->getRandomBytes(16);

    $salt .= $this->encodeBytes($bytes);

    return $salt;
  }

  private $randomState;
  private function getRandomBytes($count){
    $bytes = '';

    if (function_exists('openssl_random_pseudo_bytes') &&
        (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL is slow on Windows
      $bytes = openssl_random_pseudo_bytes($count);
    }

    if ($bytes === '' && is_readable('/dev/urandom') &&
       ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
      $bytes = fread($hRand, $count);
      fclose($hRand);
    }

    if (strlen($bytes) < $count) {
      $bytes = '';

      if ($this->randomState === null) {
        $this->randomState = microtime();
        if (function_exists('getmypid')) {
          $this->randomState .= getmypid();
        }
      }

      for ($i = 0; $i < $count; $i += 16) {
        $this->randomState = md5(microtime() . $this->randomState);

        if (PHP_VERSION >= '5') {
          $bytes .= md5($this->randomState, true);
        } else {
          $bytes .= pack('H*', md5($this->randomState));
        }
      }

      $bytes = substr($bytes, 0, $count);
    }

    return $bytes;
  }

  private function encodeBytes($input){
    // The following is code from the PHP Password Hashing Framework
    $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    $output = '';
    $i = 0;
    do {
      $c1 = ord($input[$i++]);
      $output .= $itoa64[$c1 >> 2];
      $c1 = ($c1 & 0x03) << 4;
      if ($i >= 16) {
        $output .= $itoa64[$c1];
        break;
      }

      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 4;
      $output .= $itoa64[$c1];
      $c1 = ($c2 & 0x0f) << 2;

      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 6;
      $output .= $itoa64[$c1];
      $output .= $itoa64[$c2 & 0x3f];
    } while (true);

    return $output;
  }
}

이 코드를 다음과 같이 사용할 수 있습니다 :

$bcrypt = new Bcrypt(15);

$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);

또는 Portable PHP Hashing Framework를 사용할 수도 있습니다 .


7
@The Wicked Flea : 실망 mt_rand()시켜서 죄송하지만 현재 시간과 현재 프로세스 ID를 사용하여 시드됩니다. 참조하십시오 GENERATE_SEED()에서/ext/standard/php_rand.h .
Andrew Moore

53
@ 마이크 : 계속해라, 바로 그 이유가있다!
Andrew Moore

14
getSalt 함수에서 $ salt 문자열의 시작 부분을 수정해야한다고 생각하는 사람에게는 필요하지 않습니다. $ 2a $ __는 CRYPT_BLOWFISH 소금의 일부입니다. 문서에서 : "소금을 사용한 복어 해시 :"$ 2a $ ", 두 자리 비용 매개 변수"$ "및 알파벳에서 22 자리"
jwinn

18
@MichaelLang : 좋은 점 crypt()은 동료 심사를 거쳐 검증되었습니다. 위의 코드는 PHP 's crypt()를 호출하고 POSIX crypt()함수 를 호출합니다 . 위의 모든 코드는을 호출하기 전에 무작위로 소금을 생성합니다 (암호 적으로 안전 할 필요는 없으며 소금은 비밀로 간주되지 않습니다) crypt(). 늑대를 부르기 전에 약간의 연구를해야 할 수도 있습니다.
Andrew Moore

31
이 답변은 훌륭하지만 나이를 나타 내기 시작했습니다. 이 코드 (예 : 의존하는 PHP 구현 crypt())는 5.3.7 이전의 보안 취약점에 영향을받으며, 5.3.7 이후 (매우 약간) 비효율적입니다. 관련 문제에 대한 자세한 내용은 여기를 참조하십시오 . 또한 새로운 비밀번호 해싱 API ( 뒤로 compat lib )가 애플리케이션에서 bcrypt 비밀번호 해싱을 구현하는 데 선호되는 방법입니다.
DaveRandom

295

그렇다면 bcrypt를 사용하고 싶습니까? 대박! 그러나 다른 암호화 영역과 마찬가지로 직접 암호화해서는 안됩니다. 키 관리, 소금 저장 또는 임의의 숫자 생성과 같은 것에 대해 걱정이 필요하면 잘못하고 있습니다.

그 이유는 간단합니다. bcrypt를 망가 뜨리는 것은 매우 쉽습니다 . 실제로이 페이지의 거의 모든 코드를 살펴보면 이러한 일반적인 문제 중 하나 이상을 위반하고 있음을 알 수 있습니다.

Face It, 암호화는 어렵다.

전문가에게 맡기십시오. 이 라이브러리를 유지 관리하는 것이 직업인 사람들에게 맡기십시오. 결정을 내려야한다면 잘못하고있는 것입니다.

대신 라이브러리를 사용하십시오. 요구 사항에 따라 몇 가지가 있습니다.

도서관

다음은 더 일반적인 API 중 일부입니다.

PHP 5.5 API-(5.3.7 이상에서 사용 가능)

PHP 5.5부터는 해싱 비밀번호를위한 새로운 API가 도입되었습니다. 5.3.7 이상을 위해 유지되는 shim 호환성 라이브러리도 있습니다. 이 피어 검토되는 이점이있다 간단한 사용으로 구현합니다.

function register($username, $password) {
    $hash = password_hash($password, PASSWORD_BCRYPT);
    save($username, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    if (password_verify($password, $hash)) {
        //login
    } else {
        // failure
    }
}

실제로, 그것은 매우 간단한 것을 목표로합니다.

자원:

Zend \ Crypt \ Password \ Bcrypt (5.3.2+)

이것은 PHP 5.5와 비슷한 또 다른 API이며 비슷한 목적을 수행합니다.

function register($username, $password) {
    $bcrypt = new Zend\Crypt\Password\Bcrypt();
    $hash = $bcrypt->create($password);
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $bcrypt = new Zend\Crypt\Password\Bcrypt();
    if ($bcrypt->verify($password, $hash)) {
        //login
    } else {
        // failure
    }
}

자원:

PasswordLib

이것은 암호 해싱에 대한 약간 다른 접근 방식입니다. PasswordLib은 단순히 bcrypt를 지원하지 않고 많은 수의 해싱 알고리즘을 지원합니다. 주로 제어 범위를 벗어난 레거시 및 이기종 시스템과의 호환성을 지원해야하는 상황에서 유용합니다. 많은 수의 해싱 알고리즘을 지원합니다. 그리고 5.3.2 이상을 지원합니다

function register($username, $password) {
    $lib = new PasswordLib\PasswordLib();
    $hash = $lib->createPasswordHash($password, '$2y$', array('cost' => 12));
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $lib = new PasswordLib\PasswordLib();
    if ($lib->verifyPasswordHash($password, $hash)) {
        //login
    } else {
        // failure
    }
}

참고 문헌 :

  • 소스 코드 / 문서 : GitHub

PHPASS

이것은 bcrypt를 지원하는 레이어이지만 PHP> = 5.3.2에 액세스 할 수없는 경우에 유용한 상당히 강력한 알고리즘을 지원합니다 ... 실제로는 PHP 3.0 이상을 지원합니다 (bcrypt는 아니지만).

function register($username, $password) {
    $phpass = new PasswordHash(12, false);
    $hash = $phpass->HashPassword($password);
    save($user, $hash);
}

function login($username, $password) {
    $hash = loadHashByUsername($username);
    $phpass = new PasswordHash(12, false);
    if ($phpass->CheckPassword($password, $hash)) {
        //login
    } else {
        // failure
    }
}

자원

참고 : openwall에서 호스팅되지 않는 PHPASS 대안을 사용하지 마십시오 . 다른 프로젝트입니다 !!!

BCrypt 소개

알다시피, 이러한 라이브러리는 모두 단일 문자열을 반환합니다. BCrypt가 내부적으로 작동하는 방식 때문입니다. 그리고 그것에 대한 많은 대답이 있습니다. 여기에 내가 작성하고 복사 / 붙여 넣지 않고 링크하는 선택이 있습니다.

마무리

많은 다른 선택이 있습니다. 당신이 선택한 것은 당신에게 달려 있습니다. 그러나, 나는 것이다 높게 당신이 당신을 위해 이것을 처리하기위한 위의 라이브러리 중 하나를 사용하는 것이 좋습니다.

다시 말하지만, crypt()직접 사용 하는 경우 무언가 잘못되었을 수 있습니다. 코드가 hash()(또는 md5()또는 sha1()) 직접 사용하는 경우 거의 확실하게 잘못된 일을하고있는 것입니다.

라이브러리를 사용하십시오 ...


7
소금은 무작위로 생성되어야하지만 안전한 임의의 출처에서 나올 필요는 없습니다. 소금은 비밀이 아닙니다 . 다음 소금을 추측 할 수 있다는 것은 실질적인 보안 영향이 없습니다. 암호화 된 각 암호에 대해 다른 소금을 생성하기 위해 충분히 큰 데이터 풀에서 오는 한 괜찮습니다. 해시가 나쁜 손에 들어간 경우 무지개 테이블을 사용하지 못하도록 소금이 있습니다. 그들은 비밀이 아닙니다.
Andrew Moore

7
@AndrewMoore는 절대적으로 정확합니다! 그러나 소금은 통계적으로 고유하기에 충분한 엔트로피를 가져야합니다. 응용 프로그램뿐만 아니라 모든 응용 프로그램에서도. 따라서 mt_rand()주기가 충분하지만 시드 값은 32 비트에 불과합니다. 따라서 mt_rand()효과적으로 사용 하면 32 비트의 엔트로피로 제한됩니다. 생일 문제 덕분에 7k로 생성 된 소금 만 (전 세계적으로) 50 %의 확률로 충돌 할 수 있습니다. bcrypt128 비트의 소금을 받아들이 므로 128 비트를 모두 공급할 수있는 소스를 사용하는 것이 좋습니다 ;-). (128 비트에서 2e19 해시에서 50 %의 충돌 확률이 발생 함) ...
ircmaxell

1
@ircmaxell : "충분히 큰 데이터 풀"을 강조하십시오. 그러나 소스가 VERY HIGH 엔트로피 소스 일 필요는 없으며 128 비트에 충분합니다. 그러나 사용 가능한 모든 소스를 소진하고 (OpenSSL이없는 경우 등) 유일한 대체 방법은 mt_rand ()이지만 대안 (rand ())보다 여전히 낫습니다.
Andrew Moore

4
@AndrewMoore : 절대적으로. 그것을 주장하지 않습니다. 그냥 것을 mt_rand하고 uniqid(따라서 lcg_valuerand) 첫 번째 선택이 아니다 ...
ircmaxell

1
ircmaxell, 5.3.xx의 password_compat 라이브러리에 대해 대단히 감사합니다. 이전에 필요하지 않았지만 이제는 5.3.xx PHP 서버 에서이 작업을 수행하지 않으려는 명확한 조언에 감사드립니다. 자신.
Lizardx

47

Enough With The Rainbow Tables : Secure Password Schemes 또는 Portable PHP password hashing framework에 대해 알아야 할 정보에 많은 정보가 있습니다.

목표는 암호를 느리게 해시하는 것이므로 암호 데이터베이스를 얻는 사람이 강제로 죽이려고 죽을 것입니다. Bcrypt 는 느리고 매개 변수와 함께 사용하여 속도를 느리게 선택할 수 있습니다.


7
원하는 것을 적용하면 사용자는 여러 가지 일에 동일한 암호를 사용하여 망치게됩니다. 따라서 비밀번호를 최대한 보호하거나 비밀번호 (SSO, openID 등)를 저장할 필요가없는 것을 구현해야합니다.
Arkh

41
암호 해싱은 한 번의 공격으로부터 보호하는 데 사용됩니다. 누군가 데이터베이스를 훔쳐 일반 텍스트 로그인 + 암호를 얻으려고합니다.
Arkh

4
@Josh K. 웹 서버에서 계산하는 데 1ms에서 10ms 사이의 시간이 걸리므로 phpass 조정을 통해 간단한 암호를 해독하는 것이 좋습니다.
Arkh

3
동의했다. 그러나 qwerty를 암호로 사용하는 종류의 사용자는 자신과 공격자가 쉽게 읽을 수있는 복잡한 곳에 표시하는 종류의 사용자이기도합니다. bcrypt를 사용하면 DB가 사용자의 의지에 반할 때 한 번에 sha512를 사용하는 것보다 ^ | $$ & ZL6- £와 같은 암호를 가진 사용자에게 접근하기가 더 어려워집니다.
Arkh

4
@coreyward는 그렇게하는 것이 전혀 차단하지 않는 것보다 더 해롭다는 점에 주목할 가치가 있습니다. 이는 "서비스 거부"벡터로 쉽게 간주됩니다. 알려진 모든 계정에서 잘못된 로그인 스팸을 시작하기 만하면 많은 사용자를 매우 쉽게 방해 할 수 있습니다. 특히 유료 고객 인 경우 액세스를 거부하는 것보다 공격자를 타프 팅 (지연)시키는 것이 좋습니다.
damianb

36

PHP crypt()기능을 사용 하고 적절한 복어 소금을 전달 하여 bcrypt로 단방향 해시를 만들 수 있습니다 . 전체 방정식 중 가장 중요한 것은 A) 알고리즘이 손상되지 않았으며 B) 각 암호에 올바르게 소금을 칠하는 것 입니다. 응용 프로그램 전체 소금을 사용하지 마십시오. 레인보우 테이블 하나에서 공격 할 수 있도록 전체 응용 프로그램을 엽니 다.

PHP-암호화 함수


4
이것은 올바른 접근 방식입니다. PHP의 crypt()기능을 사용하십시오.이 기능은 여러 가지 다른 암호 해싱 기능을 지원합니다. 사용하고 있지 않은지 CRYPT_STD_DES또는 CRYPT_EXT_DES다른 지원되는 유형이 올바른지 확인하십시오 (이름 아래에 bcrypt 포함 CRYPT_BLOWFISH).
caf

4
SHA는 실제로 '라운드'옵션을 통해 비용 매개 변수를 가지고 있습니다. 이를 사용할 때 bcrypt를 선호 할 이유도 없습니다.
Pieter Ennes

3
실제로, 암호의 단일 SHA-1 (또는 MD5)은 소금의 유무에 관계없이 여전히 쉽게 무차별 강제 가능합니다. bcrypt를 사용하십시오.
Paŭlo Ebermann

나는 그들이 PHP의 crypt ()를 의미 할 때 모두가 "bcrypt"라고 말하는 것을 방해하는 것을 안다.
Sliq

3
@Panique 왜? 이 알고리즘을 bcrypt 라고 합니다 . cryptbcrypt가 CRYPT_BLOWFISH상수에 해당하는 여러 비밀번호 해시를 노출합니다 . Bcrypt는 현재 지원되는 가장 강력한 알고리즘 crypt이며 지원하는 다른 알고리즘 은 상당히 약합니다.
코드 InChaos

34

편집 : 2013.01.15-서버에서 지원하는 경우 대신 martinstoeckli의 솔루션을 사용하십시오.


모든 사람들이 이것을보다 복잡하게 만들고 싶어합니다. crypt () 함수는 대부분의 작업을 수행합니다.

function blowfishCrypt($password,$cost)
{
    $chars='./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    $salt=sprintf('$2y$%02d$',$cost);
//For PHP < PHP 5.3.7 use this instead
//    $salt=sprintf('$2a$%02d$',$cost);
    //Create a 22 character salt -edit- 2013.01.15 - replaced rand with mt_rand
    mt_srand();
    for($i=0;$i<22;$i++) $salt.=$chars[mt_rand(0,63)];
    return crypt($password,$salt);
}

예:

$hash=blowfishCrypt('password',10); //This creates the hash
$hash=blowfishCrypt('password',12); //This creates a more secure hash
if(crypt('password',$hash)==$hash){ /*ok*/ } //This checks a password

비밀번호가 분명하다는 것을 알고 있지만 비밀번호로 '비밀번호'를 사용하지 마십시오.


3
소금의 생성이 향상 될 수 있으며 (OS의 임의 소스 사용) 그렇지 않으면 나에게 좋아 보입니다. 최신 PHP 버전에서는 2y대신에 사용하는 것이 좋습니다 2a.
martinstoeckli

사용하는 mcrypt_create_iv($size, MCRYPT_DEV_URANDOM)소금의 소스로.
코드 InChaos

잠시 동안 성능을 약간 향상시켜야하는 경우 mcrypt_create_iv ()를 자세히 살펴 보겠습니다.
Jon Hulka

2
Base64 인코딩을 추가하고 사용자 정의 알파벳 bcrypt사용으로 변환하십시오 . mcrypt_create_iv(17, MCRYPT_DEV_URANDOM), str_replace('+', '.', base64_encode($rawSalt)),$salt = substr($salt, 0, 22);
CodesInChaos

1
@JonHulka-PHP의 호환성 팩 [Line 127]을 살펴보십시오 . 이것은 간단한 구현입니다.
martinstoeckli

29

PHP 버전 5.5이 내장 것이다 BCrypt, 기능에 대한 지원 password_hash()password_verify(). 실제로 이들은 함수 주위의 래퍼 일 뿐이 crypt()므로 올바르게 사용하기가 더 쉬워집니다. 안전한 무작위 소금 생성을 처리하고 좋은 기본값을 제공합니다.

이 기능을 사용하는 가장 쉬운 방법은 다음과 같습니다.

$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
$isPasswordCorrect = password_verify($password, $existingHashFromDb);

이 코드는 BCrypt (algorithm 2y)로 비밀번호를 해시하고 OS 랜덤 소스에서 랜덤 솔트를 생성하며 기본 비용 매개 변수 (현재 10)를 사용합니다. 두 번째 줄은 사용자가 입력 한 비밀번호가 이미 저장된 해시 값과 일치하는지 확인합니다.

비용 매개 변수를 변경하려는 경우 비용 매개 변수를 1 씩 늘리면 해시 값을 계산하는 데 필요한 시간이 두 배가됩니다.

$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 11));

"cost"매개 변수 와 달리 "salt",이 기능은 이미 암호로 안전한 소금을 만들기 위해 최선을 다하기 때문에 매개 변수 를 생략하는 것이 가장 좋습니다 .

PHP 버전 5.3.7 이상 에는 기능 을 만든 동일한 작성자 의 호환 기능 팩password_hash()있습니다. 5.3.7 이전의 PHP 버전에 대한 지원이 없습니다 crypt()2y, 유니 코드 안전 BCrypt 알고리즘. 대신 2a이전 PHP 버전에 대한 가장 좋은 대안 인로 대체 할 수 있습니다.


3
이 글을 읽은 후, 나의 첫 생각은 "생성 된 소금을 어떻게 저장합니까?"였습니다. 문서를 살펴본 후, password_hash () 함수는 암호화 방법, 솔트 및 생성 된 해시를 저장하는 문자열을 생성합니다. 따라서 password_verify () 함수가 작동하기 위해 필요한 모든 것을 하나의 문자열로 저장합니다. 다른 사람들이 이것을 볼 때 도움이 될 수 있으므로 이것을 언급하고 싶었습니다.
jzimmerman2011

@ jzimmerman2011-정확히, 다른 대답에서 나는이 저장 형식을 예제로 설명하려고했습니다.
martinstoeckli

7

현재 생각 : 해시는 가능한 가장 느린 것이 아니라 가장 느린 것이어야합니다. 이것은 레인보우 테이블 공격을 억제 합니다 .

또한 관련이 있지만 예방 조치 : 공격자는 절대로 로그인 화면에 무제한으로 액세스 할 수 없습니다. 이를 방지하려면 : URI와 함께 모든 적중을 기록하는 IP 주소 추적 테이블을 설정하십시오. 5 분 동안 동일한 IP 주소에서 5 회 이상 로그인을 시도한 경우 설명으로 차단하십시오. 두 번째 방법은 은행과 같이 2 계층 비밀번호 체계를 사용하는 것입니다. 두 번째 패스에서 실패에 대한 잠금을 설정하면 보안이 강화됩니다.

요약 : 시간이 많이 걸리는 해시 함수를 사용하여 공격자를 느리게합니다. 또한 로그인에 대한 액세스를 너무 많이 차단하고 두 번째 비밀번호 계층을 추가하십시오.


그들은 공격자가 이미 다른 방법을 통해 내 DB를 도용했다고 가정하고 지금은 페이팔이나 무언가를 시도하기 위해 암호를 얻으려고 노력하고 있다고 생각합니다.
Vilx- December

4
2012 년의 절반 쯤되었지만이 해답은 여전히 ​​놀랍습니다. 느린 해싱 알고리즘이 레인보우 테이블 공격을 어떻게 방지합니까? 나는 임의의 바이트 범위 소금이 생각했다고? 나는 항상 해싱 알고리즘의 속도에 따라 특정 시간 내에 해시에 대해 얼마나 많은 반복을 보낼 수 있는지 생각한다고 생각했습니다. 또한 로그인이 실패한 사용자를 절대로 차단하지 마십시오. 사용자가 비밀번호를 기억하기 전에 때로는 5 배 가까이 로그인해야하는 일부 사이트에서 사용자가 지칠 것이라고 믿지 않습니다. 또한 2 차 통과 계층이 작동하지 않습니다. 휴대 전화 코드를 사용한 2 단계 인증은 가능합니다.
Sammaye

1
@Sammaye 나는 이것에 어느 정도 동의합니다. 나는 5 번의 실패한 로그인 시도에 대해 블록을 설정했다. 7 번으로 빠르게 올린 다음 20 번에 10 점으로 올랐다. 일반 사용자는 20 번의 로그인 시도를해서는 안되지만 무차별 대입 공격을 쉽게 막을 수있을 정도로 낮아야한다
Bruce Aldridge

@BruceAldridge 개인적으로 스크립트가 7 번 로그인에 실패하고 차단이 아닌 보안 문자를 표시 한 후 임의의 시간 동안 일시 중지하는 것이 더 낫다고 생각합니다. 차단은 매우 공격적인 조치입니다.
Sammaye

1
@Sammaye 나는 영구 블록이 나쁘다는 것에 동의합니다. 실패한 시도 횟수에 따라 증가하는 임시 블록을 말합니다.
Bruce Aldridge

7

이 오래된 질문에 대한 업데이트 된 답변이 있습니다!

5.5 이후로 PHP에서 비밀번호를 해시하는 올바른 방법은입니다 password_hash(). 그리고 비밀번호 를 확인하는 올바른 방법은입니다 password_verify(). 이는 PHP 8.0에서도 마찬가지입니다. 이 함수는 기본적으로 bcrypt 해시를 사용하지만 다른 강력한 알고리즘이 추가되었습니다. password_hash매개 변수 를 통해 작업 요소 (효과적으로 암호화가 얼마나 강력한 지)를 변경할 수 있습니다 .

그러나 여전히 충분히 강력하지만 bcrypt는 더 이상 최신 기술로 간주되지 않습니다 . 암호 해시 알고리즘의 더 나은 세트라는 도착했습니다 Argon2 Argon2i, Argon2d와, 그리고 Argon2id 변종. 그들 사이의 차이점 ( 여기에 설명되어 있음 ) :

Argon2에는 Argon2id와 Argon2d 및 Argon2i의 두 가지 보조 변형이 있습니다. Argon2d는 데이터 종속 메모리 액세스를 사용하므로 사이드 채널 타이밍 공격의 위협이없는 암호 화폐 및 작업 증명 애플리케이션에 적합합니다. Argon2i는 데이터 독립 메모리 액세스를 사용하는데, 이는 비밀번호 해싱 및 비밀번호 기반 키 파생에 선호됩니다. Argon2id는 메모리 전반에 대한 전반의 상반기에는 Argon2i로 작동하고 나머지는 Argon2d로 작동하므로 시간 메모리 트레이드 오프로 인한 사이드 채널 공격 방지 및 무차별 비용 절감 효과를 제공합니다.

Argon2i 지원은 PHP 7.2에서 추가되었으며 다음과 같이 요청합니다.

$hash = password_hash('mypassword', PASSWORD_ARGON2I);

그리고 Argon2id 지원은 PHP 7.3에서 추가되었습니다 :

$hash = password_hash('mypassword', PASSWORD_ARGON2ID);

결과 해시 문자열에는 생성시 사용 된 알고리즘, 솔트 및 작업 요소에 대한 정보가 포함되어 있으므로 비밀번호 확인에 변경이 필요하지 않습니다.

PHP 7.2에 추가 된 libsodium은 상당히 개별적으로 (그리고 약간 중복되어), PHP 내장 기능과 거의 같은 방식으로 작동 하는 sodium_crypto_pwhash_str ()and sodium_crypto_pwhash_str_verify()함수 를 통해 Argon2 해싱을 제공합니다 . 이것들을 사용하는 한 가지 가능한 이유는 PHP가 때때로 libargon2없이 컴파일 될 수 있고, 이로 인해 Argon2 알고리즘을 password_hash 함수에서 사용할 수 없게됩니다. PHP 7.2 이상은 항상 libsodium을 활성화해야하지만 그렇지 않을 수도 있습니다. 그러나 적어도 그 알고리즘에서 얻을 수있는 두 가지 방법이 있습니다. libsodium을 사용하여 Argon2id 해시를 생성하는 방법은 다음과 같습니다 (PHP 7.2에서도, 그렇지 않으면 Argon2id 지원이 없음).

$hash = sodium_crypto_pwhash_str(
    'mypassword',
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

소금을 수동으로 지정할 수는 없습니다. 이것은 libsodium의 기풍의 일부입니다. 사용자가 보안을 손상시킬 수있는 값으로 매개 변수를 설정할 수 없습니다 password_hash. libsodium은 당신이 그렇게 어리석은 짓을 못하게합니다!



1

우리 모두 알다시피 암호를 데이터베이스에 일반 텍스트로 저장하는 것은 안전하지 않습니다. bcrypt는 해싱 비밀번호 기술로, 비밀번호 보안을 구축하는 데 사용됩니다. bcrypt의 놀라운 기능 중 하나는 암호가 암호화 된 형식으로 저장되기 때문에 해커 공격으로부터 암호를 보호하는 데 사용되는 해커로부터 우리를 보호한다는 것입니다.

password_hash () 함수는 새 비밀번호 해시를 작성하는 데 사용됩니다. 강력하고 강력한 해싱 알고리즘을 사용합니다. password_hash () 함수는 crypt () 함수와 매우 호환됩니다. 따라서 crypt ()로 작성된 비밀번호 해시는 password_hash ()와 함께 사용할 수 있으며 그 반대도 마찬가지입니다. password_verify () 및 password_hash () 함수는 crypt () 함수를 감싸는 래퍼 일 뿐이며 정확하게 사용하기가 훨씬 쉽습니다.

통사론

string password_hash($password , $algo , $options)

다음 알고리즘은 현재 password_hash () 함수에서 지원됩니다.

PASSWORD_DEFAULT PASSWORD_BCRYPT PASSWORD_ARGON2I PASSWORD_ARGON2ID

매개 변수 :이 함수는 위에서 언급하고 아래 설명 된대로 세 개의 매개 변수를 승인합니다.

비밀번호 : 사용자의 비밀번호를 저장합니다. algo : 암호 해싱이 발생할 때 사용될 알고리즘을 나타내면서 지속적으로 사용되는 암호 알고리즘 상수입니다. 옵션 : 옵션 을 포함하는 연관 배열입니다. 이것이 제거되고 포함되지 않은 경우, 임의의 소금이 사용되며 기본 비용의 이용이 발생합니다. 반환 : 성공하면 해시 된 암호를, 실패하면 거짓을 반환합니다.

:

Input : echo password_hash("GFG@123", PASSWORD_DEFAULT); Output : $2y$10$.vGA19Jh8YrwSJFDodbfoHJIOFH)DfhuofGv3Fykk1a

아래 프로그램은 PHP의 password_hash () 함수를 보여줍니다 :

<?php echo password_hash("GFG@123", PASSWORD_DEFAULT); ?>

산출

$2y$10$Z166W1fBdsLcXPVQVfPw/uRq1ueWMA6sLt9bmdUFz9AmOGLdM393G

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