もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

Goとphpでbcryptでのパスワード検証を試してみた

はじめに

こんばんは。

先週ちょっと体調悪くて、ブログ上げるのが遅くなりました。

自分が現在作成中の簡単なサービスで、パスワード認証部分にbcryptを使ってハッシュ化しようとしています。

phpであれば、 password_hash($password, PASSWORD_BCRYPT); とかで簡単にできちゃうのですが、現在勉強中のGo言語でやるにはどうするのだろうと思って、調べてやってみました。

やってみた

今回試すパスワードは 1qaz2wsx3edc で統一しています。

各バージョンは以下です。

$ php -v
PHP 7.3.1 (cli) (built: Jan 21 2019 12:58:05) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.1, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.7.0beta2-dev, Copyright (c) 2002-2018, by Derick Rethans
    
$ go version
go version go1.11 darwin/amd64

Go言語でパスワードハッシュ化

実装は、こちら のサイトを参考にさせていただきました。

実装
package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

func main() {
    password := "1qaz2wsx3edc"
    hashedPassword, hashError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if hashError != nil {
        panic(hashError.Error())
    }

    fmt.Println(string(hashedPassword))
}
結果
$ go run main.go 
$2a$10$YDZISuUQVTcza4uIM71m/uAZeGToP7SSDcxMbmtIojP2hJWzGQFPO

こんな感じです。

これで検証は終わったんですが、ふと

Golangでハッシュ化したパスワードはphpで突き合わせできるのかな?」

と思ってやってみました。

phpでパスワード検証

実装
<?php
  
$password = "1qaz2wsx3edc";
$hashedPassword = $argv[1];
if (password_verify($password, $hashedPassword)) {
        echo "OK\n";
} else {
        echo "NG\n";
}
結果
$ php password_verify.php '$2a$10$YDZISuUQVTcza4uIM71m/uAZeGToP7SSDcxMbmtIojP2hJWzGQFPO'
OK

通りました。 まぁ同じアルゴリズムだから通るよねと、予想通りでした。

Go -> phpを試してみたので、 php -> Goも試してみました。

phpでパスワードハッシュ化

実装
<?php
  
$password = "1qaz2wsx3edc";
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
echo "{$hashedPassword}\n";
結果
$ php password_hash.php 
$2y$10$hooq151ubdNmdlihER7PEenSG53YIPylUdkjljEwkmEpJ2Hk16paW

出来上がりました。これを今度はGo言語で検証します。

Go言語でパスワード検証

実装
package main

import (
    "os"
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

func main() {
    password := "1qaz2wsx3edc"
    hashedPassword := os.Args[1]
    err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
    if err != nil {
        fmt.Println("NG")
    } else {
        fmt.Println("OK")
    }
}
結果
$ go run main.go '$2y$10$hooq151ubdNmdlihER7PEenSG53YIPylUdkjljEwkmEpJ2Hk16paW'
OK

通りました。こちらも想定通りです!

phpで作成した場合とgolangで作成した場合でハッシュ値が違うことに関しては、こちらに詳しく書かれていました。

暗号化のバージョン

上記のサイトで紹介されている「暗号化のバージョン」のところで、phpで作成した場合は 2ygolangの場合は 2a が少し気になったので調べたところ、こちらのサイトに詳しく書かれていました。

以下、引用です。

BCrypt を表すバージョンは、$2$ $2a$ $2b$ $2x$ $2y$ が存在するようです。

・$2$ (Provos・Mazières, 1999)による最初のBCrypt。

・$2a$$2b$ のいくつかの実装に存在するセキュリティ上の問題を修正したバージョン。

・$2x$ crypt_blowfish の $2a$ に存在していた問題を修正する前と同一のバージョン。

・$2y$ crypt_blowfish の $2a$ に存在していた問題を修正した後と同一のバージョン。

・$2b$ 公式の BCrypt の最も新しいバージョン。

Golangの場合は決め打ちで、2a になっているみたいです。

https://github.com/golang/crypto/blob/master/bcrypt/bcrypt.go#L57-L58

phpの場合は、参考サイトにも記載されているのですが、 php自体のバージョンによって 2a でも一部問題が発生するパターンがあるとのことで、php7など、最近のバージョンを使う場合は 2y を使うことで問題ないということでした。

終わりに

ちょっと気になって調べてるうちに、どこまで調べたらいいのかわからなくなってきたので、一旦やめました。

地味にGoのコマンドラインからの引数を受け取る flag パッケージも初めて使いました。

現場からは以上です。

追記

ありがたい指摘をいただきましたので、追記します。

自分は「コマンドラインからの引数をもらえる」ような認識でいたのですが、それは、取ろうと思えば取れるという感じで、実際の使い方とは違うようです。

github.com

golang.jp

今回の自分の使い方でいうと、osパッケージで問題ないということでした。

該当のコードも修正しておきました。

ありがたや。