Alert
์ด ๊ธ์ Claude Code์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค
TL;DR
- AES-GCM์ Fernet๋ณด๋ค ์ ์ฐํ ๋์นญ ํค ์ํธํ ๋ฐฉ์
- RSA๋ ๊ณต๊ฐ ํค/๊ฐ์ธ ํค ์์ ์ฌ์ฉํ๋ ๋น๋์นญ ์ํธํ
- ํด์ฑ์ ๋จ๋ฐฉํฅ ๋ณํ์ผ๋ก ๋น๋ฐ๋ฒํธ ์ ์ฅ ๋ฑ์ ์ฌ์ฉ
- ์ํฉ๋ณ๋ก ์ ์ ํ ์ํธํ ๋ฐฉ์์ ์ ํํ๋ ๊ฒ์ด ์ค์
์ด์ ๊ธ
Python cryptography 1 - Fernet์์ Fernet ๊ธฐ๋ฐ ๋์นญ ํค ์ํธํ๋ฅผ ๋ค๋ค๋ค. ์ด ๊ธ์์๋ Fernet์ผ๋ก ํด๊ฒฐํ๊ธฐ ์ด๋ ค์ด ์ํฉ์์ ์ฌ์ฉํ ์ ์๋ ์ ์์ค API๋ฅผ ๋ค๋ฃฌ๋ค.
1. AES ์ง์ ์ฌ์ฉํ๊ธฐ
Fernet์ ๋ด๋ถ์ ์ผ๋ก AES-128-CBC๋ฅผ ์ฌ์ฉํ์ง๋ง, ํค ๊ธธ์ด๋ ๋ชจ๋๋ฅผ ๋ฐ๊ฟ ์ ์๋ค. cryptography์ hazmat ๋ ์ด์ด๋ฅผ ์ฌ์ฉํ๋ฉด AES๋ฅผ ์ง์ ์ ์ดํ ์ ์๋ค.
AES-GCM์ด๋
AES-GCM(Galois/Counter Mode)์ ํ์ฌ ๊ฐ์ฅ ๋๋ฆฌ ๊ถ์ฅ๋๋ ๋์นญ ํค ์ํธํ ๋ชจ๋๋ค.
- ์ํธํ + ์ธ์ฆ์ ํ๋ฒ์ ์ฒ๋ฆฌํ๋ค (Authenticated Encryption)
- ๋ณ๋์ HMAC ๊ณ์ฐ์ด ํ์ ์๋ค
- AES-256 ๋ฑ ๋ ๊ธด ํค๋ฅผ ์ฌ์ฉํ ์ ์๋ค
- ์คํธ๋ฆฌ๋ฐ/์ฒญํฌ ๋จ์ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# 1. ํค ์์ฑ (256๋นํธ = 32๋ฐ์ดํธ)
key = AESGCM.generate_key(bit_length=256)
# 2. nonce ์์ฑ (12๋ฐ์ดํธ ๊ถ์ฅ)
nonce = os.urandom(12)
# 3. ์ํธํ
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, b"secret data", None)
# 4. ๋ณตํธํ
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
print(plaintext) # b'secret data'nonce๋ ์ ๋ ์ฌ์ฌ์ฉํ๋ฉด ์ ๋๋ค
๊ฐ์ ํค๋ก ๊ฐ์ nonce๋ฅผ ๋ ๋ฒ ์ฌ์ฉํ๋ฉด ์ํธํ๊ฐ ๊นจ์ง๋ค. ๋งค ์ํธํ๋ง๋ค
os.urandom(12)๋ก ์๋ก ์์ฑํ๊ณ , ciphertext์ ํจ๊ป ์ ์ฅํด์ผ ํ๋ค.
AAD (Associated Authenticated Data)
encrypt์ ์ธ ๋ฒ์งธ ์ธ์๋ AAD๋ค. ์ํธํํ์ง๋ ์์ง๋ง ๋ฌด๊ฒฐ์ฑ์ ๊ฒ์ฆํ๊ณ ์ถ์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๋๋ค.
# ํค๋ ์ ๋ณด๋ ์ํธํํ์ง ์๋, ๋ณ์กฐ ์ฌ๋ถ๋ ๊ฒ์ฆ
aad = b"user_id=42"
ciphertext = aesgcm.encrypt(nonce, b"secret data", aad)
# ๋ณตํธํ ์ ๊ฐ์ AAD๋ฅผ ๋๊ฒจ์ผ ์ฑ๊ณต
plaintext = aesgcm.decrypt(nonce, ciphertext, aad)
# AAD๊ฐ ๋ค๋ฅด๋ฉด InvalidTag ์์ธ
aesgcm.decrypt(nonce, ciphertext, b"user_id=99")
# cryptography.exceptions.InvalidTagFernet vs AES-GCM ๋น๊ต
| ํญ๋ชฉ | Fernet | AES-GCM |
|---|---|---|
| ๋์ด๋ | ์ฌ์ | ์ค๊ฐ |
| ํค ๊ธธ์ด | 128๋นํธ ๊ณ ์ | 128/192/256๋นํธ ์ ํ |
| ๋ชจ๋ | CBC + HMAC | GCM (์ธ์ฆ ๋ด์ฅ) |
| nonce/IV ๊ด๋ฆฌ | ์๋ | ์ง์ ๊ด๋ฆฌ |
| ๋์ฉ๋ ํ์ผ | ๋ถ์ ํฉ | ์ ํฉ |
| ์ฉ๋ | ๊ฐ๋จํ ๋ฐ์ดํฐ ์ํธํ | ์ธ๋ฐํ ์ ์ด๊ฐ ํ์ํ ๋ |
2. ๋น๋์นญ ํค ์ํธํ (RSA)
๋์นญ ํค๊ฐ ๊ฐ์ ์ด์ ๋ก ์ ๊ทธ๊ณ ์ฌ๋ ๊ธ๊ณ ์๋ค๋ฉด, RSA๋ ์ฐ์ฒดํต๊ณผ ๊ฐ๋ค. ๋๊ตฌ๋ ์ฐํธ๋ฌผ์ ๋ฃ์ ์ ์์ง๋ง(๊ณต๊ฐ ํค๋ก ์ํธํ), ์ด์ด์ ๊บผ๋ด๋ ๊ฒ์ ์ด์ ๋ฅผ ๊ฐ์ง ์ฃผ์ธ๋ง ๊ฐ๋ฅํ๋ค(๊ฐ์ธ ํค๋ก ๋ณตํธํ).
- ๊ณต๊ฐ ํค : ๋๊ตฌ์๊ฒ๋ ๊ณต๊ฐ. ์ํธํ์ ์ฌ์ฉ
- ๊ฐ์ธ ํค : ๋ณธ์ธ๋ง ๋ณด์ . ๋ณตํธํ์ ์ฌ์ฉ
ํค ์ ์์ฑ
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
# ๊ฐ์ธ ํค ์์ฑ
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# ๊ณต๊ฐ ํค ์ถ์ถ
public_key = private_key.public_key()ํค๋ฅผ ํ์ผ๋ก ์ ์ฅ/๋ถ๋ฌ์ค๊ธฐ
# ๊ฐ์ธ ํค ์ ์ฅ (PEM ํ์)
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"),
)
Path("private.pem").write_bytes(pem_private)
# ๊ณต๊ฐ ํค ์ ์ฅ
pem_public = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
Path("public.pem").write_bytes(pem_public)
# ๊ฐ์ธ ํค ๋ถ๋ฌ์ค๊ธฐ
loaded_private = serialization.load_pem_private_key(
Path("private.pem").read_bytes(),
password=b"passphrase",
)
# ๊ณต๊ฐ ํค ๋ถ๋ฌ์ค๊ธฐ
loaded_public = serialization.load_pem_public_key(
Path("public.pem").read_bytes(),
)์ํธํ / ๋ณตํธํ
# ๊ณต๊ฐ ํค๋ก ์ํธํ
ciphertext = public_key.encrypt(
b"secret message",
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
# ๊ฐ์ธ ํค๋ก ๋ณตํธํ
plaintext = private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
print(plaintext) # b'secret message'RSA๋ ์์ ๋ฐ์ดํฐ๋ง ์ํธํํ ์ ์๋ค
RSA-2048 ๊ธฐ์ค ์ต๋ ์ฝ 190๋ฐ์ดํธ๊น์ง๋ง ์ํธํ ๊ฐ๋ฅํ๋ค. ํฐ ๋ฐ์ดํฐ๋ AES๋ก ์ํธํํ๊ณ , AES ํค๋ง RSA๋ก ์ํธํํ๋ ํ์ด๋ธ๋ฆฌ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
๋์งํธ ์๋ช / ๊ฒ์ฆ
์ํธํ์ ๋ฐ๋ ๋ฐฉํฅ์ด๋ค. ๊ฐ์ธ ํค๋ก ์๋ช ํ๊ณ ๊ณต๊ฐ ํค๋ก ๊ฒ์ฆํ๋ค. ๋ฐ์ดํฐ๊ฐ ๋ณ์กฐ๋์ง ์์์์ ์ฆ๋ช ํ ๋ ์ฌ์ฉํ๋ค. ์ค์ ๋ก TLS handshake์์ ์๋ฒ ์ธ์ฆ์ ์ด ๋ฐฉ์์ด ์ฐ์ธ๋ค.
from cryptography.hazmat.primitives.asymmetric import utils
message = b"this data must not be tampered"
# ๊ฐ์ธ ํค๋ก ์๋ช
signature = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256(),
)
# ๊ณต๊ฐ ํค๋ก ๊ฒ์ฆ (๋ณ์กฐ๋์ง ์์์ผ๋ฉด ์ ์ ํต๊ณผ)
public_key.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256(),
)
# ์์ธ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด ๊ฒ์ฆ ์ฑ๊ณต
# ๋ฉ์์ง๊ฐ ๋ณ์กฐ๋ ๊ฒฝ์ฐ
public_key.verify(signature, b"tampered data", ...)
# cryptography.exceptions.InvalidSignature3. ํด์ฑ
ํด์ฑ์ ์ํธํ์ ๋ค๋ฅด๋ค. ๋จ๋ฐฉํฅ ๋ณํ์ด๊ธฐ ๋๋ฌธ์ ํด์ ๊ฐ์์ ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ํ ์ ์๋ค.
| ๊ตฌ๋ถ | ์ํธํ | ํด์ฑ |
|---|---|---|
| ๋ฐฉํฅ | ์๋ฐฉํฅ (์ํธํ โ ๋ณตํธํ) | ๋จ๋ฐฉํฅ (์๋ณธ โ ํด์) |
| ํค | ํ์ | ๋ถํ์ |
| ์ฉ๋ | ๋ฐ์ดํฐ ๋ณดํธ | ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ, ๋น๋ฐ๋ฒํธ ์ ์ฅ |
๋ฐ์ดํฐ ํด์ฑ (SHA-256)
ํ์ผ์ด๋ ๋ฉ์์ง์ ๋ฌด๊ฒฐ์ฑ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ๋ค.
from cryptography.hazmat.primitives import hashes
digest = hashes.Hash(hashes.SHA256())
digest.update(b"hello ")
digest.update(b"world")
result = digest.finalize()
print(result.hex())
# ์ ํด์ง ๊ธธ์ด์ ํด์ ๊ฐ ์ถ๋ ฅ (64์ hex)๋น๋ฐ๋ฒํธ ํด์ฑ๊ณผ ๋ฐ์ดํฐ ํด์ฑ์ ๋ค๋ฅด๋ค
SHA-256์ผ๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ฅํ๋ฉด ์ ๋๋ค
SHA-256์ ์๋๊ฐ ๋น ๋ฅด๊ธฐ ๋๋ฌธ์ ๋ฌด์ฐจ๋ณ ๋์ ๊ณต๊ฒฉ(brute-force)์ ์ทจ์ฝํ๋ค. ๋น๋ฐ๋ฒํธ ํด์ฑ์๋ ์๋์ ์ผ๋ก ๋๋ฆฐ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํด์ผ ํ๋ค.
๋น๋ฐ๋ฒํธ ํด์ฑ์๋ bcrypt๋ฅผ ์ฌ์ฉํ๋ค.
pip install bcryptimport bcrypt
password = b"my_secure_password"
# ํด์ฑ (salt ์๋ ์์ฑ)
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
print(hashed)
# b'$2b$12$...'
# ๊ฒ์ฆ
if bcrypt.checkpw(password, hashed):
print("๋น๋ฐ๋ฒํธ ์ผ์น")
else:
print("๋น๋ฐ๋ฒํธ ๋ถ์ผ์น")| ์๊ณ ๋ฆฌ์ฆ | ์ฉ๋ | ์๋ |
|---|---|---|
| SHA-256 | ํ์ผ ๋ฌด๊ฒฐ์ฑ, ๋ฐ์ดํฐ ์ง๋ฌธ | ๋น ๋ฆ |
| bcrypt | ๋น๋ฐ๋ฒํธ ์ ์ฅ | ์๋์ ์ผ๋ก ๋๋ฆผ |
| scrypt | ๋น๋ฐ๋ฒํธ ์ ์ฅ (๋ฉ๋ชจ๋ฆฌ ์ง์ฝ์ ) | ๋๋ฆผ |
| argon2 | ๋น๋ฐ๋ฒํธ ์ ์ฅ (์ต์ ๊ถ์ฅ) | ๋๋ฆผ |
4. ์ค์ ์ ํ ๊ฐ์ด๋
์ํฉ์ ๋ฐ๋ผ ์ด๋ค ์ํธํ ๋ฐฉ์์ ์ ํํด์ผ ํ๋์ง ์ ๋ฆฌํ๋ค.
| ์ํฉ | ์ถ์ฒ ๋ฐฉ์ | ์ด์ |
|---|---|---|
| API ํค/ํ ํฐ ์ ์ฅ | Fernet | ๊ฐ๋จํ๊ณ ์ถฉ๋ถํ ์์ |
| ์ค์ ํ์ผ ์ํธํ | Fernet | ๋จ์ผ ํค๋ก ๊ฐํธํ๊ฒ ๊ด๋ฆฌ |
| ๋์ฉ๋ ํ์ผ ์ํธํ | AES-GCM | ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ, ์ฑ๋ฅ ์ฐ์ |
| ์๋ฒ ๊ฐ ๋ฐ์ดํฐ ์ ์ก | AES-GCM + RSA (ํ์ด๋ธ๋ฆฌ๋) | ํค ๊ตํ ๋ฌธ์ ํด๊ฒฐ |
| ๋น๋ฐ๋ฒํธ ์ ์ฅ | bcrypt / argon2 | ๋ณตํธํ๊ฐ ํ์ ์๊ณ ๋๋ ค์ผ ์์ |
| ํ์ผ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ | SHA-256 | ๋น ๋ฅด๊ณ ์ถฉ๋ ์ ํญ์ฑ ๋์ |
| ๋ฐ์ดํฐ ์๋ช /์ธ์ฆ | RSA ์๋ช | ๋ณ์กฐ ๋ฐฉ์ง + ๋ฐ์ ์ ์ฆ๋ช |
ํ๋จ ๊ธฐ์ค
- ๋ณตํธํ๊ฐ ํ์ํ๊ฐ? โ ํ์ ์์ผ๋ฉด ํด์ฑ
- ํค๋ฅผ ์๋๋ฐฉ์๊ฒ ์ ๋ฌํด์ผ ํ๋๊ฐ? โ ์ ๋ฌํด์ผ ํ๋ฉด RSA ๋๋ ํ์ด๋ธ๋ฆฌ๋
- ๋ฐ์ดํฐ ํฌ๊ธฐ๊ฐ ํฐ๊ฐ? โ ํฌ๋ฉด AES-GCM
- ๊ฐ๋จํ๊ฒ ๋๋ด๊ณ ์ถ์๊ฐ? โ Fernet
5. ํํ ์ค์์ ๋ณด์ ์ฃผ์์ฌํญ
ํค๋ฅผ ์ฝ๋์ ํ๋์ฝ๋ฉ
# ์ ๋ ํ๋ฉด ์ ๋๋ ํจํด
key = b"ZmDfcTF7_60GrrY4vBGJSVgmYR0yGH8rrOamiLkI6mA="ํค๊ฐ git ํ์คํ ๋ฆฌ์ ๋จ์ผ๋ฉด ์ญ์ ํด๋ ์ด๋ฏธ ์ ์ถ๋ ๊ฒ์ด๋ค. ํ๊ฒฝ๋ณ์๋ ์ํฌ๋ฆฟ ๋งค๋์ ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
ECB ๋ชจ๋ ์ฌ์ฉ
ECB(Electronic Codebook)๋ ๊ฐ์ ํ๋ฌธ ๋ธ๋ก์ด ํญ์ ๊ฐ์ ์ํธ๋ฌธ์ ์์ฑํ๋ค. ํจํด์ด ๊ทธ๋๋ก ๋๋ฌ๋๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ๋ฉด ์ ๋๋ค. GCM์ด๋ CBC๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
nonce/IV ์ฌ์ฌ์ฉ
AES-GCM์์ ๊ฐ์ ํค๋ก ๊ฐ์ nonce๋ฅผ ๋ ๋ฒ ์ฌ์ฉํ๋ฉด ์ํธํ๊ฐ ์์ ํ ๊นจ์ง๋ค. ๋งค๋ฒ os.urandom()์ผ๋ก ์์ฑํด์ผ ํ๋ค.
์์ฒด ์ํธํ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ
๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. ์ง์ ๋ง๋ ์ํธํ๋ ๊ฑฐ์ ํ์คํ๊ฒ ์ทจ์ฝ์ ์ด ์๋ค.
SHA-256์ผ๋ก ๋น๋ฐ๋ฒํธ ํด์ฑ
์์์ ๋ค๋ค๋ฏ์ด SHA-256์ ๋น ๋ฅด๊ธฐ ๋๋ฌธ์ ๋น๋ฐ๋ฒํธ ํด์ฑ์ ๋ถ์ ํฉํ๋ค. bcrypt, scrypt, argon2๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
ํต์ฌ ์์น
- ์ง์ ๋ง๋ค์ง ๋ง๊ณ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค
- ํค๋ ์ฝ๋์ ๋ถ๋ฆฌํ๋ค
- nonce/IV๋ ๋งค๋ฒ ์๋ก ์์ฑํ๋ค
- ๋น๋ฐ๋ฒํธ๋ ํด์ฑ, ๋ฐ์ดํฐ๋ ์ํธํ๋ก ๊ตฌ๋ถํ๋ค