TL;DR

  • AES-GCM์€ Fernet๋ณด๋‹ค ์œ ์—ฐํ•œ ๋Œ€์นญ ํ‚ค ์•”ํ˜ธํ™”, RSA๋Š” ๊ณต๊ฐœ ํ‚ค/๊ฐœ์ธ ํ‚ค ์Œ์˜ ๋น„๋Œ€์นญ ์•”ํ˜ธํ™”
  • ์‹ค๋ฌด์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋Š” AES๋กœ, AES ํ‚ค๋Š” RSA๋กœ ์•”ํ˜ธํ™”ํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๋ฐฉ์‹ ์‚ฌ์šฉ
  • ํ•ด์‹ฑ์€ ๋‹จ๋ฐฉํ–ฅ ๋ณ€ํ™˜์œผ๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅ ๋“ฑ์— ์‚ฌ์šฉ, bcrypt/argon2 ๊ถŒ์žฅ

AI-assisted

์ด์ „ ๊ธ€

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.InvalidTag
Fernet vs AES-GCM ๋น„๊ต
ํ•ญ๋ชฉFernetAES-GCM
๋‚œ์ด๋„์‰ฌ์›€์ค‘๊ฐ„
ํ‚ค ๊ธธ์ด128๋น„ํŠธ ๊ณ ์ •128/192/256๋น„ํŠธ ์„ ํƒ
๋ชจ๋“œCBC + HMACGCM (์ธ์ฆ ๋‚ด์žฅ)
nonce/IV ๊ด€๋ฆฌ์ž๋™์ง์ ‘ ๊ด€๋ฆฌ
๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ๋ถ€์ ํ•ฉ์ ํ•ฉ
์šฉ๋„๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ

2. ๋น„๋Œ€์นญ ํ‚ค ์•”ํ˜ธํ™” (RSA)

๋Œ€์นญ ํ‚ค๊ฐ€ ๊ฐ™์€ ์—ด์‡ ๋กœ ์ž ๊ทธ๊ณ  ์—ฌ๋Š” ๊ธˆ๊ณ ์˜€๋‹ค๋ฉด, RSA๋Š” ์šฐ์ฒดํ†ต๊ณผ ๊ฐ™๋‹ค. ๋ˆ„๊ตฌ๋‚˜ ์šฐํŽธ๋ฌผ์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์ง€๋งŒ(๊ณต๊ฐœ ํ‚ค๋กœ ์•”ํ˜ธํ™”), ์—ด์–ด์„œ ๊บผ๋‚ด๋Š” ๊ฒƒ์€ ์—ด์‡ ๋ฅผ ๊ฐ€์ง„ ์ฃผ์ธ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค(๊ฐœ์ธ ํ‚ค๋กœ ๋ณตํ˜ธํ™”).

  • ๊ณต๊ฐœ ํ‚ค : ๋ˆ„๊ตฌ์—๊ฒŒ๋‚˜ ๊ณต๊ฐœ. ์•”ํ˜ธํ™”์— ์‚ฌ์šฉ
  • ๊ฐœ์ธ ํ‚ค : ๋ณธ์ธ๋งŒ ๋ณด์œ . ๋ณตํ˜ธํ™”์— ์‚ฌ์šฉ
RSA ํ‚ค์˜ ์ˆ˜ํ•™์  ์›๋ฆฌ

RSA ํ‚ค๋Š” ๋‹ค์Œ ๊ฐ’๋“ค๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. ๊ฐ ๊ฐ’์ด ์–ด๋–ป๊ฒŒ ๊ฒฐ์ •๋˜๋Š”์ง€ ์ž‘์€ ์ˆซ์ž ์˜ˆ์‹œ๋กœ ์‚ดํŽด๋ณด์ž.

