취약한 암호 알고리즘 사용

취약한 암호 알고리즘 사용

설명

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

참조