nasa9084
· 2 min read

Goのbcryptはバイナリセーフなのか

Goのbcryptはバイナリセーフなのか

TL;DR

  • Goのbcryptはバイナリセーフ

かれこれ半月は前の話題ではあるのですが、このような記事が話題になっていました。

この記事中ではPHPを例として取り扱っており、それを受けて次のような記事も公開されました。
Rubyでもbcryptはバイナリセーフではない
これはRubyで検証した記事です。

私は普段Goを書いてるので、Goではどうなのか検証してみました。
対象のパッケージはx/crypto/bcryptです。

1. 長い文字列

まずはbcryptの72文字制限から確認してみます。@fursichさんの記事に記載のある文字列をそのまま使ってみましょう。

correctPassword := "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password"

bcrypted, _ := bcrypt.GenerateFromPassword([]byte(correctPassword), 10)

// bcrypt.CompareHashAndPasswordはパスワードが正しければnil、間違っていればerrorを返す

// もちろん一致
password1 := "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password"
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, []byte(password1)))
// Output: <nil>

// もちろん不一致
password2 := "a_totally_different_thing"
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, []byte(password2)))
// Output: crypto/bcrypt: hashedPassword is not the hash of the given password

// 一致してしまう
password3 := "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_cucumber"
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, []byte(password3)))
// Output: <nil>

password4 := "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooongSpeach"
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, []byte(password4)))
// Output: <nil>

パスワードの文字数が72文字に制限されてしまい、72文字目以降が切り捨てられてしまう問題はGoのbcryptでも同様に発生しました。

2. null文字

さて、72文字制限があることがわかったところで、本題のnull文字です。PHPやrubyではbcrypt関数がnull文字以降のハッシュ化をやめてしまうことにより、バイナリセーフではない、というお話でした。Goではどうでしょうか。

まずは徳丸さんのブログに記載のあったもの、つぎに@fursichさんのブログに記載のあったものと検証してみます。
最初は徳丸さんのブログに記載されたものから。

correctPassword := "Aaaaaa3@"

shaHasher := sha512.New()
shaHasher.Write([]byte(correctPassword))
sha512Sum1 := shaHasher.Sum(nil)

// 先頭がNULLバイト
fmt.Println(hex.EncodeToString(sha512Sum1))
// Output: 0011780c00726845802482273be4b2e9329a5d403276b5088fc3e49ca866262632108b32dd2950b680a32eb3808ec9e5710af59c6f6f60f6bbcc9e17098f8685

bcrypted, _ := bcrypt.GenerateFromPassword(sha512Sum1, 10)

wrongPassword := "A very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong password"

shaHasher.Reset()
shaHasher.Write([]byte(wrongPassword))
sha512Sum2 := shaHasher.Sum(nil)

// こちらも先頭がNULLバイト
fmt.Println(hex.EncodeToString(sha512Sum2))
// 00895faa444575854626475e154dcb8407670af59d9be977a0aa855a49702e45b9c2aad59befe77241bf48f870b08c06bcf80726a6887f41689dc1f0ed977506

// 一致してしま・・・わない
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, sha512Sum2))
// Output: crypto/bcrypt: hashedPassword is not the hash of the given password

おや?一致しないですね。@fursichさんの方もやってみましょう。

correctPassword := "good_looking_password_380"

shaHasher := sha512.New()
shaHasher.Write([]byte(correctPassword))
sha512Sum1 := shaHasher.Sum(nil)

// 先頭がNULLバイト
fmt.Println(hex.EncodeToString(sha512Sum1))
// Output: 00b6e5e6355c916ad309b4986881bd0eb15cc324cbd28085b6ec1842cce40dc393a16a2a345097f07917d7501168966971fe7fdb10280adcb8af384553653edd

bcrypted, _ := bcrypt.GenerateFromPassword(sha512Sum1, 10)

wrongPassword1 := "RandomString105"

shaHasher.Reset()
shaHasher.Write([]byte(wrongPassword1))
sha512Sum2 := shaHasher.Sum(nil)

// こちらも先頭がNULLバイト
fmt.Println(hex.EncodeToString(sha512Sum2))
// 00f172bb04c41fc09f6a69fc0801473ad609fb2f7cbe420e3b9bf91a3ecb36e30617e846bae522621f22db94601e59579ad210d53a53d902d21236d2bf70f19f

// 一致してしま・・・わない
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, sha512Sum2))
// Output: crypto/bcrypt: hashedPassword is not the hash of the given password

wrongPassword2 := "good_looking_password_420"

shaHasher.Reset()
shaHasher.Write([]byte(wrongPassword2))
sha512Sum3 := shaHasher.Sum(nil)

// こちらも先頭がNULLバイト
fmt.Println(hex.EncodeToString(sha512Sum3))
// 00f99c56f7c5218ce5af9af7b80107f562d4f7dc8e52559d657f400a863665537b89df346fdb23eef771acc92c77bfa2dacd50e8d9013fb8c387bba447c7dba4

// 一致してしま・・・わない
fmt.Println(bcrypt.CompareHashAndPassword(bcrypted, sha512Sum3))
// Output: crypto/bcrypt: hashedPassword is not the hash of the given password

一致しませんでした。どうやらGoのbcrypt実装は文字列長制限はあるものの、バイナリセーフではあるようです。