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

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

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

설명은 무엇입니까?



답변

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를 사용할 수도 있습니다 .


답변

그렇다면 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()) 직접 사용하는 경우 거의 확실하게 잘못된 일을하고있는 것입니다.

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


답변

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

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


답변

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

PHP-암호화 함수


답변


편집 : 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

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


답변

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 버전에 대한 가장 좋은 대안 인로 대체 할 수 있습니다.


답변

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

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

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