p, q  โ†’ ๊ฐœ์ธํ‚ค๋ฅผ ๋งŒ๋“ค ์‚ฌ๋žŒ์ด ๋žœ๋คํ•˜๊ฒŒ ๊ณ ๋ฅด๋Š” ํฐ ์†Œ์ˆ˜
n     โ†’ p ร— q ๋กœ ๊ณ„์‚ฐ๋จ
ฯ†(n)  โ†’ (p - 1) ร— (q - 1) ๋กœ ๊ณ„์‚ฐ๋จ
e     โ†’ ฯ†(n)๊ณผ ์„œ๋กœ์†Œ์ธ ๊ณต๊ฐœ ์ง€์ˆ˜. ๋ณดํ†ต 65537์„ ์‚ฌ์šฉ
d     โ†’ e ร— d โ‰ก 1 mod ฯ†(n) ์„ ๋งŒ์กฑํ•˜๋Š” ๋ชจ๋“ˆ๋Ÿฌ ์—ญ์›

p = 61, q = 53์œผ๋กœ ์˜ˆ์‹œ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

n = 61 ร— 53 = 3233
ฯ†(n) = 60 ร— 52 = 3120
 
e = 17 ์„ ํƒ (gcd(17, 3120) = 1 ์ด๋ฏ€๋กœ ๊ฐ€๋Šฅ)
 
d = ?
  17 ร— d โ‰ก 1 mod 3120 ์„ ๋งŒ์กฑํ•˜๋Š” ๊ฐ’
  17 ร— 2753 = 46801 = 1 + 15 ร— 3120
  โ†’ d = 2753
 
๊ณต๊ฐœํ‚ค = (n=3233, e=17)
๊ฐœ์ธํ‚ค = (n=3233, d=2753)
๊ฐ’์˜๋ฏธ๊ณต๊ฐœ ์—ฌ๋ถ€๊ฒฐ์ • ๋ฐฉ์‹
p, qํฐ ์†Œ์ˆ˜๋น„๋ฐ€๋žœ๋ค ์ƒ์„ฑ
np ร— q๊ณต๊ฐœ๊ณ„์‚ฐ
ฯ†(n)(p-1)(q-1)๋น„๋ฐ€๊ณ„์‚ฐ
e๊ณต๊ฐœ ์ง€์ˆ˜๊ณต๊ฐœ์กฐ๊ฑด ๋งŒ์กฑํ•˜๋Š” ๊ฐ’ ์„ ํƒ
d๊ฐœ์ธ ์ง€์ˆ˜๋น„๋ฐ€e์˜ ๋ชจ๋“ˆ๋Ÿฌ ์—ญ์›์œผ๋กœ ๊ณ„์‚ฐ

์•”ํ˜ธํ™”์™€ ๋ณตํ˜ธํ™”๋Š” ์ด ๊ด€๊ณ„๋ฅผ ์ด์šฉํ•œ๋‹ค.

์•”ํ˜ธํ™”: C = M^e mod n
๋ณตํ˜ธํ™”: M = C^d mod n

e ร— d โ‰ก 1 mod ฯ†(n) ์ด๋ผ๋Š” ๊ด€๊ณ„ ๋•๋ถ„์— M^(eร—d) mod n = M์ด ์„ฑ๋ฆฝํ•˜๊ณ , ์•”ํ˜ธํ™”ํ•œ ๊ฒƒ์„ ๋ณตํ˜ธํ™”ํ•˜๋ฉด ์›๋ณธ์ด ๋ณต์›๋œ๋‹ค.

Python์œผ๋กœ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

import math
 
p, q = 61, 53
n = p * q
phi = (p - 1) * (q - 1)
e = 17
 
print(f"gcd(e, phi): {math.gcd(e, phi)}")  # 1
 
# Python 3.8+์—์„œ ๋ชจ๋“ˆ๋Ÿฌ ์—ญ์› ๊ณ„์‚ฐ
d = pow(e, -1, phi)
print(f"d: {d}")             # 2753
print(f"e*d mod phi: {(e * d) % phi}")  # 1
 
# ์•”ํ˜ธํ™” / ๋ณตํ˜ธํ™”
message = 65
ciphertext = pow(message, e, n)    # 2790
decrypted = pow(ciphertext, d, n)  # 65
 
