Encryption
암호화
데이터를 읽을 수 없는 형태로 변환하여 기밀성을 보호하는 기술입니다. 대칭키(AES), 비대칭키(RSA) 암호화와 해시 함수로 구분됩니다.
암호화
데이터를 읽을 수 없는 형태로 변환하여 기밀성을 보호하는 기술입니다. 대칭키(AES), 비대칭키(RSA) 암호화와 해시 함수로 구분됩니다.
암호화(Encryption)는 평문(Plaintext)을 암호문(Ciphertext)으로 변환하여 인가되지 않은 접근으로부터 데이터를 보호하는 기술입니다. 암호화의 역과정인 복호화(Decryption)는 암호문을 다시 평문으로 변환합니다. 현대 암호학은 수학적 알고리즘과 키(Key)를 사용하여 컴퓨터로도 해독이 사실상 불가능한 수준의 보안을 제공합니다.
대칭키 암호화(Symmetric Encryption)는 암호화와 복호화에 동일한 키를 사용합니다. AES, ChaCha20이 대표적이며, 속도가 빠르고 대용량 데이터 처리에 적합합니다. 단점은 키 교환 문제로, 안전하게 키를 전달하는 방법이 필요합니다. 비대칭키 암호화(Asymmetric Encryption)는 공개키와 개인키 쌍을 사용합니다. RSA, ECDSA가 대표적이며, 공개키로 암호화하면 개인키로만 복호화할 수 있습니다. 키 교환 없이 안전한 통신이 가능하지만 대칭키보다 느립니다.
해시 함수(Hash Function)는 암호화와 다르게 단방향 변환입니다. 임의 크기 입력을 고정 크기 출력(해시값)으로 변환하며, 원본 복원이 불가능합니다. SHA-256, bcrypt, Argon2가 대표적입니다. 비밀번호 저장, 데이터 무결성 검증, 디지털 서명에 사용됩니다. 같은 입력은 항상 같은 해시를 생성하지만, 해시에서 원본을 역추적하는 것은 계산상 불가능합니다.
실무에서는 하이브리드 방식을 사용합니다. TLS 핸드셰이크에서 비대칭키로 세션 키를 안전하게 교환하고, 이후 데이터 전송은 대칭키(세션 키)로 암호화합니다. 이렇게 하면 비대칭키의 보안성과 대칭키의 속도를 모두 활용할 수 있습니다. 저장 데이터(at-rest)는 AES-256으로, 전송 데이터(in-transit)는 TLS로 보호하는 것이 표준입니다.
# AES-256-GCM 대칭키 암호화 - Python cryptography
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import base64
class SymmetricEncryption:
"""대칭키 암호화: 같은 키로 암호화/복호화"""
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) -> dict:
"""
평문 암호화
GCM 모드는 암호화 + 무결성 검증(AEAD) 제공
"""
# 12바이트 nonce (매번 새로 생성!)
nonce = os.urandom(12)
# 암호화 (UTF-8 인코딩)
ciphertext = self.aesgcm.encrypt(
nonce,
plaintext.encode('utf-8'),
None # associated_data (선택)
)
return {
'nonce': base64.b64encode(nonce).decode(),
'ciphertext': base64.b64encode(ciphertext).decode()
}
def decrypt(self, encrypted: dict) -> str:
"""암호문 복호화"""
nonce = base64.b64decode(encrypted['nonce'])
ciphertext = base64.b64decode(encrypted['ciphertext'])
plaintext = self.aesgcm.decrypt(nonce, ciphertext, None)
return plaintext.decode('utf-8')
# 사용 예시
if __name__ == "__main__":
crypto = SymmetricEncryption()
# 민감 데이터 암호화
original = "사용자 비밀번호: P@ssw0rd123"
encrypted = crypto.encrypt(original)
print(f"암호문: {encrypted['ciphertext'][:40]}...")
# 복호화
decrypted = crypto.decrypt(encrypted)
print(f"복호화: {decrypted}")
# 키 저장 (안전한 곳에!)
print(f"키: {base64.b64encode(crypto.key).decode()}")
# RSA 비대칭키 암호화 - 공개키/개인키 쌍 사용
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
class AsymmetricEncryption:
"""비대칭키 암호화: 공개키로 암호화, 개인키로 복호화"""
def __init__(self):
# RSA 키 쌍 생성 (2048비트 이상 권장)
self.private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
self.public_key = self.private_key.public_key()
def encrypt(self, plaintext: str) -> bytes:
"""공개키로 암호화 (누구나 암호화 가능)"""
ciphertext = self.public_key.encrypt(
plaintext.encode('utf-8'),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return ciphertext
def decrypt(self, ciphertext: bytes) -> str:
"""개인키로 복호화 (키 소유자만 복호화 가능)"""
plaintext = self.private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return plaintext.decode('utf-8')
def export_public_key(self) -> str:
"""공개키를 PEM 형식으로 내보내기 (공유 가능)"""
pem = self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
return pem.decode('utf-8')
def sign(self, message: str) -> bytes:
"""개인키로 서명 (발신자 증명)"""
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 signature
def verify(self, message: str, signature: bytes) -> bool:
"""공개키로 서명 검증"""
try:
self.public_key.verify(
signature,
message.encode('utf-8'),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return True
except Exception:
return False
# 사용 예시: 안전한 메시지 전송
sender = AsymmetricEncryption()
receiver = AsymmetricEncryption()
# 1. 발신자가 수신자의 공개키로 암호화
message = "기밀 정보입니다"
encrypted = receiver.public_key.encrypt(
message.encode(),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"암호화 완료: {len(encrypted)} bytes")
# 2. 수신자가 자신의 개인키로 복호화
decrypted = receiver.decrypt(encrypted)
print(f"복호화: {decrypted}")
# 해시 함수 & 비밀번호 해싱
import hashlib
import bcrypt
import secrets
# === 1. SHA-256 해시 (데이터 무결성 검증) ===
def sha256_hash(data: str) -> str:
"""SHA-256 해시 생성 - 파일 무결성, 데이터 검증용"""
return hashlib.sha256(data.encode()).hexdigest()
# 같은 입력 = 같은 해시
print(sha256_hash("hello")) # 2cf24dba5fb0a30e26e83b2ac5b9e29e...
print(sha256_hash("hello")) # 동일한 해시
# 작은 변화 = 완전히 다른 해시 (눈사태 효과)
print(sha256_hash("hello!")) # 전혀 다른 값
# === 2. 비밀번호 해싱 (bcrypt 권장) ===
def hash_password(password: str) -> bytes:
"""
비밀번호 해싱 - bcrypt 사용
- 솔트 자동 생성
- 계산 비용 조절 가능 (brute-force 방어)
"""
salt = bcrypt.gensalt(rounds=12) # 2^12 반복
return bcrypt.hashpw(password.encode(), salt)
def verify_password(password: str, hashed: bytes) -> bool:
"""비밀번호 검증"""
return bcrypt.checkpw(password.encode(), hashed)
# 비밀번호 저장 시
password = "MySecretP@ss123"
hashed = hash_password(password)
print(f"저장할 해시: {hashed.decode()}")
# 로그인 시 검증
is_valid = verify_password("MySecretP@ss123", hashed)
print(f"비밀번호 일치: {is_valid}") # True
is_valid = verify_password("wrong_password", hashed)
print(f"비밀번호 일치: {is_valid}") # False
# === 3. 토큰 생성 (secrets 모듈) ===
# API 키, 세션 토큰 등 암호학적으로 안전한 랜덤 값
api_key = secrets.token_urlsafe(32) # URL-safe 32바이트
session_token = secrets.token_hex(16) # 16바이트 hex
print(f"API Key: {api_key}")
print(f"Session: {session_token}")
"저장 데이터는 AES-256으로 암호화하고, 전송 중에는 TLS 1.3으로 보호합니다. 암호화 키는 AWS KMS에서 관리하고, 키 로테이션 주기는 90일로 설정할게요. 비밀번호는 bcrypt로 해싱해서 저장합니다."
"개인정보는 컬럼 레벨 암호화를 적용했습니다. 주민번호, 카드번호는 AES-256-GCM으로 암호화하고, 검색이 필요한 필드는 결정론적 암호화를 사용해서 인덱싱이 가능합니다. 해시와 암호화를 구분해서 사용하고 있습니다."
"대칭키와 비대칭키의 차이는 키 사용 방식입니다. 대칭키는 같은 키로 암복호화하므로 빠르지만 키 교환 문제가 있고, 비대칭키는 공개키/개인키 쌍을 사용해서 키 교환 없이 안전하지만 느립니다. TLS는 둘을 하이브리드로 조합합니다."
MD5와 SHA-1은 충돌 공격에 취약합니다. 무결성 검증에도 SHA-256 이상을 사용하세요. 비밀번호 해싱에는 bcrypt, Argon2를 사용하세요.
암호화 알고리즘을 직접 구현하지 마세요. 검증된 라이브러리(cryptography, OpenSSL)만 사용하세요. "자체 암호화"는 거의 항상 취약합니다.
암호화 키를 코드에 직접 작성하면 Git에 노출됩니다. 환경변수, KMS, Vault 등 안전한 키 관리 시스템을 사용하세요.
AES-256-GCM(대칭키), RSA-2048+(비대칭키), bcrypt/Argon2(비밀번호)를 사용하세요. 키 로테이션 정책을 수립하고, 암호문과 키를 분리 보관하세요.