MD5 による弱いパスワードハッシュ

弱い、または危険な暗号アルゴリズムの使用

説明

MD5 はパスワードの保存や検証には適していません。計算が速いため、漏えいしたハッシュに対して大量の候補を試すことができ、ソルトがなければ事前計算した表も再利用できます。復元できるかはパスワードの強度と攻撃に使える資源によって異なり、一般的な衝突攻撃が特定ユーザーのパスワード復元を意味するわけではありません。

想定される影響

  • 高速なオフラインの推測により、漏えいしたハッシュからパスワードを割り出される可能性があります。
  • 盗まれたパスワードによってアカウントが乗っ取られ、個人情報の閲覧、不正な決済、管理機能の悪用などにつながるおそれがあります。
  • パスワードを使い回していると、他のサービスのアカウントも被害を受ける可能性があります。
  • 不適切な保存方式が、適用される個人情報保護やセキュリティの要件に違反し、制裁の対象になる可能性があります。

対処方法

  • bcrypt、scrypt、Argon2 など、パスワード用のハッシュ方式を使ってください。Go では golang.org/x/crypto/bcrypt を利用できます。
  • bcrypt の cost は 10 以上など十分な値にし、性能テストで調整して推測攻撃のコストを高めてください。
  • API に応じてソルトを管理してください。bcrypt.GenerateFromPassword はランダムなソルトを生成して結果に含めますが、argon2.IDKey は呼び出し側がソルトを渡す必要があります。選んだ方式に対応した、検証済みの保存形式を使ってください。
  • 既存の MD5 ハッシュは、ログイン成功時に bcrypt などで再ハッシュして置き換える段階的な移行を行ってください。
  • ハッシュ、ソルト、パスワードからの鍵導出を自作せず、実績のあるライブラリを使ってください。

例

変更前

go
package main

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

type User struct {
    PasswordHash string
}

// 変更前: MD5 でパスワードをハッシュ化
func (u *User) SetPassword(plain string) {
    h := md5.New()
    h.Write([]byte(plain))
    sum := h.Sum(nil)

    // MD5 ハッシュによるパスワード保存は危険
    u.PasswordHash = hex.EncodeToString(sum)
}

func (u *User) CheckPassword(plain string) bool {
    h := md5.New()
    h.Write([]byte(plain))
    sum := h.Sum(nil)
    return u.PasswordHash == hex.EncodeToString(sum)
}

変更後

go
package main

import (
    "fmt"

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

type User struct {
    PasswordHash string
}

// 変更後: bcrypt でパスワードをハッシュ化
func (u *User) SetPassword(plain string) error {
    // cost は 10 以上にし、性能テストで調整
    hash, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost)
    if err != nil {
        return err
    }
    u.PasswordHash = string(hash)
    return nil
}

func (u *User) CheckPassword(plain string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(plain))
    return err == nil
}

func main() {
    u := &User{}
    if err := u.SetPassword("MySecurePassword!123"); err != nil {
        panic(err)
    }

    fmt.Println("login ok?", u.CheckPassword("MySecurePassword!123")) // true
    fmt.Println("login ok?", u.CheckPassword("wrong"))               // false
}

説明:

  • 変更前: crypto/md5 は高速で、ソルトを付けずにパスワードをハッシュ化しています。GPU や ASIC で多数の候補を試すことができ、同じパスワードは同じハッシュになるため、事前計算した表も再利用できます。
  • 変更後: bcrypt.GenerateFromPassword はランダムなソルトと、計算コストの高い処理を組み合わせます。bcrypt.CompareHashAndPassword で保存済みハッシュとパスワードを照合します。ソルトや反復処理を自作せずに、MD5 より推測のコストを高められます。

参考資料