print(f"message: {message}, ciphertext: {ciphertext}, decrypted: {decrypted}")
๊ณต๊ฐœํ‚ค๋กœ ๋ณตํ˜ธํ™”ํ•  ์ˆ˜ ์—†๋Š” ์ด์œ 

๊ณต๊ฐœํ‚ค์—๋Š” (n, e)๋งŒ ์žˆ๊ณ  d๋Š” ์—†๋‹ค. d๋ฅผ ์•Œ์•„๋‚ด๋ ค๋ฉด ฯ†(n)์ด ํ•„์š”ํ•˜๊ณ , ฯ†(n)์„ ๊ณ„์‚ฐํ•˜๋ ค๋ฉด n์„ ์†Œ์ธ์ˆ˜๋ถ„ํ•ดํ•ด์„œ p, q๋ฅผ ์•Œ์•„์•ผ ํ•œ๋‹ค.

n = 3233 โ†’ 61 ร— 53 (์ž‘์€ ์ˆซ์ž๋Š” ์‰ฝ๊ฒŒ ๋ถ„ํ•ด๋จ)

์‹ค์ œ RSA-2048์—์„œ๋Š” n์ด 617์ž๋ฆฌ ์ •๋„์˜ ์ˆซ์ž๋‹ค. ์ด ํฌ๊ธฐ์˜ ์†Œ์ธ์ˆ˜๋ถ„ํ•ด๋Š” ํ˜„์žฌ ์ปดํ“จํŒ… ๋Šฅ๋ ฅ์œผ๋กœ ํ˜„์‹ค์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋ž˜์„œ ๊ณต๊ฐœํ‚ค๋งŒ์œผ๋กœ๋Š” ๊ฐœ์ธํ‚ค๋ฅผ ์•Œ์•„๋‚ผ ์ˆ˜ ์—†๋‹ค.

ํ‚ค ์Œ ์ƒ์„ฑ
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.InvalidSignature

3. ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™”

์™œ RSA๋กœ ๋ฐ์ดํ„ฐ ์ „์ฒด๋ฅผ ์•”ํ˜ธํ™”ํ•˜์ง€ ์•Š๋Š”๊ฐ€

RSA๋Š” ๋‘ ๊ฐ€์ง€ ๋‹จ์ ์ด ์žˆ๋‹ค.

  • ๋А๋ฆฌ๋‹ค : ๋Œ€์นญ ํ‚ค ์•”ํ˜ธํ™”๋ณด๋‹ค ์ˆ˜๋ฐฑ ๋ฐฐ ๋А๋ฆฌ๋‹ค
  • ํฌ๊ธฐ ์ œํ•œ์ด ์žˆ๋‹ค : RSA-2048 + OAEP ๊ธฐ์ค€ ํ•œ ๋ฒˆ์— ์•ฝ 190๋ฐ”์ดํŠธ๊นŒ์ง€๋งŒ ์•”ํ˜ธํ™” ๊ฐ€๋Šฅํ•˜๋‹ค

์‹ค๋ฌด์—์„œ ๋‹ค๋ฃจ๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ณดํ†ต ์ด๋ณด๋‹ค ํ›จ์”ฌ ํฌ๋‹ค. ๋ฐ˜๋ฉด AES ๋Œ€์นญ ํ‚ค๋Š” 16~32๋ฐ”์ดํŠธ๋กœ, RSA๋กœ ์•”ํ˜ธํ™”ํ•˜๊ธฐ์— ์ถฉ๋ถ„ํžˆ ์ž‘๋‹ค. ๊ทธ๋ž˜์„œ ์‹ค๋ฌด์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์—ญํ• ์„ ๋‚˜๋ˆˆ๋‹ค.

ํฐ ๋ฐ์ดํ„ฐ   โ†’ AES ๊ฐ™์€ ๋Œ€์นญํ‚ค๋กœ ์•”ํ˜ธํ™” (๋น ๋ฆ„)
AES ํ‚ค ์ž์ฒด โ†’ RSA ๊ฐ™์€ ๋น„๋Œ€์นญํ‚ค๋กœ ์•”ํ˜ธํ™” (ํ‚ค ์ „๋‹ฌ ๋ฌธ์ œ ํ•ด๊ฒฐ)

