Golangによるパスワードの保存
パスワードの保存方法として、
平文のパスワードを単方向ハッシュにかけて保存するのが一般的です。
ハッシュアルゴリズムにはSHA-256
、SHA-1
、MD5
等があります。
Golang は crypto
パッケージで簡単に実装することが可能です。
以下、 sha256
によるの実装例です。
//import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "my password")
fmt.Printf("% x", h.Sum(nil))
より良い方法
大抵の場合は、暗号化時に使用されたハッシュアルゴリズムが上記のような公開されているものであることが原因で、かつ技術の進歩によりパソコンの計算力も上がり、ハッカーがrainbow table
を使用することで上記の方法でハッシュされたパスワードをクラックすることが(時間的に)難しく無くなってきたのが現状です。
rainbow table
:ハッシュ結果(ダイジェスト)のデータベース
直接的な解決方法の一つは、自分でハッシュアルゴリズムをデザインすることです。
しかしながら、優良なハッシュアルゴリズムはとてもデザインが難しいのです。
そのため、実際のアプリケーションでは既存のハッシュアルゴリズムを利用して複数回ハッシュすることが行われます。
しかし単純な複数回ハッシュでは、ハッカーも当然思いつきます。
そこで、管理者自身だけが知っているランダムな文字列をさらに追加して再度ハッシュします。
そのランダム文字列を salt
**(ソルト → 塩)と言います。料理に塩***を*ひとふりするだけでグンとおいしさが増すように、ハッシュからパスワードをクラックする難易度もグンと上がります。
//import "crypto/md5"
h := md5.New()
io.WriteString(h, "your password")
pw_md5 :=fmt.Sprintf("%x", h.Sum(nil))
//saltを指定します
salt := "your salt"
//salt1+ユーザ名+salt2+MD5を連結します。
io.WriteString(h, salt)
//io.WriteString(h, "username") // ユーザ名を連結しても良い
//io.WriteString(h, salt2) // saltは複数かけても良い
io.WriteString(h, pw_md5)
result :=fmt.Sprintf("%x", h.Sum(nil))
これで、salt
が漏洩していなければ、ハッカーはもし暗号化された文字列を手に入れてもオリジナルのパスワードが何だったのか推測するのはほとんど不可能です。
もっと安全な方法
しかし、並列計算能力の向上によりrainbow table
を作成するだけの十分なリソースがあればこのような攻撃はすでにまったくもって可能です。
そこで、故意にパスワードの計算に必要となるリソースと時間を増加させることによって、誰にもrainbow table
を作成するのに必要となるリソースを与えないという方法があります。
この方法、アルゴリズムの因子(パラメータ)を調整することで計算強度を上げることにより実現できます。
Golang には既にscrypt
というライブラリが用意されていて、以下のその例です。
package main
import (
"encoding/base64"
"fmt"
"log"
"golang.org/x/crypto/scrypt"
)
func main() {
password = "your password"
// saltはご自身のものに入れ替えてください
salt := "your salt"
dk, err := scrypt.Key([]byte(password), []byte(salt), 1<<15, 8, 1, 32)
if err != nil {
log.Fatal(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(dk))
}
scrypt
の詳細については、こちらに論文があります。