PHPパスワード生成・認証

PHPによるパスワードの生成と認証(CRYPT_BLOWFISH)

UserFrostingではパスワードの生成にpassword_hash関数(CRYPT_BLOWFISH)を採用しています。
このログインシステムとアプリ(FlexisipはMD5関数使用)のパスワード認証システムの整合性を図るため、PHPによるログインシステムのパスワード生成・認証方法について纏めます(UserFrostingまたはFlexisipのどちらかの認証方法を採用)。

以下コマンドによりターミナルでPHPコードを実行します。

$ php -a

password_hash関数

https://www.php.net/manual/en/function.password-hash.php

password_hash ( string $password , int $algo [, array $options ] ) : string

以下 int $algo に該当する箇所に適用する定義済み定数です。
https://www.php.net/manual/en/password.constants.php

  • PASSWORD_DEFAULT
  • PASSWORD_BCRYPT - Use the CRYPT_BLOWFISH algorithm to create the hash. This will produce a standard crypt() compatible hash using the “$2y$” identifier. The result will always be a 60 character string, or FALSE on failure.
  • PASSWORD_ARGON2I
  • PASSWORD_ARGON2ID

ex)

php > $options=['cost' => 04];
php > echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options);
$2y$04$x3kiESnKtbj6/FdxUQzEE.vUhNdZP/124VsHrNU99dtxM3rXSKlFO

PASSWORD_BCRYPT(CRYPT_BLOWFISH)の詳細についてはPHPのcrypt関数を参照のこと。

https://www.php.net/manual/en/function.crypt.php

上記60文字のハッシュ出力は、

  1. $2y$:ハッシュアルゴリズム(CRYPT_BLOWFISH)
  2. 04$:ハッシュ回数(2^04=16回)
  3. x3kiESnKtbj6/FdxUQzEE.:ソルトsaltと呼ばれる22文字のランダムな文字列(自動生成)

を含んだ形で出力されます。

パスワードが同じでも生成されるハッシュ出力は異なります。同じコマンドを実行します。

php > echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options);
$2y$04$6XJn2jDzu/iWxOXobKGMPuxK77wCbSIyJDo1QPlQruqq9E5j.oaq6

パスワードとして保存される上記ハッシュ出力を、その都度変化させることでセキュリティを確保しています。パスワード認証の際には、次のpassword_verify関数により、上記項目1~3を含んだ60文字のハッシュ出力平文のパスワード(“rasmuslerdorf”) をセットで指定することで正誤を判定します。

password_verify関数

https://www.php.net/manual/en/function.password-verify.php

正しい場合は1を出力します。

password_verify ( string $password , string $hash ) : bool

ex)

php > $hash='$2y$04$x3kiESnKtbj6/FdxUQzEE.vUhNdZP/124VsHrNU99dtxM3rXSKlFO';
php > echo password_verify('rasmuslerdorf', $hash);
1

php > $hash='$2y$04$6XJn2jDzu/iWxOXobKGMPuxK77wCbSIyJDo1QPlQruqq9E5j.oaq6';
php > echo password_verify('rasmuslerdorf', $hash);
1

パスワードハッシュに何故ソルト:Saltが必要か?

平文パスワードにランダムな文字列であるSaltを混合させて複数回ハッシュすることで、この文字列が盗まれた場合でも、その解読には莫大なコンピュータリソースが必要となるため。
パスワード解読の困難度をより高くする為に必要なオプションです。

Laravel Hashing

上記LaravelによるHashファサードにより、パスワードアルゴリズムをBcryptまたはArgon2から選択します。各アルゴリズムの細かい設定はconfig/hash.php内で定義します。
上記アルゴリズム以外を利用する場合には、PHPのハッシュ関数を適用します。

UserFrostingはPHPフレームワークであるLaravelにより構築されていて、パスワード生成・認証にはFacadeクラスを利用しています。以下Password.phpでPasswordクラス内にhash関数は存在しないのですが、その場合、Facadeクラスの__callStatic():を通してHasherクラスのhash関数が呼び出されます。クラスからインスタンスを生成し処理する手間を省いているようです。

Laravel Facades

https://medium.com/a-young-devoloper/understanding-laravel-facades-4802025899e6

Registration.php

...php
protected function hashPassword()
    {
        $this->userdata['password'] = Password::hash($this->userdata['password']);
    }
...

Password.php

...

use UserFrosting\System\Facade;

class Password extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'passwordHasher';
    }
}

Facade.php

/**
     * Handle dynamic, static calls to the object.
     *
     * @param string $method
     * @param array  $args
     *
     * @throws \RuntimeException
     *
     * @return mixed
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (!$instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        switch (count($args)) {
            case 0:
                return $instance->$method();
            case 1:
                return $instance->$method($args[0]);
            case 2:
                return $instance->$method($args[0], $args[1]);
            case 3:
                return $instance->$method($args[0], $args[1], $args[2]);
            case 4:
                return $instance->$method($args[0], $args[1], $args[2], $args[3]);
            default:
                return call_user_func_array([$instance, $method], $args);
        }
    }

Hasher.php

/**
     * Hashes a plaintext password using bcrypt.
     *
     * @param string $password the plaintext password.
     * @param array  $options
     *
     * @throws HashFailedException
     *
     * @return string the hashed password.
     */
    public function hash($password, array $options = [])
    {
        $hash = password_hash($password, PASSWORD_BCRYPT, [
            'cost' => $this->cost($options),
        ]);

        if (!$hash) {
            throw new HashFailedException();
        }

        return $hash;
    }

hash関数

https://www.php.net/manual/en/function.hash.php

phpによるテキストデータのハッシュ出力の確認

$ php -a
php > echo 'MD5:      ' . hash('md5', 'yourpassword') . "\n";
MD5:      637b9adadf7acce5c70e5d327a725b13
php > echo 'SHA-256:      ' . hash('sha256', 'yourpassword') . "\n";
SHA-256:      e3c652f0ba0b4801205814f8b6bc49672c4c74e25b497770bb89b22cdeb4e951