【技術メモ】Go言語でbcryptパスワードハッシュ化の実装方法

👉 セキュアなWeb/APIサーバー実装、ユーザー認証システム、パスワード管理に必須のbcryptハッシュ化メモ。Golang、bcrypt、パスワードハッシュ、認証、セキュリティ対策のまとめ。

## bcrypt主要関数メモ

### ⚡ GenerateFromPassword
```go
func GenerateFromPassword(password []byte, cost int) ([]byte, error)
```

- **入力**: 
  - `password []byte` → 平文パスワード
  - `cost int` → 計算コスト (4-31)
- **出力**: 
  - ハッシュ値 + エラー
- **メモ**: 
  - パスワード長は最大72バイト
  - 低コスト指定時は自動的に10に引き上げ

### ⚡ CompareHashAndPassword
```go
func CompareHashAndPassword(hashedPassword, password []byte) error
```

- **入力**: 
  - `hashedPassword []byte` → 保存済みハッシュ
  - `password []byte` → 検証したいパスワード
- **出力**: 
  - 一致時 → nil
  - 不一致時 → エラー

### ⚡ コスト定数
```go
const (
    MinCost     int = 4  // 最小（非推奨）
    MaxCost     int = 31 // 最大（重い）
    DefaultCost int = 10 // 標準
)
```

## サンプルコード集

### ✅ 最小限のハッシュ化実装
```go
// パスワードハッシュ化（最小実装）
password := []byte("super-secret")
hash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
// → "$2a$10$..."
```

### ✅ パスワード検証（ログイン処理など）
```go
// 検証処理（最小実装）
err := bcrypt.CompareHashAndPassword(hash, []byte("super-secret")) 
if err == nil {
    // 認証成功
} else {
    // 認証失敗
}
```

### ✅ ハイセキュリティ設定
```go
// セキュリティ重視（処理は遅くなる）
hash, err := bcrypt.GenerateFromPassword(password, 14)
```

### ✅ 便利ユーティリティ関数
```go
// 文字列 → ハッシュ文字列 変換
func HashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

// パスワード検証（true/false形式）
func CheckPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}
```

## 実装例（認証システム）

### 👤 ユーザー登録処理
```go
func RegisterUser(email, password string) error {
    // ハッシュ生成
    hashedPW, err := HashPassword(password)
    if err != nil {
        return fmt.Errorf("パスワードハッシュ化エラー: %w", err)
    }
    
    // DBに保存
    _, err = db.Exec(
        "INSERT INTO users (email, password_hash, created_at) VALUES (?, ?, NOW())", 
        email, hashedPW
    )
    return err
}
```

### 🔐 ログイン認証処理
```go
func LoginUser(email, password string) (bool, error) {
    var hashedPW string
    
    // ハッシュ取得
    err := db.QueryRow(
        "SELECT password_hash FROM users WHERE email = ?", 
        email
    ).Scan(&hashedPW)
    
    if err != nil {
        return false, err // ユーザーなし or DBエラー
    }
    
    // パスワード検証
    return CheckPasswordHash(password, hashedPW), nil
}
```

## bcryptハッシュ構造

```
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
\__/\__/\____________________/\_____________________________/
 |   |           |                          |
 |   |           |                          +-- 実際のハッシュ値
 |   |           |
 |   |           +-- ソルト (16バイト)
 |   |
 |   +-- コスト (10)
 |
 +-- ハッシュ種別 ($2a$ = bcrypt)
```

## 重要メモ

- **DB設計**: `VARCHAR(60)`以上で保存すること
- **同一パスワード**: 毎回異なるハッシュ生成（自動ソルト）
- **コスト増加**: +1ごとにハッシュ時間約2倍に増加
- **セキュリティ**: 最低コスト10以上を推奨（OWASP基準）
- **メモリ使用**: 他ハッシュ関数より多め（考慮すべき）
- **特徴**: GPUによる総当たり攻撃に強い設計
- **注意**: Webサーバーではレスポンス時間への影響を考慮

## よくあるコード例

### JWT認証と組み合わせ
```go
// ユーザー認証後にJWTトークン発行
func GenerateJWT(userID int) (string, error) {
    // ここにJWT生成コード
}

func LoginHandler(w http.ResponseWriter, r *http.Request) {
    // 認証処理
    success, _ := LoginUser(email, password)
    if success {
        token, _ := GenerateJWT(userID)
        // tokenをクライアントに返す
    }
}
```

### パスワードリセット
```go
func ResetPassword(userID int, newPassword string) error {
    hashedPW, _ := HashPassword(newPassword)
    _, err := db.Exec("UPDATE users SET password_hash = ? WHERE id = ?", 
        hashedPW, userID)
    return err
}
```

## エラーハンドリング

```go
// よくあるエラーパターン
switch {
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
    // パスワード不一致
case errors.Is(err, bcrypt.ErrHashTooShort):
    // ハッシュ形式不正
default:
    // その他エラー
}
```

## 参考情報

https://pkg.go.dev/golang.org/x/crypto/bcrypt

https://poniblog.com/posts/tech/go-hash/