์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋Œ€์นญ ํ‚ค๋ฅผ **์„ธ์…˜ ํ‚ค(session key)**๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™”์˜ ์ „์ฒด ํ๋ฆ„

Alice๊ฐ€ Bob์—๊ฒŒ ํŒŒ์ผ์„ ๋ณด๋‚ด๋Š” ์ƒํ™ฉ์„ ์˜ˆ์‹œ๋กœ ๋ณด์ž.

1. Bob์ด ๊ณต๊ฐœํ‚ค/๊ฐœ์ธํ‚ค ์Œ์„ ๋งŒ๋“ฆ
2. Bob์ด ๊ณต๊ฐœํ‚ค๋ฅผ Alice์—๊ฒŒ ์คŒ
3. Alice๊ฐ€ ๋žœ๋คํ•œ AES ์„ธ์…˜ ํ‚ค๋ฅผ ์ƒ์„ฑ
4. Alice๊ฐ€ ํŒŒ์ผ์„ AES ์„ธ์…˜ ํ‚ค๋กœ ์•”ํ˜ธํ™”
5. Alice๊ฐ€ AES ์„ธ์…˜ ํ‚ค๋ฅผ Bob์˜ ๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™”
6. Alice๊ฐ€ [์•”ํ˜ธํ™”๋œ ํŒŒ์ผ + ์•”ํ˜ธํ™”๋œ ์„ธ์…˜ ํ‚ค]๋ฅผ Bob์—๊ฒŒ ์ „์†ก
7. Bob์ด ์ž์‹ ์˜ ๊ฐœ์ธํ‚ค๋กœ AES ์„ธ์…˜ ํ‚ค๋ฅผ ๋ณตํ˜ธํ™”
8. Bob์ด AES ์„ธ์…˜ ํ‚ค๋กœ ํŒŒ์ผ์„ ๋ณตํ˜ธํ™”

์ค‘๊ฐ„์— ๊ณต๊ฒฉ์ž Eve๊ฐ€ ์ „์†ก ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๋กœ์ฑ„๋„ Bob์˜ ๊ฐœ์ธํ‚ค๊ฐ€ ์—†์œผ๋ฉด AES ์„ธ์…˜ ํ‚ค๋ฅผ ๋ณตํ˜ธํ™”ํ•  ์ˆ˜ ์—†๋‹ค. AES ํ‚ค๋ฅผ ๋ชจ๋ฅด๋ฉด ํŒŒ์ผ๋„ ๋ณตํ˜ธํ™”ํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์•ˆ์ „ํ•˜๋‹ค.

๊ณต๊ฐœํ‚ค์˜ ์ง„์œ„ ํ™•์ธ

Alice๊ฐ€ ๋ฐ›์€ ๊ณต๊ฐœํ‚ค๊ฐ€ ์ง„์งœ Bob์˜ ๊ฒƒ์ธ์ง€ ์–ด๋–ป๊ฒŒ ์•Œ๊นŒ? ๊ณต๊ฒฉ์ž Eve๊ฐ€ ์ž์‹ ์˜ ๊ณต๊ฐœํ‚ค๋ฅผ Bob์˜ ๊ฒƒ์ด๋ผ๊ณ  ์†์ด๋ฉด Eve๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ธ์ฆ์„œ์™€ CA๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. HTTPS๊ฐ€ ๋ฐ”๋กœ ์ด ๊ตฌ์กฐ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ 17. ์•ˆ์ „์„ฑ์„ ์œ„ํ•œ ๊ธฐ์ˆ  ์ฐธ๊ณ .

Python ๊ตฌํ˜„
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
import os
import base64
import json
 
 
def b64encode(data: bytes) -> str:
    return base64.b64encode(data).decode("utf-8")
 
 
