🔐 보안

암호화

Encryption

데이터를 허가된 사용자만 읽을 수 있는 형태로 변환하는 핵심 보안 기술입니다. 평문(Plaintext)을 암호문(Ciphertext)으로 변환하여 기밀성을 보장하며, 대칭키(AES), 비대칭키(RSA), 해시(SHA-256) 등 다양한 알고리즘이 용도에 따라 사용됩니다.

📖 암호화의 역사

BC 700

고대 암호

스키탈레(Scytale) 암호 - 막대에 가죽을 감아 메시지를 숨김. 시저 암호(Caesar Cipher) - 알파벳을 일정 수만큼 밀어서 치환.

1940s

기계식 암호

독일의 에니그마(Enigma) 기계. 앨런 튜링이 해독에 성공하여 2차 세계대전 종전을 앞당김. 현대 컴퓨터 과학의 시초.

1976

공개키 암호의 탄생

Diffie-Hellman 키 교환 발표. 1977년 RSA 알고리즘 개발. 키 배포 문제 해결로 인터넷 보안의 기반 마련.

2001

AES 표준화

미국 NIST에서 AES(Advanced Encryption Standard)를 표준으로 채택. 전 세계 정부, 금융기관, 기업의 표준 암호화 방식으로 사용.

2020s

양자 내성 암호

양자 컴퓨터 위협에 대비한 Post-Quantum Cryptography(PQC) 연구. NIST에서 CRYSTALS-Kyber, CRYSTALS-Dilithium 등 표준화 진행.

🔐 정보보안의 3요소 (CIA Triad)

암호화는 정보보안의 핵심 3요소를 실현하는 기술적 수단입니다.

🔒
기밀성
Confidentiality
허가된 사용자만 정보에 접근할 수 있음. AES, RSA 등 암호화로 구현.
무결성
Integrity
정보가 변조되지 않았음을 보장. SHA-256 해시, HMAC으로 검증.
🆔
인증
Authentication
사용자/시스템의 신원 확인. 디지털 서명, 인증서로 구현.

🔑 암호화 유형 비교

🔐
대칭키 암호화
암호화/복호화에 동일한 키 사용. 빠른 속도, 대용량 데이터에 적합.
AES-256 ChaCha20 3DES
🔓
비대칭키 암호화
공개키/개인키 쌍 사용. 키 교환 문제 해결, 디지털 서명 가능.
RSA ECC Ed25519
#️⃣
해시 함수
단방향 변환(복호화 불가). 무결성 검증, 패스워드 저장에 사용.
SHA-256 bcrypt Argon2

📊 주요 알고리즘 비교

알고리즘 유형 키 크기 속도 주요 용도 보안 상태
AES-256 대칭키 256 bit 매우 빠름 파일 암호화, TLS, 디스크 권장
ChaCha20 대칭키 256 bit 매우 빠름 모바일, IoT, TLS 1.3 권장
RSA-2048 비대칭키 2048 bit 느림 키 교환, 디지털 서명 권장 (4096 선호)
ECC P-256 비대칭키 256 bit 빠름 TLS, 인증서, 서명 권장
SHA-256 해시 256 bit 매우 빠름 무결성 검증, 블록체인 권장
Argon2id 해시(KDF) 가변 의도적 지연 패스워드 저장 최고 권장
DES 대칭키 56 bit 빠름 레거시 시스템 사용 금지
MD5 해시 128 bit 빠름 체크섬(비보안) 보안 용도 금지

💻 실제 구현 코드

# AES-256-GCM 암호화 (Python cryptography 라이브러리)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import base64

