説明
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の前に拒否します。鍵の供給と交換は呼び出し側で管理する必要があります。