def b64decode(data: str) -> bytes:
    return base64.b64decode(data.encode("utf-8"))
 
 
# 1. Bob: RSA ๊ณต๊ฐœํ‚ค/๊ฐœ์ธํ‚ค ์Œ ์ƒ์„ฑ
bob_private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)
bob_public_key = bob_private_key.public_key()
 
 
# 2. Alice: ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ ์ค€๋น„
plaintext = """
์ด๊ฒƒ์€ Bob์—๊ฒŒ ๋ณด๋‚ด๋Š” ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.
์‹ค์ œ๋กœ๋Š” PDF ํŒŒ์ผ, ์ด๋ฏธ์ง€, JSON, DB ๋ฐฑ์—… ํŒŒ์ผ ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
""".encode("utf-8")
 
 
# 3. Alice: ์ž„์‹œ AES ์„ธ์…˜ ํ‚ค ์ƒ์„ฑ
aes_key = AESGCM.generate_key(bit_length=256)
nonce = os.urandom(12)
 
 
# 4. Alice: ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” AES-GCM์œผ๋กœ ์•”ํ˜ธํ™”
aesgcm = AESGCM(aes_key)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=None)
 
 
# 5. Alice: AES ํ‚ค๋ฅผ Bob์˜ RSA ๊ณต๊ฐœํ‚ค๋กœ ์•”ํ˜ธํ™”
encrypted_aes_key = bob_public_key.encrypt(
    aes_key,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None,
    ),
)
 
 
# 6. Alice โ†’ Bob: ์ „์†กํ•  ํŒจํ‚ค์ง€ ๊ตฌ์„ฑ
package = {
    "algorithm": "RSA-OAEP-SHA256 + AES-256-GCM",
    "encrypted_aes_key": b64encode(encrypted_aes_key),
    "nonce": b64encode(nonce),
    "ciphertext": b64encode(ciphertext),
}
serialized_package = json.dumps(package, ensure_ascii=False, indent=2)
print("Alice๊ฐ€ Bob์—๊ฒŒ ๋ณด๋‚ด๋Š” ํŒจํ‚ค์ง€:")
print(serialized_package)
 
 
# 7. Bob: ํŒจํ‚ค์ง€ ์ˆ˜์‹  ํ›„ RSA ๊ฐœ์ธํ‚ค๋กœ AES ํ‚ค ๋ณตํ˜ธํ™”
received = json.loads(serialized_package)
decrypted_aes_key = bob_private_key.decrypt(
    b64decode(received["encrypted_aes_key"]),
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None,
    ),
)
 
 
# 8. Bob: ๋ณตํ˜ธํ™”ํ•œ AES ํ‚ค๋กœ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ณตํ˜ธํ™”
bob_aesgcm = AESGCM(decrypted_aes_key)
decrypted_plaintext = bob_aesgcm.decrypt(
    b64decode(received["nonce"]),
    b64decode(received["ciphertext"]),
    associated_data=None,
)
 
print("\nBob์ด ๋ณตํ˜ธํ™”ํ•œ ์›๋ฌธ:")
print(decrypted_plaintext.decode("utf-8"))

์ด ์ฝ”๋“œ์—์„œ ์—ญํ• ์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์—ญํ• ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์œ 
์‹ค์ œ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”AES-GCM (๋Œ€์นญ ํ‚ค)๋น ๋ฅด๊ณ  ๋Œ€์šฉ๋Ÿ‰ ๊ฐ€๋Šฅ
AES ํ‚ค ์ „๋‹ฌRSA-OAEP (๋น„๋Œ€์นญ ํ‚ค)์‚ฌ์ „์— ๋น„๋ฐ€ ํ‚ค๋ฅผ ๊ณต์œ ํ•˜์ง€ ์•Š์•„๋„ ๋จ

4. ํ•ด์‹ฑ

ํ•ด์‹ฑ์€ ์•”ํ˜ธํ™”์™€ ๋‹ค๋ฅด๋‹ค. ๋‹จ๋ฐฉํ–ฅ ๋ณ€ํ™˜์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด์‹œ ๊ฐ’์—์„œ ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์›ํ•  ์ˆ˜ ์—†๋‹ค.