class AESEncryptor:
    """AES-256-GCM 암호화 클래스 (인증된 암호화)"""

    def __init__(self, key: bytes = None):
        # 키가 없으면 256비트(32바이트) 랜덤 키 생성
        self.key = key or AESGCM.generate_key(bit_length=256)
        self.aesgcm = AESGCM(self.key)

    def encrypt(self, plaintext: str, associated_data: bytes = None) -> str:
        """평문을 암호화하여 Base64 문자열로 반환"""
        # 12바이트 nonce (GCM 권장 크기)
        nonce = os.urandom(12)

        # 암호화 (인증 태그 자동 추가)
        ciphertext = self.aesgcm.encrypt(
            nonce,
            plaintext.encode('utf-8'),
            associated_data
        )

        # nonce + ciphertext를 Base64로 인코딩
        return base64.b64encode(nonce + ciphertext).decode('utf-8')

    def decrypt(self, encrypted: str, associated_data: bytes = None) -> str:
        """암호문을 복호화하여 평문 반환"""
        data = base64.b64decode(encrypted)
        nonce, ciphertext = data[:12], data[12:]

        # 복호화 (인증 실패 시 예외 발생)
        plaintext = self.aesgcm.decrypt(nonce, ciphertext, associated_data)
        return plaintext.decode('utf-8')

# 사용 예시
encryptor = AESEncryptor()

# 민감한 데이터 암호화
secret_data = "주민등록번호: 901234-1234567"
encrypted = encryptor.encrypt(secret_data)
print(f"암호화: {encrypted}")

# 복호화
decrypted = encryptor.decrypt(encrypted)
print(f"복호화: {decrypted}")

# 키 안전 저장 (실제로는 KMS, HSM 사용)
key_b64 = base64.b64encode(encryptor.key).decode()
print(f"키(안전 저장 필요): {key_b64}")
# RSA 암호화 + 디지털 서명 (Python cryptography)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization
import base64

class RSACrypto:
    """RSA 암호화 및 디지털 서명"""

    def __init__(self, key_size: int = 4096):
        # RSA 키 쌍 생성 (최소 2048, 권장 4096)
        self.private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=key_size
        )
        self.public_key = self.private_key.public_key()

    def encrypt(self, plaintext: str) -> str:
        """공개키로 암호화 (상대방이 보낸 공개키 사용)"""
        ciphertext = self.public_key.encrypt(
            plaintext.encode('utf-8'),
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        return base64.b64encode(ciphertext).decode('utf-8')

    def decrypt(self, ciphertext: str) -> str:
        """개인키로 복호화 (자신의 개인키 사용)"""
        plaintext = self.private_key.decrypt(
            base64.b64decode(ciphertext),
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        return plaintext.decode('utf-8')

    def sign(self, message: str) -> str:
        """개인키로 서명 (발신자 신원 증명)"""
        signature = self.private_key.sign(
            message.encode('utf-8'),
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return base64.b64encode(signature).decode('utf-8')

    def verify(self, message: str, signature: str) -> bool:
        """공개키로 서명 검증 (발신자 확인)"""
        try:
            self.public_key.verify(
                base64.b64decode(signature),
                message.encode('utf-8'),
                padding.PSS(
                    mgf=padding.MGF1(hashes.SHA256()),
                    salt_length=padding.PSS.MAX_LENGTH
                ),
                hashes.SHA256()
            )
            return True
        except:
            return False

# 사용 예시: 안전한 메시지 전송
alice = RSACrypto()
bob = RSACrypto()

# Alice가 Bob에게 암호화된 메시지 전송
message = "계약금 1억원을 아래 계좌로 송금"

# Bob의 공개키로 암호화 (Bob만 복호화 가능)
encrypted = bob.encrypt(message)

# Alice의 개인키로 서명 (Alice가 보낸 것 증명)
signature = alice.sign(message)

# Bob이 수신 후
decrypted = bob.decrypt(encrypted)
is_valid = alice.verify(decrypted, signature)  # Alice 공개키로 검증

print(f"메시지: {decrypted}")
print(f"서명 검증: {'유효' if is_valid else '위조 의심'}")
# 패스워드 안전 저장 (bcrypt + Argon2)
import bcrypt
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import secrets

class PasswordManager:
    """안전한 패스워드 관리 (Argon2id 권장)"""

    def __init__(self, use_argon2: bool = True):
        self.use_argon2 = use_argon2
        if use_argon2:
            # Argon2id: 메모리 하드 + GPU 저항
            self.hasher = PasswordHasher(
                time_cost=3,          # 반복 횟수
                memory_cost=65536,     # 64MB 메모리
                parallelism=4,         # 4 스레드
                hash_len=32,           # 해시 길이
                salt_len=16            # Salt 길이
            )

    def hash_password(self, password: str) -> str:
        """패스워드를 해시하여 저장용 문자열 반환"""
        if self.use_argon2:
            return self.hasher.hash(password)
        else:
            # bcrypt: 자동 salt 생성, cost factor 12 권장
            salt = bcrypt.gensalt(rounds=12)
            return bcrypt.hashpw(password.encode(), salt).decode()

    def verify_password(self, password: str, hash_str: str) -> bool:
        """입력된 패스워드가 저장된 해시와 일치하는지 확인"""
        try:
            if self.use_argon2:
                self.hasher.verify(hash_str, password)
                return True
            else:
                return bcrypt.checkpw(password.encode(), hash_str.encode())
        except (VerifyMismatchError, ValueError):
            return False

    def needs_rehash(self, hash_str: str) -> bool:
        """해시 파라미터 업그레이드 필요 여부 확인"""
        if self.use_argon2:
            return self.hasher.check_needs_rehash(hash_str)
        return False

# 사용 예시: 회원가입/로그인
pm = PasswordManager(use_argon2=True)

# 회원가입 시
user_password = "MySecure@Pass123!"
stored_hash = pm.hash_password(user_password)
print(f"DB 저장: {stored_hash}")

# 로그인 시
login_attempt = "MySecure@Pass123!"
if pm.verify_password(login_attempt, stored_hash):
    print("로그인 성공!")

    # 해시 파라미터 업그레이드 체크
    if pm.needs_rehash(stored_hash):
        new_hash = pm.hash_password(login_attempt)
        print("해시 업그레이드 완료")
else:
    print("로그인 실패")

# 패스워드 강도 검증 추가 권장
def check_password_strength(password: str) -> dict:
    return {
        "length": len(password) >= 12,
        "uppercase": any(c.isupper() for c in password),
        "lowercase": any(c.islower() for c in password),
        "digit": any(c.isdigit() for c in password),
        "special": any(c in "!@#$%^&*" for c in password)
    }

💬 대화로 배우기

👤
주니어 개발자
데이터베이스에 사용자 비밀번호를 저장해야 하는데, AES로 암호화하면 되나요?
🔐
보안 엔지니어
절대 안 됩니다! AES는 복호화가 가능한 양방향 암호화입니다. 패스워드는 해시로 저장해야 합니다. Argon2id나 bcrypt를 사용하세요. 공격자가 DB를 탈취해도 원본 패스워드를 알 수 없습니다.
👤
주니어 개발자
그런데 SHA-256이 더 빠르고 유명하지 않나요? 왜 Argon2를 쓰나요?
🔐
보안 엔지니어
바로 그 "빠름"이 문제입니다! SHA-256은 초당 수십억 번 해시를 계산할 수 있어서 무차별 대입 공격에 취약합니다. Argon2는 의도적으로 느리고 많은 메모리를 사용하게 설계되어 GPU 병렬 공격도 어렵습니다.
👤
주니어 개발자
API 서버 간 통신에는 어떤 암호화를 써야 하나요?
🔐
보안 엔지니어
TLS 1.3을 사용하세요. TLS는 내부적으로 RSA/ECDHE로 키 교환, AES-GCM/ChaCha20으로 데이터 암호화를 조합합니다. 직접 구현하지 말고 검증된 라이브러리(OpenSSL, BoringSSL)를 사용하는 것이 핵심입니다.

⚠️ 주의사항

🚫

자체 암호화 알고리즘 금지

"Don't roll your own crypto" - 직접 만든 암호화 알고리즘은 반드시 취약점이 있습니다. AES, RSA, SHA-256 등 검증된 표준만 사용하세요.

🔒

ECB 모드 절대 사용 금지

AES-ECB는 같은 평문이 같은 암호문이 되어 패턴이 노출됩니다. 반드시 AES-GCM 또는 AES-CBC(IV 사용)를 사용하세요.

🔑

키 관리가 암호화보다 중요

암호화 키를 코드에 하드코딩하거나 Git에 커밋하면 암호화 의미가 없습니다. AWS KMS, HashiCorp Vault, Azure Key Vault 등 전용 키 관리 시스템을 사용하세요.

양자 컴퓨터 대비

2030년대 양자 컴퓨터가 RSA, ECC를 무력화할 수 있습니다. 현재 수집된 암호화 데이터가 미래에 해독될 수 있으니, 민감 데이터는 Post-Quantum 암호화 전환을 계획하세요.

🔥 암호화 실패 사례

Adobe 데이터 유출
2013년

사건: Adobe에서 1억 5,300만 개의 사용자 계정 정보가 유출되었습니다. 패스워드가 AES-ECB 모드로 암호화되어 있었는데, 같은 패스워드는 같은 암호문이 되는 ECB의 특성으로 인해 패턴 분석이 가능했습니다.

문제점: 패스워드에 양방향 암호화(AES)를 사용한 것 자체가 잘못이며, ECB 모드는 패턴을 숨기지 못합니다. 가장 많이 사용된 패스워드 "123456"의 암호문이 동일하여 쉽게 역추적되었습니다.

1.53억 계정 노출 ECB 모드 취약점 패스워드 패턴 노출
교훈:

패스워드는 반드시 해시(bcrypt, Argon2)로 저장. 암호화 시 ECB 모드 절대 금지.

Heartbleed (OpenSSL 취약점)
2014년

사건: OpenSSL의 Heartbeat 확장 기능에서 버퍼 오버리드 취약점이 발견되었습니다. 공격자가 서버 메모리의 64KB를 읽을 수 있어, 암호화에 사용되는 개인키가 노출되었습니다.

영향: 전 세계 웹 서버의 약 17%(50만 대 이상)가 영향을 받았습니다. 개인키가 노출되면 과거에 캡처된 모든 TLS 통신이 복호화될 수 있습니다.

50만+ 서버 영향 개인키 노출 위험 과거 통신 해독 가능
교훈:

암호화 라이브러리 정기 업데이트 필수. Forward Secrecy(ECDHE) 사용으로 키 노출 피해 최소화.

WannaCry 랜섬웨어
2017년

사건: WannaCry 랜섬웨어가 150개국 30만 대 이상의 컴퓨터를 감염시켰습니다. 피해자의 파일을 AES로 암호화한 후 RSA 공개키로 AES 키를 암호화하여, 공격자의 개인키 없이는 복호화가 불가능했습니다.

피해: 영국 NHS(국민건강서비스)는 수술 취소, 구급차 우회 등 의료 서비스가 마비되었습니다. 전 세계 피해액은 약 40~80억 달러로 추산됩니다.

30만+ 컴퓨터 감염 40~80억 달러 피해 NHS 의료 서비스 중단
교훈:

암호화는 양날의 검. 정기 백업, 패치 관리, 네트워크 분리로 랜섬웨어 대비.

🧠 이해도 퀴즈

Q. 사용자 패스워드를 데이터베이스에 저장할 때 가장 적합한 방법은?
A. AES-256-GCM으로 암호화하여 저장
B. SHA-256으로 해시하여 저장
C. Argon2id 또는 bcrypt로 해시하여 저장
D. RSA 공개키로 암호화하여 저장
정답: C

패스워드는 복호화가 불가능한 해시로 저장해야 합니다. AES/RSA는 복호화가 가능하므로 키가 노출되면 모든 패스워드가 유출됩니다.

SHA-256은 빠른 해시 함수로 초당 수십억 번 계산이 가능하여 무차별 대입 공격에 취약합니다.

Argon2id(권장)와 bcrypt는 의도적으로 느리고 메모리를 많이 사용하여 GPU 병렬 공격에 강합니다.

🔗 연관 용어

📚 학습 자료