弱い暗号アルゴリズムの使用

弱い暗号アルゴリズムの使用

説明

MD5とSHA-1の衝突に関する弱点は、異なるデータを同じハッシュにし、完全性や署名の検証を弱めます。DESの短い鍵、3DESの小さなブロックサイズと限られた安全余裕、RC4の鍵ストリームの偏りは、別の暗号化リスクを生みます。ハッシュ衝突と暗号の解読は異なる問題であり、実際の用途に合う方式への変更が必要です。

想定される影響

  • 衝突攻撃により、改ざんされたデータや署名が検証を通過するおそれがあります。
  • 暗号方式と使用量によっては、機密の平文が漏えいする可能性があります。
  • 不適切なトークン認証方式では、メッセージを偽造されるおそれがあります。

対処方法

  • 衝突耐性が必要なハッシュには、SHA-256などのSHA-2またはSHA-3を使ってください。攻撃者がデータとハッシュを両方変更できる場合、単純なハッシュだけでは完全性を認証できません。
  • メッセージ認証には専用の秘密鍵を使うHMAC-SHA256を適用し、検証には hmac.Equal を使ってください。パスワード保存には専用のパスワードハッシュ関数を使ってください。
  • DES、3DES、RC4をAES-GCMなどの認証付き暗号へ変更してください。CBCが不可避な場合は、予測できないIVと独立した鍵によるencrypt-then-MACが必要です。
  • 鍵はハードコードせず、承認されたシークレット・鍵管理システムから供給してください。
  • 同じ鍵でGCMのnonceを再利用しないでください。この例のようなランダムnonceでは、鍵ごとの暗号化回数が 2^32 を超えないよう管理してください。

例

変更前の3DES関数は、ブロックサイズの倍数の入力しか扱えない不適切な暗号化例です。それ以外の長さではパニックが発生する可能性があり、認証も提供しません。

変更前

go
package insecure

import (
    "crypto/des"
    "crypto/md5"
    "crypto/rc4"
    "crypto/sha1"
)

// ファイルの完全性検証にMD5を使用(弱い方式)
func CheckFileIntegrityMD5(data []byte) [16]byte {
    return md5.Sum(data)
}

// トークンの認証にSHA-1を使用(弱い方式)
func SignTokenSHA1(token string, secret []byte) []byte {
    h := sha1.New()
    h.Write(secret)
    h.Write([]byte(token))
    return h.Sum(nil)
}

// 機密データの暗号化に3DESを使用(弱い方式)
func EncryptWith3DES(key, plaintext []byte) ([]byte, error) {
    block, err := des.NewTripleDESCipher(key)
    if err != nil {
        return nil, err
    }
    ciphertext := make([]byte, len(plaintext))
    // 単純化した例: モードなしでブロックを直接処理(別の危険もある)
    for i := 0; i < len(plaintext); i += block.BlockSize() {
        block.Encrypt(ciphertext[i:], plaintext[i:])
    }
    return ciphertext, nil
}

// RC4によるストリーム暗号(弱い方式)
func EncryptWithRC4(key, plaintext []byte) ([]byte, error) {
    c, err := rc4.NewCipher(key)
    if err != nil {
        return nil, err
    }
    dst := make([]byte, len(plaintext))
    c.XORKeyStream(dst, plaintext)
    return dst, nil
}

変更後

go
package secure

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/hmac"
    "crypto/rand"
    "crypto/sha256"
    "io"
    "fmt"
)

// ファイルの完全性検証にSHA-256を使用
func CheckFileIntegritySHA256(data []byte) [32]byte {
    return sha256.Sum256(data)
}

// トークンの認証にHMAC-SHA256を使用
func SignTokenHMAC(token string, secret []byte) []byte {
    mac := hmac.New(sha256.New, secret)
    mac.Write([]byte(token))
    return mac.Sum(nil)
}

// AES-GCMによる認証付き暗号化
func EncryptWithAESGCM(key, plaintext []byte) (nonce []byte, ciphertext []byte, err error) {
    block, err := aes.NewCipher(key) // 16、24、32バイトの鍵(AES-128/192/256)
    if err != nil {
        return nil, nil, err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, nil, err
    }

    nonce = make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, nil, err
    }

    ciphertext = gcm.Seal(nil, nonce, plaintext, nil)
    return nonce, ciphertext, nil
}

func DecryptWithAESGCM(key, nonce, ciphertext []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    if len(nonce) != gcm.NonceSize() {
        return nil, fmt.Errorf("invalid nonce length")
    }

    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return nil, err
    }
    return plaintext, nil
}

解説:

  • 変更前: 弱いハッシュ、HMACではない SHA1(secret || token)、認証のない3DESブロック処理、RC4を使います。それぞれのリスクは、衝突耐性、メッセージ認証、暗号化の用途によって異なります。
  • 変更後: SHA-256、HMAC-SHA256、AES-GCMを用途別に使います。nonceを暗号文と一緒に保存し、復号に失敗したデータは使用しないでください。不正なnonce長は Open の前に拒否します。鍵の供給と交換は呼び出し側で管理する必要があります。

参考資料