๊ตฌ๋ถ„์•”ํ˜ธํ™”ํ•ด์‹ฑ
๋ฐฉํ–ฅ์–‘๋ฐฉํ–ฅ (์•”ํ˜ธํ™” โ†” ๋ณตํ˜ธํ™”)๋‹จ๋ฐฉํ–ฅ (์›๋ณธ โ†’ ํ•ด์‹œ)
ํ‚คํ•„์š”๋ถˆํ•„์š”
์šฉ๋„๋ฐ์ดํ„ฐ ๋ณดํ˜ธ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅ
๋ฐ์ดํ„ฐ ํ•ด์‹ฑ (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 bcrypt
import 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๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅ (์ตœ์‹  ๊ถŒ์žฅ)๋А๋ฆผ

5. ์‹ค์ „ ์„ ํƒ ๊ฐ€์ด๋“œ

์ƒํ™ฉ์— ๋”ฐ๋ผ ์–ด๋–ค ์•”ํ˜ธํ™” ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผ ํ•˜๋Š”์ง€ ์ •๋ฆฌํ•œ๋‹ค.

์ƒํ™ฉ์ถ”์ฒœ ๋ฐฉ์‹์ด์œ 
API ํ‚ค/ํ† ํฐ ์ €์žฅFernet๊ฐ„๋‹จํ•˜๊ณ  ์ถฉ๋ถ„ํžˆ ์•ˆ์ „
์„ค์ • ํŒŒ์ผ ์•”ํ˜ธํ™”Fernet๋‹จ์ผ ํ‚ค๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ด€๋ฆฌ
๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ์•”ํ˜ธํ™”AES-GCM์ŠคํŠธ๋ฆฌ๋ฐ ์ฒ˜๋ฆฌ, ์„ฑ๋Šฅ ์šฐ์ˆ˜
์„œ๋ฒ„ ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†กAES-GCM + RSA (ํ•˜์ด๋ธŒ๋ฆฌ๋“œ)ํ‚ค ๊ตํ™˜ ๋ฌธ์ œ ํ•ด๊ฒฐ
๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅbcrypt / argon2๋ณตํ˜ธํ™”๊ฐ€ ํ•„์š” ์—†๊ณ  ๋А๋ ค์•ผ ์•ˆ์ „
ํŒŒ์ผ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆSHA-256๋น ๋ฅด๊ณ  ์ถฉ๋Œ ์ €ํ•ญ์„ฑ ๋†’์Œ
๋ฐ์ดํ„ฐ ์„œ๋ช…/์ธ์ฆRSA ์„œ๋ช…๋ณ€์กฐ ๋ฐฉ์ง€ + ๋ฐœ์‹ ์ž ์ฆ๋ช…

ํŒ๋‹จ ๊ธฐ์ค€

  • ๋ณตํ˜ธํ™”๊ฐ€ ํ•„์š”ํ•œ๊ฐ€? โ†’ ํ•„์š” ์—†์œผ๋ฉด ํ•ด์‹ฑ
  • ํ‚ค๋ฅผ ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š”๊ฐ€? โ†’ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋ฉด RSA ๋˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ
  • ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๊ฐ€ ํฐ๊ฐ€? โ†’ ํฌ๋ฉด AES-GCM
  • ๊ฐ„๋‹จํ•˜๊ฒŒ ๋๋‚ด๊ณ  ์‹ถ์€๊ฐ€? โ†’ Fernet

6. ํ”ํ•œ ์‹ค์ˆ˜์™€ ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ

ํ‚ค๋ฅผ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉ
# ์ ˆ๋Œ€ ํ•˜๋ฉด ์•ˆ ๋˜๋Š” ํŒจํ„ด
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๋Š” ๋งค๋ฒˆ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ๋‹ค
  • ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•ด์‹ฑ, ๋ฐ์ดํ„ฐ๋Š” ์•”ํ˜ธํ™”๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค