タオルケット体操

サツバツいんたーねっと

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

photo by wolfpix

ʕ ◔ϖ◔ʔ < Go is God.

文字列からハッシュを生成して返す関数

パスワードを生の文字列のまま保存してはならない(いましめ)。

パスワードを平文で送ってくるっぽいサイトまとめ

今まではフレームワークが勝手にやってくれてる的な状態に頼ってたんですが、自分でやらないといけない空気を感じたので自分でやらないといけないようです。

最初にささっと書いたのがこれです。

import (
    "encoding/hex"
    "crypto/sha256"
)

func toHash(password string) string {
    converted := sha256.Sum256([]byte(password))
    return hex.EncodeToString(converted[:])
}

文字列からsha256ハッシュを求めて、出てきたバイトを文字列に直して返します。

が、一度のハッシュ化だけでは全然脆弱っぽいです。
単方向の暗号化はrainbow tableなどといった手法でパパッと破られるっぽいです。二重三重にハッシュ化しても無駄で、ソルトをふりかける(一度ハッシュ化した文字列に、任意の文字列を追加して再度ハッシュ化する)などの対策が必要になるっぽいです。

ここら辺で書くのが面倒くさくなってきました。
ここまで面倒かつ重要なコードであれば既に存在してるはず、しててくれ……! たのむ!

scrypt vs bcrypt

どうやら二つあるっぽい

とりあえず両方のサンプルコードを試す。

package main

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

func toHashFromScrypt(pass string) string {
    salt := []byte("some salt")
    converted, _ := scrypt.Key([]byte(pass), salt, 16384, 8, 1, 32)
    return hex.EncodeToString(converted[:])
}

func toHashFromBcrypt(pass string) string {
    converted, _ := bcrypt.GenerateFromPassword([]byte(pass), 10)
    return hex.EncodeToString(converted[:])
}

func main() {
    fmt.Println(toHashFromScrypt("boee"))
    fmt.Println(toHashFromBcrypt("boee"))
}

みた感じ、bcryptの方が高級なインターフェースを備えてるっぽい?
ただ、外部から明示的にsaltを指定出来ないっぽい。

ここら辺は疎いのでベストな方法はわからないが、とりあえずscryptを使う方針で考えておこうかなーなどとおもいました(対した理由はない)。

なおGitHubにあるbcryptは上記のものとはまた違うものであるもよう。
jameskeane/bcrypt · GitHub

なおbcryptはCompareHashAndPasswordという関数を使ってhashと比較することでタイミング攻撃の脆弱性に対処出来るらしい(参考:http://www.somethingsimilar.com/2012/05/24/finding-go.crypto-and-go.net/)。
ハード系のコードを書く場合はscryptよりもbcryptを使って、さらにbytes.Equalを使わないようにするべきだろう。

セキュリティは色々覚えることがあって大変だしめんどくさいんですけども平文パスワードをメールで送ってくるようなサービスを作らないように気をつけたいですね。

あれから4年、そこにはいまだに生パスワードを平文メールで送る企業の姿が! - ちょろげ日記

パスワードをハッシュ化する理由の徳丸さんによる解説

パスワードリマインダが駄目な理由 | 徳丸浩の日記

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践