설명
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호출 전에 거부합니다. 키 공급과 교체는 호출자가 관리해야 합니다.