説明
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 より推測のコストを高められます。