時々私は「PHPでパスワードを保存するのにbcryptを使う、bcryptルール」というアドバイスを聞きます。
しかしbcrypt
は何ですか? PHPはそのような機能を何も提供していません、ウィキペディアはファイル暗号化ユーティリティとWeb検索では異なる言語での Blowfish の実装をいくつか明らかにしています。 Blowfishはmcrypt
を介してPHPでも利用できるようになりましたが、パスワードの保存にどのように役立ちますか?フグは一般的な目的の暗号です、それは2つの方法で働きます。暗号化できる場合は、復号化できます。パスワードは一方向ハッシュ関数を必要とします。
説明は何ですか?
bcrypt
は(設定可能なラウンド数を介して)ハードウェアで拡張可能なハッシュアルゴリズムです。その遅さと複数のラウンドにより、攻撃者はパスワードを解読できるようにするために膨大な資金とハードウェアを配置する必要があります。そのパスワードごとに追加してください salt (bcrypt
はsaltを必要とします)そしてあなたは莫大な量の資金やハードウェアなしには攻撃は事実上実行不可能であると確信できます。
bcrypt
は、パスワードをハッシュするためにEksblowfishアルゴリズムを使用します。 EksblowfishとBlowfishの暗号化フェーズはまったく同じですが、Eksblowfishのキースケジュールフェーズでは、以降のすべての状態はsaltとkeyの両方に依存します。両方の知識がなくても、どの状態も事前計算できません。 この重要な違いのため、bcrypt
は一方向ハッシュアルゴリズムです。 ソルトを知らずにプレーンテキストのパスワードを取得することはできません。 とkey (パスワード)を丸めてください。 [ 出典 ]
パスワードハッシュ関数 は直接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.';
}
互換ライブラリ on GitHub は、もともとCで書かれた上記の関数のソースコードに基づいて作成されており、これは同じ機能を提供します。互換性ライブラリがインストールされると、使用方法は上記と同じです(まだ5.3.xブランチにいる場合は、省略形の配列表記を除く)。
crypt()
関数を使って入力文字列のbcryptハッシュを生成することができます。このクラスは自動的にsaltを生成し、入力に対して既存のハッシュを検証します。 バージョン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をめちゃくちゃにする にするのはとても簡単です。実際、このページのほとんどすべてのコードを見ると、これらの一般的な問題の少なくとも1つに違反していることがわかります。
専門家にお任せください。これらのライブラリを維持するのが仕事である人々に任せてください。決定を下す必要がある場合、それは間違っています。
代わりに、ライブラリを使用してください。要件に応じていくつかあります。
一般的なAPIの内訳を次に示します。
PHP 5.5以降、パスワードをハッシュするための新しいAPIが導入されています。 5.3.7+向けに(私によって)維持されているshim互換性ライブラリもあります。これには、査読済みでsimple実装を使用するという利点があります。
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
}
}
本当に、それは非常にシンプルであることを目指しています。
リソース:
これは、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は、単に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
}
}
参照:
これは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が内部的に機能する方法が原因です。そして、それについてたくさんの答えがあります。ここに私が書いたセレクションがあり、ここではコピー/ペーストしませんが、リンクします:
md5
パスワードをbcryptに移行するさまざまな選択肢があります。どちらを選ぶかはあなた次第です。ただし、を強くお勧めしますこれを処理するために上記のライブラリのいずれかを使用することをお勧めします。
繰り返しますが、crypt()
を直接使用している場合、おそらく何か間違ったことをしていることになります。コードでhash()
(またはmd5()
またはsha1()
)を直接使用している場合、ほとんど間違いなく何か間違ったことをしていることになります。
ライブラリを使用するだけです...
レインボーテーブルで十分な情報を得ることができます。安全なパスワードスキームについて知っておくべきことまたはPortable PHP password hashingフレームワーク。
目標は、パスワードを遅くすることでパスワードをハッシュ化することです。パスワードデータベースを取得している人がそれをブルートフォースしようとして死ぬことになります(パスワードをチェックするための10ミリ秒の遅れはあなたにとって何もありません。 Bcrypt は遅く、どれぐらい遅いかを選択するためにパラメータと共に使用できます。
PHPのcrypt()
関数を使い、適切なBlowfishソルトを渡すことで、bcryptで一方向ハッシュを作成できます。方程式全体の中で最も重要なのは、A)アルゴリズムが危険にさらされていないこと、およびB) 各パスワードを正しく塩にすることです です。アプリケーション全体の塩を使用しないでください。これにより、アプリケーション全体が単一のRainbowテーブルから攻撃される可能性があります。
編集: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
それは明白であるべきだと私は知っています、しかしあなたのパスワードとして 'password'を使わないでください。
PHPのバージョン5.5では、BCrypt、 password_hash()
、および password_verify()
のサポートが組み込まれています。実際にこれらは関数 crypt()
のまわりのラッパーであり、正しく使うことをより簡単にするでしょう。安全なランダムソルトの生成に注意を払い、良いデフォルト値を提供します。
この機能を使用する最も簡単な方法は次のとおりです。
$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
$isPasswordCorrect = password_verify($password, $existingHashFromDb);
このコードは、パスワードをBCrypt(アルゴリズム2y
)でハッシュし、OSのランダムなソースからランダムなsaltを生成し、デフォルトのcostパラメータを使用します(現時点では10)。 2行目では、ユーザーがパスワードを入力したときに、すでに保存されているハッシュ値と一致するかどうかを確認します。
コストパラメータを変更したい場合は、コストパラメータを1増やしてハッシュ値を計算するのに必要な時間を2倍にして、次のようにすることができます。
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 11));
"cost"
パラメータとは対照的に、関数はすでに暗号的に安全なsaltを作成するために最善を尽くしているので、"salt"
パラメータを省略するのが最善です。
PHPバージョン5.3.7以降では、password_hash()
関数を作ったのと同じ作者からの 互換パック があります。 5.3.7より前のPHPバージョンでは、Unicode安全なBCryptアルゴリズムである2y
を伴うcrypt()
のサポートはありません。代わりに2a
に置き換えることができます。これは、初期のPHPバージョンの最良の選択肢です。
別の方法は、 彼の論文 のColin Percivalによるbcryptよりも優れているように特に設計されたscryptを使用することです。 PECLではscrypt PHP拡張子 があります。理想的にはこのアルゴリズムはPHPにロールインされ、それがpassword_ *関数に対して(理想的には "PASSWORD_SCRYPT"として)指定できるようになるでしょうが、それはまだありません。
現在の考え:ハッシュは、利用可能な最も遅いものではなく、最も遅いものであるべきです。これは レインボーテーブル を抑制します。
関連性はありますが、予防策:攻撃者があなたのログイン画面に無制限にアクセスすることは絶対にありません。これを防ぐには、次のようにします。すべてのヒットをURIとともに記録するIPアドレス追跡テーブルを設定します。 5分以内に同じIPアドレスから5回以上ログインが試行された場合は、説明をブロックしてください。 2番目のアプローチは、銀行がそうであるように、2段階のパスワードスキームを持つことです。 2回目のパスで失敗に対するロックアウトを設定すると、セキュリティが向上します。
概要:時間のかかるハッシュ関数を使用して攻撃者を遅らせます。また、ログインへのアクセスが多すぎることをブロックし、2番目のパスワード層を追加します。
OAuth 2 パスワードの場合:
$bcrypt = new \Zend\Crypt\Password\Bcrypt;
$bcrypt->create("youpasswordhere", 10)