My Note

自己理解のためのブログ

GoのWebフレームワーク Echoでの開発記録(bcryptを利用してログインパスワードを暗号化)

はじめに

今回は bcrypt を利用してログインパスワードを暗号化します。

今回は以前にまとめた内容のアップデート版。 yhidetoshi.hatenablog.com

bcryptについて

説明については以下の記事がわかりやすく参照しました。

medium-company.com

  • ソルト
    • パスワードをハッシュ値へと変換する際に、パスワードに付与するランダムな文字列のこと
  • ストレッチング
  • bcrypt はこの ソルトストレッチング を実施してくれる

handler/hash/hash.go

  • サインアップとログイン時に利用するために以下のコードを用意
    • PasswordEncrypt() 平文の文字列を受け取り暗号化する
    • CheckHashPassword()暗号化された文字列と平文の文字列を受け取り一致するか確認する
package hash

import "golang.org/x/crypto/bcrypt"

// 暗号化 (hash)
func PasswordEncrypt(password string) (string, error) {
    hashPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(hashPassword), err
}

// 暗号化パスワードと比較
func CheckHashPassword(hashPassword, password string) error {
    return bcrypt.CompareHashAndPassword([]byte(hashPassword), []byte(password))
}

bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) について確認

  • costはストレッチ回数を決めるパラーメタのようですね。(コストは 4〜31で指定可能)今回はデフォルト値を指定

bcryptのコード参照

const (
    MinCost     int = 4  // the minimum allowable cost as passed in to GenerateFromPassword
    MaxCost     int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
    DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
    maxSaltSize        = 16
)

func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
    p, err := newFromPassword(password, cost)
    if err != nil {
        return nil, err
    }
    return p.Hash(), nil
}

func newFromPassword(password []byte, cost int) (*hashed, error) {
    if cost < MinCost {
        cost = DefaultCost
    }
}

bcryptのソースコードに関しては、下記の記事で詳細に説明されています。(しっかり読んでいくには時間がかかりそうなので.. 。)

go bcryptのコードを読む - Qiita

サインアップ時

  • PasswordEncrypt()に 入力フォームに入力された文字列を渡して暗号化して DBに登録する

  • handler/auth/auth.go (一部抜粋)

package auth

    signUpForm := FormData{
        Username: c.FormValue("username"),
        Password: c.FormValue("password"),
    }
    username := html.EscapeString(signUpForm.Username)
    password := html.EscapeString(signUpForm.Password)

    users := new(user.User)
    users.Password, _ = hash.PasswordEncrypt(password)
 
    user.CreateUser(users) // DBに書き込む

ログイン時

  • ログイン時に以下の2つのデータを比較してログインチェックを実施する

    • user.Password : DBから取得したパスワード(暗号化済み)
    • u.Password: 入力フォームで入力されたパスワード(平文)
  • handler/auth/auth.go (一部抜粋)

package auth

func Login(c echo.Context) error {
    loginForm := FormData{
        Username: c.FormValue("username"),
        Password: c.FormValue("password"),
    }
    // Formからデータ取得
    username := html.EscapeString(loginForm.Username)
    password := html.EscapeString(loginForm.Password)

    u := new(user.User)
    u.Username = username
    u.Password = password

    if err := c.Bind(u); err != nil {
        return err
    }

    user := user.GetUser(
        &user.User{Username: u.Username},
    )
    err := hash.CheckHashPassword(user.Password, u.Password)

    if u.Username != user.Username || err != nil { // FormとDBのデータを比較
        return &echo.HTTPError{
            Code:    http.StatusUnauthorized,
            Message: "Invalid Username or Password",
        }
    }

model/user/user.go (一部抜粋)

package user

type User struct {
    ID        int `gorm:"primaryKey"`
    Username  string
    Password  string
    CreatedAt time.Time `gorm:"autoCreateTime"`
    UpdatedAt time.Time `gorn:"autoUpdateTime"`
}

func CreateUser(u *User) {
    db.Create(u)
}

func GetUser(u *User) User {
    var user User
    db.Where(u).
        First(&user)
    return user
}

確認

サインアップページでアカウント作成

  • ID/PW は test/hogehoge で作成。

  • DBに保存されたパスワードを確認↓
mysql> select id, username, password from users where username="test";
+----+----------+--------------------------------------------------------------+
| id | username | password                                                     |
+----+----------+--------------------------------------------------------------+
|  2 | test     | $2a$10$uuY/yhmk8DMaIEI4CMzXAeOjQGBaJ6lWkr.YUDAkza.uPxfU3AIXe |
+----+----------+--------------------------------------------------------------+

パスワードの "hogehoge" が暗号化されていますね!

  • ログイン確認
❯ curl -u test:hogehoge http://localhost:8080/login  -o /dev/null -w '%{http_code}\n' -s
200

goでbcryptライブラリを利用してログインパスワードを暗号化して管理できるようになりました。