Cryptography ๊ธฐ์ดˆ

Cryptography ๊ธฐ์ดˆ

์ด์ „: 01. Security ๊ธฐ์ดˆ | ๋‹ค์Œ: 03. Hashing๊ณผ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ


์•”ํ˜ธํ•™์€ ์˜๋„๋œ ๋‹น์‚ฌ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ†ต์‹ ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ๊ด€ํ–‰์ž…๋‹ˆ๋‹ค. ์ด ๋ ˆ์Šจ์€ ํ˜„๋Œ€ ์•”ํ˜ธํ•™์˜ ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ๋ถ„์•ผ์ธ ๋Œ€์นญ ๋ฐ ๋น„๋Œ€์นญ ์•”ํ˜ธํ™”์™€ ํ‚ค ๊ตํ™˜ ํ”„๋กœํ† ์ฝœ, ๋””์ง€ํ„ธ ์„œ๋ช…, ๊ทธ๋ฆฌ๊ณ  ์‹ค์šฉ์ ์ธ Python ๊ตฌํ˜„์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์ด ๋ ˆ์Šจ์ด ๋๋‚˜๋ฉด ์ฃผ์–ด์ง„ ๋ฌธ์ œ์— ๋Œ€ํ•ด ์˜ฌ๋ฐ”๋ฅธ ์•”ํ˜ธํ™” ๊ธฐ๋ณธ ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ํ•จ์ •์„ ํ”ผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋‚œ์ด๋„: โญโญโญ

ํ•™์Šต ๋ชฉํ‘œ: - ๋Œ€์นญ ๋ฐ ๋น„๋Œ€์นญ ์•”ํ˜ธํ™”์˜ ์ฐจ์ด์  ์ดํ•ดํ•˜๊ธฐ - Python์—์„œ AES-GCM๊ณผ ChaCha20-Poly1305 ์•”ํ˜ธํ™” ๊ตฌํ˜„ํ•˜๊ธฐ - ๋น„๋Œ€์นญ ์•”ํ˜ธํ•™์„ ์œ„ํ•œ RSA, ECDSA, Ed25519 ์ดํ•ดํ•˜๊ธฐ - Diffie-Hellman๊ณผ ECDH ํ‚ค ๊ตํ™˜ ๊ตฌํ˜„ํ•˜๊ธฐ - ๋””์ง€ํ„ธ ์„œ๋ช… ์ƒ์„ฑ ๋ฐ ๊ฒ€์ฆํ•˜๊ธฐ - ์ผ๋ฐ˜์ ์ธ ์•”ํ˜ธํ•™์  ํ•จ์ • ์ธ์‹ํ•˜๊ณ  ํ”ผํ•˜๊ธฐ - ํ˜„๋Œ€ ์•”ํ˜ธํ•™ ๊ถŒ์žฅ ์‚ฌํ•ญ ์ ์šฉํ•˜๊ธฐ


๋ชฉ์ฐจ

  1. ์•”ํ˜ธํ•™ ๊ฐœ์š”
  2. ๋Œ€์นญ ์•”ํ˜ธํ™”
  3. ๋ธ”๋ก ์•”ํ˜ธ ์šด์˜ ๋ชจ๋“œ
  4. AES-GCM: ํ˜„๋Œ€ ํ‘œ์ค€
  5. ChaCha20-Poly1305
  6. ๋น„๋Œ€์นญ ์•”ํ˜ธํ™”
  7. RSA
  8. ํƒ€์› ๊ณก์„  ์•”ํ˜ธํ•™
  9. ํ‚ค ๊ตํ™˜
  10. ๋””์ง€ํ„ธ ์„œ๋ช…
  11. ์ผ๋ฐ˜์ ์ธ ํ•จ์ •๊ณผ ํ”ผํ•˜๋Š” ๋ฐฉ๋ฒ•
  12. ํ˜„๋Œ€ ๊ถŒ์žฅ ์‚ฌํ•ญ
  13. ์—ฐ์Šต ๋ฌธ์ œ
  14. ์ฐธ๊ณ  ์ž๋ฃŒ

1. ์•”ํ˜ธํ•™ ๊ฐœ์š”

1.1 ์ „์ฒด ๊ตฌ์กฐ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ํ˜„๋Œ€ ์•”ํ˜ธํ•™ ๋ถ„๋ฅ˜                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                      โ”‚
โ”‚  Cryptography                                                        โ”‚
โ”‚  โ”œโ”€โ”€ Symmetric (๊ณต์œ  ํ‚ค)                                             โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ Block Ciphers                                               โ”‚
โ”‚  โ”‚   โ”‚   โ”œโ”€โ”€ AES (128/192/256-bit key)                               โ”‚
โ”‚  โ”‚   โ”‚   โ”œโ”€โ”€ Modes: ECB, CBC, CTR, GCM, CCM                        โ”‚
โ”‚  โ”‚   โ”‚   โ””โ”€โ”€ Legacy: DES, 3DES, Blowfish (ํ”ผํ•  ๊ฒƒ)                  โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ Stream Ciphers                                              โ”‚
โ”‚  โ”‚       โ”œโ”€โ”€ ChaCha20(-Poly1305)                                    โ”‚
โ”‚  โ”‚       โ””โ”€โ”€ Legacy: RC4 (๊นจ์ง, ํ”ผํ•  ๊ฒƒ)                            โ”‚
โ”‚  โ”‚                                                                   โ”‚
โ”‚  โ”œโ”€โ”€ Asymmetric (๊ณต๊ฐœ/๊ฐœ์ธ ํ‚ค ์Œ)                                    โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ Encryption                                                  โ”‚
โ”‚  โ”‚   โ”‚   โ”œโ”€โ”€ RSA-OAEP                                               โ”‚
โ”‚  โ”‚   โ”‚   โ””โ”€โ”€ ECIES (Elliptic Curve Integrated Encryption)           โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ Digital Signatures                                          โ”‚
โ”‚  โ”‚   โ”‚   โ”œโ”€โ”€ RSA-PSS                                                โ”‚
โ”‚  โ”‚   โ”‚   โ”œโ”€โ”€ ECDSA (secp256r1, secp384r1)                          โ”‚
โ”‚  โ”‚   โ”‚   โ””โ”€โ”€ Ed25519 / Ed448                                       โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ Key Exchange                                                โ”‚
โ”‚  โ”‚       โ”œโ”€โ”€ Diffie-Hellman (DH)                                    โ”‚
โ”‚  โ”‚       โ”œโ”€โ”€ ECDH (X25519, P-256)                                   โ”‚
โ”‚  โ”‚       โ””โ”€โ”€ Post-quantum: ML-KEM (CRYSTALS-Kyber)                 โ”‚
โ”‚  โ”‚                                                                   โ”‚
โ”‚  โ”œโ”€โ”€ Hash Functions (๋ ˆ์Šจ 03์—์„œ ๋‹ค๋ฃธ)                               โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ SHA-2 (SHA-256, SHA-512)                                   โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ SHA-3 (Keccak)                                             โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ BLAKE2/BLAKE3                                              โ”‚
โ”‚  โ”‚                                                                   โ”‚
โ”‚  โ””โ”€โ”€ Key Derivation Functions                                        โ”‚
โ”‚      โ”œโ”€โ”€ HKDF                                                        โ”‚
โ”‚      โ”œโ”€โ”€ PBKDF2                                                      โ”‚
โ”‚      โ””โ”€โ”€ scrypt / Argon2 (๋น„๋ฐ€๋ฒˆํ˜ธ ๊ธฐ๋ฐ˜)                            โ”‚
โ”‚                                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

1.2 ํ•ต์‹ฌ ๊ฐœ๋…

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ํ•ต์‹ฌ ์•”ํ˜ธํ•™ ๊ฐœ๋…                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์šฉ์–ด               โ”‚ ์ •์˜                                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Plaintext          โ”‚ ์›๋ณธ, ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ                   โ”‚
โ”‚ Ciphertext         โ”‚ ์•”ํ˜ธํ™”๋œ (์ฝ์„ ์ˆ˜ ์—†๋Š”) ์ถœ๋ ฅ                   โ”‚
โ”‚ Key                โ”‚ ์•”ํ˜ธํ™”/๋ณตํ˜ธํ™”์— ์‚ฌ์šฉ๋˜๋Š” ๋น„๋ฐ€ ๊ฐ’               โ”‚
โ”‚ Nonce / IV         โ”‚ ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ์ˆซ์ž; ๋™์ผํ•œ ํ‰๋ฌธ์ด           โ”‚
โ”‚                    โ”‚ ๋™์ผํ•œ ์•”ํ˜ธ๋ฌธ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€             โ”‚
โ”‚ Authenticated enc. โ”‚ ๋ฌด๊ฒฐ์„ฑ๋„ ๊ฒ€์ฆํ•˜๋Š” ์•”ํ˜ธํ™” (AEAD)               โ”‚
โ”‚ Key derivation     โ”‚ ๋น„๋ฐ€๋ฒˆํ˜ธ๋‚˜ ๋‹ค๋ฅธ ํ‚ค๋กœ๋ถ€ํ„ฐ                       โ”‚
โ”‚                    โ”‚ ์•”ํ˜ธํ™” ํ‚ค๋ฅผ ์œ ๋„                               โ”‚
โ”‚ Forward secrecy    โ”‚ ์žฅ๊ธฐ ํ‚ค์˜ ์นจํ•ด๊ฐ€ ๊ณผ๊ฑฐ                          โ”‚
โ”‚                    โ”‚ ์„ธ์…˜ ํ‚ค๋ฅผ ์นจํ•ดํ•˜์ง€ ์•Š์Œ                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

1.3 Python Cryptography ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์ •

์ด ๋ ˆ์Šจ์˜ ๋ชจ๋“  ์ฝ”๋“œ ์˜ˆ์ œ๋Š” ๋†’์€ ์ˆ˜์ค€์˜ ๋ ˆ์‹œํ”ผ์™€ ๋‚ฎ์€ ์ˆ˜์ค€์˜ ๊ธฐ๋ณธ ์š”์†Œ๋ฅผ ์ œ๊ณตํ•˜๋Š” cryptography ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

pip install cryptography
# Verify installation
import cryptography
print(f"cryptography version: {cryptography.__version__}")

# The library has two main layers:
# 1. High-level (recipes): cryptography.fernet, cryptography.hazmat.primitives.kdf
# 2. Low-level (hazmat): cryptography.hazmat.primitives.ciphers, asymmetric, etc.
#
# "hazmat" stands for "hazardous materials" - these primitives can be
# misused. Always prefer high-level APIs when available.

2. ๋Œ€์นญ ์•”ํ˜ธํ™”

๋Œ€์นญ ์•”ํ˜ธํ™”๋Š” ์•”ํ˜ธํ™”์™€ ๋ณตํ˜ธํ™” ๋ชจ๋‘์— ๋™์ผํ•œ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋น ๋ฅด๊ณ  ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋ฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚Plaintext โ”‚โ”€โ”€Keyโ”€โ”€โ–ถโ”‚ Encrypt  โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚Ciphertextโ”‚
โ”‚ "Hello"  โ”‚         โ”‚ (AES)    โ”‚         โ”‚ 0xA3F1.. โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚Ciphertextโ”‚โ”€โ”€Keyโ”€โ”€โ–ถโ”‚ Decrypt  โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚Plaintext โ”‚
โ”‚ 0xA3F1.. โ”‚         โ”‚ (AES)    โ”‚         โ”‚ "Hello"  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

         ๋‘ ์ž‘์—… ๋ชจ๋‘ ๋™์ผํ•œ ํ‚ค ์‚ฌ์šฉ!

2.1 AES (Advanced Encryption Standard)

AES๋Š” ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋Œ€์นญ ์•”ํ˜ธ์ž…๋‹ˆ๋‹ค. 128๋น„ํŠธ ๋ธ”๋ก์—์„œ ์ž‘๋™ํ•˜๋ฉฐ 128, 192 ๋˜๋Š” 256๋น„ํŠธ์˜ ํ‚ค ํฌ๊ธฐ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         AES ํ‚ค ํฌ๊ธฐ                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํ‚ค ํฌ๊ธฐ  โ”‚ ๋ผ์šด๋“œ   โ”‚ ๋ณด์•ˆ     โ”‚ ์‚ฌ์šฉ ์‚ฌ๋ก€                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 128-bit  โ”‚ 10       โ”‚ ~128 bit โ”‚ ๋ฒ”์šฉ, ๋น ๋ฆ„                         โ”‚
โ”‚ 192-bit  โ”‚ 12       โ”‚ ~192 bit โ”‚ ์‹ค์ œ๋กœ ๊ฑฐ์˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ          โ”‚
โ”‚ 256-bit  โ”‚ 14       โ”‚ ~256 bit โ”‚ ์ •๋ถ€/๊ตฐ์‚ฌ, ์–‘์ž ์ดํ›„               โ”‚
โ”‚          โ”‚          โ”‚          โ”‚ ์ €ํ•ญ ๋งˆ์ง„                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

2.2 Fernet: ๋†’์€ ์ˆ˜์ค€์˜ ๋Œ€์นญ ์•”ํ˜ธํ™”

๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€์˜ ๊ฒฝ์šฐ Fernet ํด๋ž˜์Šค๋Š” ๊ฐ„๋‹จํ•œ API๋กœ ์ธ์ฆ๋œ ์•”ํ˜ธํ™”๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

from cryptography.fernet import Fernet
import base64

# Generate a random key (URL-safe base64 encoded)
key = Fernet.generate_key()
print(f"Key: {key.decode()}")
print(f"Key length: {len(base64.urlsafe_b64decode(key))} bytes = "
      f"{len(base64.urlsafe_b64decode(key)) * 8} bits")

# Create cipher
cipher = Fernet(key)

# Encrypt
plaintext = b"Sensitive financial data: account balance $50,000"
ciphertext = cipher.encrypt(plaintext)
print(f"\nPlaintext:  {plaintext.decode()}")
print(f"Ciphertext: {ciphertext[:60]}...")
print(f"Ciphertext length: {len(ciphertext)} bytes")

# Decrypt
decrypted = cipher.decrypt(ciphertext)
assert decrypted == plaintext
print(f"Decrypted:  {decrypted.decode()}")

# Fernet includes a timestamp - you can set a TTL (time-to-live)
import time
token = cipher.encrypt(b"temporary secret")
# time.sleep(2)  # Uncomment to test expiration
try:
    cipher.decrypt(token, ttl=60)  # Valid for 60 seconds
    print("\nToken is still valid")
except Exception as e:
    print(f"\nToken expired: {e}")

Fernet์ด ๋‚ด๋ถ€์ ์œผ๋กœ ํ•˜๋Š” ์ผ:

1. ๋žœ๋ค 128๋น„ํŠธ IV ์ƒ์„ฑ
2. AES-128-CBC๋กœ ์•”ํ˜ธํ™”
3. HMAC-SHA256์œผ๋กœ ์ธ์ฆ
4. ์—ฐ๊ฒฐ: version || timestamp || IV || ciphertext || HMAC
5. ๊ฒฐ๊ณผ๋ฅผ Base64 ์ธ์ฝ”๋”ฉ

3. ๋ธ”๋ก ์•”ํ˜ธ ์šด์˜ ๋ชจ๋“œ

๋ธ”๋ก ์•”ํ˜ธ(AES ๊ฐ™์€)๋Š” ๊ณ ์ • ํฌ๊ธฐ ๋ธ”๋ก์„ ์•”ํ˜ธํ™”ํ•ฉ๋‹ˆ๋‹ค. ์šด์˜ ๋ชจ๋“œ๋Š” ํ•œ ๋ธ”๋ก๋ณด๋‹ค ๊ธด ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

3.1 ECB ๋ชจ๋“œ (์ ˆ๋Œ€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”)

ECB(Electronic Codebook)๋Š” ๊ฐ ๋ธ”๋ก์„ ๋…๋ฆฝ์ ์œผ๋กœ ์•”ํ˜ธํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ํ‰๋ฌธ ๋ธ”๋ก์€ ๋™์ผํ•œ ์•”ํ˜ธ๋ฌธ ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜์—ฌ ํŒจํ„ด์„ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ECB ๋ชจ๋“œ - ์™œ ๊นจ์กŒ๋Š”๊ฐ€                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  ํ‰๋ฌธ ๋ธ”๋ก:       [AAAA] [BBBB] [AAAA] [CCCC] [AAAA]            โ”‚
โ”‚                      โ”‚      โ”‚      โ”‚      โ”‚      โ”‚              โ”‚
โ”‚                     AES    AES    AES    AES    AES             โ”‚
โ”‚                      โ”‚      โ”‚      โ”‚      โ”‚      โ”‚              โ”‚
โ”‚  ์•”ํ˜ธ๋ฌธ ๋ธ”๋ก:     [X1X1] [Y2Y2] [X1X1] [Z3Z3] [X1X1]           โ”‚
โ”‚                                                                  โ”‚
โ”‚  ๋ฌธ์ œ: ๋™์ผํ•œ ํ‰๋ฌธ ๋ธ”๋ก โ†’ ๋™์ผํ•œ ์•”ํ˜ธ๋ฌธ!                        โ”‚
โ”‚  ๊ณต๊ฒฉ์ž๋Š” ๋ณตํ˜ธํ™” ์—†์ด ํŒจํ„ด์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.                    โ”‚
โ”‚                                                                  โ”‚
โ”‚  ๊ณ ์ „์  ์˜ˆ: ECB ์•”ํ˜ธํ™”๋œ ๋น„ํŠธ๋งต ์ด๋ฏธ์ง€๋Š” ๋ชจ์–‘์„ ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค       โ”‚
โ”‚  ์ธ์ ‘ํ•œ ๋™์ผํ•œ ํ”ฝ์…€์ด ๋™์ผํ•œ ์•”ํ˜ธ๋ฌธ์„ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.       โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
# Demonstration: ECB leaks patterns
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import os

key = os.urandom(32)  # AES-256

# ECB mode (DO NOT USE in production - for demonstration only)
def ecb_encrypt_block(key: bytes, plaintext: bytes) -> bytes:
    """Encrypt a single block with AES-ECB. For demonstration only!"""
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    encryptor = cipher.encryptor()
    return encryptor.update(plaintext) + encryptor.finalize()

# Same plaintext block produces same ciphertext
block1 = b"AAAAAAAAAAAAAAAA"  # 16 bytes = 1 AES block
block2 = b"BBBBBBBBBBBBBBBB"

ct1a = ecb_encrypt_block(key, block1)
ct1b = ecb_encrypt_block(key, block1)  # Same input
ct2  = ecb_encrypt_block(key, block2)

print("ECB Pattern Leak Demonstration:")
print(f"  Block 'AAA...' โ†’ {ct1a.hex()[:32]}...")
print(f"  Block 'AAA...' โ†’ {ct1b.hex()[:32]}... (SAME ciphertext!)")
print(f"  Block 'BBB...' โ†’ {ct2.hex()[:32]}... (different)")
print(f"  ct1a == ct1b: {ct1a == ct1b}")  # True - this is the problem

3.2 CBC ๋ชจ๋“œ (๋ ˆ๊ฑฐ์‹œ, ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉ)

CBC(Cipher Block Chaining)๋Š” ์•”ํ˜ธํ™”ํ•˜๊ธฐ ์ „์— ๊ฐ ํ‰๋ฌธ ๋ธ”๋ก์„ ์ด์ „ ์•”ํ˜ธ๋ฌธ ๋ธ”๋ก๊ณผ XORํ•ฉ๋‹ˆ๋‹ค. ๋žœ๋ค IV๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        CBC ๋ชจ๋“œ                                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚     IV โ”€โ”€โ”                                                      โ”‚
โ”‚          โ–ผ                                                      โ”‚
โ”‚  P1 โ”€โ”€โ–ถ XOR โ”€โ”€โ–ถ AES โ”€โ”€โ–ถ C1                                    โ”‚
โ”‚                          โ”‚                                      โ”‚
โ”‚                          โ–ผ                                      โ”‚
โ”‚  P2 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ XOR โ”€โ”€โ–ถ AES โ”€โ”€โ–ถ C2                            โ”‚
โ”‚                                  โ”‚                              โ”‚
โ”‚                                  โ–ผ                              โ”‚
โ”‚  P3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ XOR โ”€โ”€โ–ถ AES โ”€โ”€โ–ถ C3                    โ”‚
โ”‚                                                                  โ”‚
โ”‚  ๊ฐ ์•”ํ˜ธ๋ฌธ ๋ธ”๋ก์€ ๋ชจ๋“  ์ด์ „ ๋ธ”๋ก์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.                   โ”‚
โ”‚  ๋žœ๋ค IV๋Š” ๋™์ผํ•œ ํ‰๋ฌธ์ด ๋‹ค๋ฅด๊ฒŒ ์•”ํ˜ธํ™”๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.         โ”‚
โ”‚                                                                  โ”‚
โ”‚  โš  ํŒจ๋”ฉ ํ•„์š” (์˜ˆ: PKCS#7)                                      โ”‚
โ”‚  โš  ์ธ์ฆ๋˜์ง€ ์•Š์œผ๋ฉด ํŒจ๋”ฉ ์˜ค๋ผํด ๊ณต๊ฒฉ์— ์ทจ์•ฝ                      โ”‚
โ”‚  โš  ์•”ํ˜ธํ™”๋ฅผ ๋ณ‘๋ ฌํ™”ํ•  ์ˆ˜ ์—†์Œ                                    โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import os

def aes_cbc_encrypt(key: bytes, plaintext: bytes) -> tuple:
    """AES-CBC encryption with PKCS7 padding. Returns (iv, ciphertext)."""
    iv = os.urandom(16)  # Random 128-bit IV

    # Pad plaintext to block size
    padder = padding.PKCS7(128).padder()
    padded = padder.update(plaintext) + padder.finalize()

    # Encrypt
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(padded) + encryptor.finalize()

    return iv, ciphertext

def aes_cbc_decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes:
    """AES-CBC decryption with PKCS7 unpadding."""
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    padded = decryptor.update(ciphertext) + decryptor.finalize()

    # Remove padding
    unpadder = padding.PKCS7(128).unpadder()
    plaintext = unpadder.update(padded) + unpadder.finalize()

    return plaintext

# Usage
key = os.urandom(32)  # AES-256
message = b"CBC mode requires padding and a random IV for each message"

iv, ct = aes_cbc_encrypt(key, message)
print(f"IV:         {iv.hex()}")
print(f"Ciphertext: {ct.hex()[:64]}...")

pt = aes_cbc_decrypt(key, iv, ct)
print(f"Decrypted:  {pt.decode()}")

# Same plaintext, different IV โ†’ different ciphertext
iv2, ct2 = aes_cbc_encrypt(key, message)
print(f"\nSame message, new IV:")
print(f"  ct1: {ct.hex()[:32]}...")
print(f"  ct2: {ct2.hex()[:32]}...")
print(f"  Same? {ct == ct2}")  # False - good!

3.3 CTR ๋ชจ๋“œ

CTR(Counter) ๋ชจ๋“œ๋Š” ๋ธ”๋ก ์•”ํ˜ธ๋ฅผ ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ณ‘๋ ฌํ™” ๊ฐ€๋Šฅํ•˜๋ฉฐ ํŒจ๋”ฉ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        CTR ๋ชจ๋“œ                                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  Nonce|Counter=0 โ”€โ”€โ–ถ AES โ”€โ”€โ–ถ Keystream0 โ”€โ”€XORโ”€โ”€โ–ถ C0            โ”‚
โ”‚                                               โ–ฒ                  โ”‚
โ”‚                                               โ”‚                  โ”‚
โ”‚                                              P0                  โ”‚
โ”‚                                                                  โ”‚
โ”‚  Nonce|Counter=1 โ”€โ”€โ–ถ AES โ”€โ”€โ–ถ Keystream1 โ”€โ”€XORโ”€โ”€โ–ถ C1            โ”‚
โ”‚                                               โ–ฒ                  โ”‚
โ”‚                                               โ”‚                  โ”‚
โ”‚                                              P1                  โ”‚
โ”‚                                                                  โ”‚
โ”‚  โœ“ ๋ณ‘๋ ฌํ™” ๊ฐ€๋Šฅ (์•”ํ˜ธํ™” ๋ฐ ๋ณตํ˜ธํ™”)                               โ”‚
โ”‚  โœ“ ํŒจ๋”ฉ ํ•„์š” ์—†์Œ                                               โ”‚
โ”‚  โœ“ ๋ชจ๋“  ๋ธ”๋ก์œผ๋กœ ์‹œํฌ ๊ฐ€๋Šฅ                                      โ”‚
โ”‚  โš  Nonce ์žฌ์‚ฌ์šฉ์€ ์น˜๋ช…์  (๋‘ ํ‰๋ฌธ์˜ XOR ๋ˆ„์ถœ)                  โ”‚
โ”‚  โš  ์ธ์ฆ ์—†์Œ (๋Œ€์‹  GCM ์‚ฌ์šฉ)                                    โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

4. AES-GCM: ํ˜„๋Œ€ ํ‘œ์ค€

GCM(Galois/Counter Mode)์€ CTR ๋ชจ๋“œ ์•”ํ˜ธํ™”์™€ GMAC ์ธ์ฆ์„ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ผ ์ž‘์—…์œผ๋กœ ๊ธฐ๋ฐ€์„ฑ๊ณผ ๋ฌด๊ฒฐ์„ฑ์„ ๋ชจ๋‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ Authenticated Encryption with Associated Data(AEAD)๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        AES-GCM (AEAD)                                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                      โ”‚
โ”‚  ์ž…๋ ฅ:  Key, Nonce (96-bit), Plaintext, AAD (์„ ํƒ)                  โ”‚
โ”‚  ์ถœ๋ ฅ: Ciphertext, Authentication Tag (128-bit)                     โ”‚
โ”‚                                                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                โ”‚
โ”‚  โ”‚  Nonce   โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  AES-CTR    โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Ciphertext โ”‚                โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚  Encryption โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜                โ”‚
โ”‚                  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜            โ”‚                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                               โ”‚                       โ”‚
โ”‚  โ”‚  AAD    โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                    โ”‚                       โ”‚
โ”‚  โ”‚(header) โ”‚          โ–ผ                    โ–ผ                       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                  โ”‚
โ”‚                  โ”‚   GHASH     โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚   Auth   โ”‚                  โ”‚
โ”‚                  โ”‚  (GMAC)    โ”‚     โ”‚   Tag    โ”‚                  โ”‚
โ”‚                  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ”‚
โ”‚                                                                      โ”‚
โ”‚  AAD = Associated Authenticated Data                                โ”‚
โ”‚  - ์ธ์ฆ๋˜์ง€๋งŒ ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์Œ                                        โ”‚
โ”‚  - ์˜ˆ: ๋ฉ”์‹œ์ง€ ํ—ค๋”, ํŒจํ‚ท ์‹œํ€€์Šค ๋ฒˆํ˜ธ                                โ”‚
โ”‚  - AAD ๋ณ€์กฐ๋Š” ์ธ์ฆ ์‹คํŒจ๋ฅผ ์œ ๋ฐœ                                      โ”‚
โ”‚                                                                      โ”‚
โ”‚  โœ“ AEAD: ๊ธฐ๋ฐ€์„ฑ + ๋ฌด๊ฒฐ์„ฑ + ์ง„์ •์„ฑ                                  โ”‚
โ”‚  โœ“ ๋ณ‘๋ ฌํ™” ๊ฐ€๋Šฅ                                                      โ”‚
โ”‚  โœ“ ํ•˜๋“œ์›จ์–ด ๊ฐ€์† (AES-NI)                                          โ”‚
โ”‚  โš  Nonce๋Š” ํ‚ค๋‹น ๊ณ ์œ ํ•ด์•ผ ํ•จ (์ ˆ๋Œ€ ์žฌ์‚ฌ์šฉ ๊ธˆ์ง€!)                    โ”‚
โ”‚  โš  96๋น„ํŠธ nonce๋Š” ํ‚ค๋‹น ~2^32 ์•”ํ˜ธํ™”๋กœ ์ œํ•œ                        โ”‚
โ”‚                                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

4.1 AES-GCM ๊ตฌํ˜„

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def aes_gcm_encrypt(key: bytes, plaintext: bytes,
                     aad: bytes = None) -> tuple:
    """
    Encrypt with AES-GCM.
    Returns (nonce, ciphertext_with_tag).
    """
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)  # 96-bit nonce (NIST recommended)
    ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
    return nonce, ciphertext

def aes_gcm_decrypt(key: bytes, nonce: bytes, ciphertext: bytes,
                     aad: bytes = None) -> bytes:
    """
    Decrypt with AES-GCM.
    Raises InvalidTag if authentication fails.
    """
    aesgcm = AESGCM(key)
    return aesgcm.decrypt(nonce, ciphertext, aad)

# Generate a 256-bit key
key = AESGCM.generate_key(bit_length=256)
print(f"Key: {key.hex()} ({len(key) * 8} bits)")

# Encrypt a message
message = b"Top secret: The missile codes are 12345"
aad = b"message-id: 42, timestamp: 2026-01-15"  # Authenticated but not encrypted

nonce, ciphertext = aes_gcm_encrypt(key, message, aad)
print(f"\nNonce:      {nonce.hex()}")
print(f"Ciphertext: {ciphertext.hex()[:64]}...")
print(f"CT length:  {len(ciphertext)} bytes "
      f"(plaintext: {len(message)} + tag: 16)")

# Decrypt
plaintext = aes_gcm_decrypt(key, nonce, ciphertext, aad)
print(f"Decrypted:  {plaintext.decode()}")

# Tamper with ciphertext โ†’ authentication fails
tampered_ct = bytearray(ciphertext)
tampered_ct[0] ^= 0xFF  # Flip bits in first byte
try:
    aes_gcm_decrypt(key, nonce, bytes(tampered_ct), aad)
    print("ERROR: Should have failed!")
except Exception as e:
    print(f"\nTamper detected: {type(e).__name__}")

# Tamper with AAD โ†’ authentication fails
try:
    aes_gcm_decrypt(key, nonce, ciphertext, b"tampered AAD")
    print("ERROR: Should have failed!")
except Exception as e:
    print(f"AAD tamper detected: {type(e).__name__}")

4.2 AES-GCM์„ ์‚ฌ์šฉํ•œ ํŒŒ์ผ ์•”ํ˜ธํ™”

import os
import struct
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from pathlib import Path

class FileEncryptor:
    """Encrypt/decrypt files using AES-256-GCM."""

    CHUNK_SIZE = 64 * 1024  # 64 KB chunks
    NONCE_SIZE = 12
    TAG_SIZE = 16

    def __init__(self, key: bytes):
        if len(key) != 32:
            raise ValueError("Key must be 256 bits (32 bytes)")
        self.aesgcm = AESGCM(key)

    def encrypt_file(self, input_path: str, output_path: str) -> dict:
        """
        Encrypt a file chunk by chunk.
        Format: [nonce (12B)][chunk_count (4B)][encrypted_chunk1][encrypted_chunk2]...
        """
        input_file = Path(input_path)
        if not input_file.exists():
            raise FileNotFoundError(f"Input file not found: {input_path}")

        file_size = input_file.stat().st_size
        chunks_written = 0

        with open(input_path, 'rb') as fin, open(output_path, 'wb') as fout:
            # Write file nonce (used as base; each chunk gets nonce + counter)
            base_nonce = os.urandom(self.NONCE_SIZE)
            fout.write(base_nonce)

            # Placeholder for chunk count
            chunk_count_pos = fout.tell()
            fout.write(struct.pack('<I', 0))  # Will update later

            while True:
                chunk = fin.read(self.CHUNK_SIZE)
                if not chunk:
                    break

                # Derive unique nonce for this chunk
                chunk_nonce = self._derive_chunk_nonce(base_nonce, chunks_written)

                # AAD includes chunk index to prevent reordering
                aad = struct.pack('<I', chunks_written)

                encrypted = self.aesgcm.encrypt(chunk_nonce, chunk, aad)

                # Write: [length (4B)][encrypted_data]
                fout.write(struct.pack('<I', len(encrypted)))
                fout.write(encrypted)
                chunks_written += 1

            # Update chunk count
            fout.seek(chunk_count_pos)
            fout.write(struct.pack('<I', chunks_written))

        return {
            "input_size": file_size,
            "chunks": chunks_written,
            "output_size": Path(output_path).stat().st_size
        }

    def decrypt_file(self, input_path: str, output_path: str) -> dict:
        """Decrypt a file encrypted with encrypt_file."""
        with open(input_path, 'rb') as fin, open(output_path, 'wb') as fout:
            base_nonce = fin.read(self.NONCE_SIZE)
            chunk_count = struct.unpack('<I', fin.read(4))[0]

            for i in range(chunk_count):
                chunk_nonce = self._derive_chunk_nonce(base_nonce, i)
                aad = struct.pack('<I', i)

                enc_len = struct.unpack('<I', fin.read(4))[0]
                encrypted = fin.read(enc_len)

                decrypted = self.aesgcm.decrypt(chunk_nonce, encrypted, aad)
                fout.write(decrypted)

        return {"chunks_decrypted": chunk_count}

    def _derive_chunk_nonce(self, base_nonce: bytes, chunk_index: int) -> bytes:
        """Derive a unique nonce for each chunk by XORing with chunk index."""
        nonce_int = int.from_bytes(base_nonce, 'big') ^ chunk_index
        return nonce_int.to_bytes(self.NONCE_SIZE, 'big')

# Usage example
key = AESGCM.generate_key(bit_length=256)
encryptor = FileEncryptor(key)

# Create a test file
test_data = b"Hello, encrypted world! " * 10000  # ~240 KB
with open("/tmp/test_plain.bin", "wb") as f:
    f.write(test_data)

# Encrypt
info = encryptor.encrypt_file("/tmp/test_plain.bin", "/tmp/test_encrypted.bin")
print(f"Encrypted: {info}")

# Decrypt
info = encryptor.decrypt_file("/tmp/test_encrypted.bin", "/tmp/test_decrypted.bin")
print(f"Decrypted: {info}")

# Verify
with open("/tmp/test_decrypted.bin", "rb") as f:
    decrypted_data = f.read()
assert decrypted_data == test_data
print("Verification: OK - decrypted data matches original")

# Clean up
for f in ["/tmp/test_plain.bin", "/tmp/test_encrypted.bin", "/tmp/test_decrypted.bin"]:
    Path(f).unlink(missing_ok=True)

5. ChaCha20-Poly1305

ChaCha20-Poly1305๋Š” ChaCha20 ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ์™€ Poly1305 MAC์„ ๊ฒฐํ•ฉํ•œ AEAD ์•”ํ˜ธ์ž…๋‹ˆ๋‹ค. AES-GCM์˜ ์ฃผ์š” ๋Œ€์•ˆ์ž…๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  AES-GCM vs ChaCha20-Poly1305                        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                  โ”‚ AES-256-GCM          โ”‚ ChaCha20-Poly1305          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํ‚ค ํฌ๊ธฐ          โ”‚ 256 bits             โ”‚ 256 bits                   โ”‚
โ”‚ Nonce ํฌ๊ธฐ       โ”‚ 96 bits              โ”‚ 96 bits                    โ”‚
โ”‚ Tag ํฌ๊ธฐ         โ”‚ 128 bits             โ”‚ 128 bits                   โ”‚
โ”‚ ์†๋„ (HW ๊ฐ€์†)   โ”‚ ๋งค์šฐ ๋น ๋ฆ„ (AES-NI)   โ”‚ AES-NI๋กœ ๋А๋ฆผ              โ”‚
โ”‚ ์†๋„ (์†Œํ”„ํŠธ์›จ์–ด)โ”‚ ๋А๋ฆผ                 โ”‚ ๋น ๋ฆ„ (ํŠน์ˆ˜ HW ๋ถˆํ•„์š”)      โ”‚
โ”‚ ๋ชจ๋ฐ”์ผ/์ž„๋ฒ ๋””๋“œ  โ”‚ HW ์ง€์› ํ•„์š”         โ”‚ ์šฐ์ˆ˜ (์ˆœ์ˆ˜ ์†Œํ”„ํŠธ์›จ์–ด)     โ”‚
โ”‚ ์‚ฌ์ด๋“œ ์ฑ„๋„      โ”‚ ์ฃผ์˜ ํ•„์š” (T-ํ…Œ์ด๋ธ”) โ”‚ ๋ณธ์งˆ์ ์œผ๋กœ ์ƒ์ˆ˜ ์‹œ๊ฐ„       โ”‚
โ”‚ ์‚ฌ์šฉ์ฒ˜           โ”‚ TLS, IPsec, ๋””์Šคํฌ ์•”โ”‚ TLS (Google/CF), WireGuardโ”‚
โ”‚ Nonce ์˜ค์šฉ       โ”‚ ์น˜๋ช…์                โ”‚ ์น˜๋ช…์                      โ”‚
โ”‚ ์–‘์ž ์ดํ›„        โ”‚ PQ ์ €ํ•ญ ์—†์Œ         โ”‚ PQ ์ €ํ•ญ ์—†์Œ               โ”‚
โ”‚                  โ”‚ (๋‹จ๋…์œผ๋กœ)           โ”‚ (๋‹จ๋…์œผ๋กœ)                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

5.1 ChaCha20-Poly1305 ๊ตฌํ˜„

import os
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305

# Generate a 256-bit key
key = ChaCha20Poly1305.generate_key()
chacha = ChaCha20Poly1305(key)

# Encrypt
nonce = os.urandom(12)  # 96-bit nonce
message = b"ChaCha20-Poly1305 is great for mobile and embedded devices"
aad = b"metadata: device=mobile, version=1"

ciphertext = chacha.encrypt(nonce, message, aad)
print(f"Plaintext:  {message.decode()}")
print(f"Ciphertext: {ciphertext.hex()[:64]}...")
print(f"CT length:  {len(ciphertext)} (plaintext {len(message)} + tag 16)")

# Decrypt
plaintext = chacha.decrypt(nonce, ciphertext, aad)
assert plaintext == message
print(f"Decrypted:  {plaintext.decode()}")

# Tamper detection
tampered = bytearray(ciphertext)
tampered[-1] ^= 0x01
try:
    chacha.decrypt(nonce, bytes(tampered), aad)
except Exception as e:
    print(f"Tamper detected: {type(e).__name__}")

5.2 XChaCha20-Poly1305 (ํ™•์žฅ Nonce)

XChaCha20์€ 192๋น„ํŠธ nonce๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(96๋น„ํŠธ ๋Œ€๋น„). ์ด๋Š” ํ˜„์‹ค์ ์ธ ์ถฉ๋Œ ์œ„ํ—˜ ์—†์ด ๋ฌด์ž‘์œ„๋กœ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์„ ๋งŒํผ ์ถฉ๋ถ„ํžˆ ํฝ๋‹ˆ๋‹ค. ์ด๋Š” nonce ๊ด€๋ฆฌ ๋ถ€๋‹ด์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

# XChaCha20 is available through libsodium bindings (PyNaCl)
# pip install pynacl

import nacl.secret
import nacl.utils

# XChaCha20-Poly1305 with 192-bit random nonce
key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)  # 256-bit
box = nacl.secret.SecretBox(key)

# Encrypt - nonce is generated automatically (192-bit, random-safe)
message = b"With XChaCha20, random nonces are always safe"
encrypted = box.encrypt(message)

print(f"Nonce size: {box.NONCE_SIZE} bytes = {box.NONCE_SIZE * 8} bits")
print(f"Encrypted length: {len(encrypted)} bytes")

# Decrypt
decrypted = box.decrypt(encrypted)
print(f"Decrypted: {decrypted.decode()}")

6. ๋น„๋Œ€์นญ ์•”ํ˜ธํ™”

๋น„๋Œ€์นญ(๊ณต๊ฐœํ‚ค) ์•”ํ˜ธํ™”๋Š” ํ‚ค ์Œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: ์•”ํ˜ธํ™”(๋˜๋Š” ๊ฒ€์ฆ)๋ฅผ ์œ„ํ•œ ๊ณต๊ฐœ ํ‚ค์™€ ๋ณตํ˜ธํ™”(๋˜๋Š” ์„œ๋ช…)๋ฅผ ์œ„ํ•œ ๊ฐœ์ธ ํ‚ค์ž…๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ๋น„๋Œ€์นญ ์•”ํ˜ธํ•™                                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                      โ”‚
โ”‚  ํ‚ค ์ƒ์„ฑ                                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                       โ”‚
โ”‚  โ”‚ KeyGen() โ”‚โ”€โ”€โ–ถ Private Key (๋น„๋ฐ€๋กœ ์œ ์ง€!)                         โ”‚
โ”‚  โ”‚          โ”‚โ”€โ”€โ–ถ Public Key  (์ž์œ ๋กญ๊ฒŒ ๊ณต์œ )                        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                       โ”‚
โ”‚                                                                      โ”‚
โ”‚  ์•”ํ˜ธํ™” (๋ˆ„๊ตฌ๋‚˜ โ†’ ํ‚ค ์†Œ์œ ์ž)                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   Public Key   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚
โ”‚  โ”‚ Plaintext โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ Encrypt  โ”‚โ”€โ”€โ–ถ Ciphertext            โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ”‚                                                                      โ”‚
โ”‚  ๋ณตํ˜ธํ™” (ํ‚ค ์†Œ์œ ์ž๋งŒ)                                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   Private Key  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚
โ”‚  โ”‚Ciphertext โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ Decrypt  โ”‚โ”€โ”€โ–ถ Plaintext             โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ”‚                                                                      โ”‚
โ”‚  ์„œ๋ช… (ํ‚ค ์†Œ์œ ์ž โ†’ ๋ˆ„๊ตฌ๋‚˜ ๊ฒ€์ฆ ๊ฐ€๋Šฅ)                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   Private Key  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚
โ”‚  โ”‚ Message   โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚  Sign    โ”‚โ”€โ”€โ–ถ Signature             โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ”‚                                                                      โ”‚
โ”‚  ๊ฒ€์ฆ (๊ณต๊ฐœ ํ‚ค๋ฅผ ๊ฐ€์ง„ ๋ˆ„๊ตฌ๋‚˜)                                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   Public Key   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚
โ”‚  โ”‚ Message + โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ Verify   โ”‚โ”€โ”€โ–ถ Valid / Invalid       โ”‚
โ”‚  โ”‚ Signature โ”‚               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                      โ”‚
โ”‚                                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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

๋น„๋Œ€์นญ ์•”ํ˜ธํ™”๋Š” ๋А๋ฆฌ๊ณ  ์•”ํ˜ธํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์–‘์ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™”๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: ๋Œ€์นญ ํ‚ค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์•”ํ˜ธํ™”ํ•œ ๋‹ค์Œ ์ˆ˜์‹ ์ž์˜ ๊ณต๊ฐœ ํ‚ค๋กœ ๋Œ€์นญ ํ‚ค๋ฅผ ์•”ํ˜ธํ™”ํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™”                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  ๋ฐœ์‹ ์ž:                                                         โ”‚
โ”‚  1. ๋žœ๋ค ๋Œ€์นญ ํ‚ค ์ƒ์„ฑ (์˜ˆ: AES-256)                             โ”‚
โ”‚  2. ๋Œ€์นญ ํ‚ค๋กœ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™” (AES-GCM)                           โ”‚
โ”‚  3. ์ˆ˜์‹ ์ž์˜ ๊ณต๊ฐœ ํ‚ค๋กœ ๋Œ€์นญ ํ‚ค ์•”ํ˜ธํ™” (RSA)                     โ”‚
โ”‚  4. ์ „์†ก: [encrypted_key] + [encrypted_data]                    โ”‚
โ”‚                                                                  โ”‚
โ”‚  ์ˆ˜์‹ ์ž:                                                         โ”‚
โ”‚  1. ๊ฐœ์ธ ํ‚ค๋กœ ๋Œ€์นญ ํ‚ค ๋ณตํ˜ธํ™” (RSA)                              โ”‚
โ”‚  2. ๋Œ€์นญ ํ‚ค๋กœ ๋ฐ์ดํ„ฐ ๋ณตํ˜ธํ™” (AES-GCM)                           โ”‚
โ”‚                                                                  โ”‚
โ”‚  ์ด๊ฒƒ์€ ๋‹ค์Œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:                                       โ”‚
โ”‚  - ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ๋Œ€์นญ ์•”ํ˜ธํ™”์˜ ์†๋„                        โ”‚
โ”‚  - ํ‚ค ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ ๊ณต๊ฐœ ํ‚ค ์•”ํ˜ธํ™”์˜ ํŽธ๋ฆฌํ•จ                       โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

7. RSA

RSA(Rivest-Shamir-Adleman)๋Š” ๊ฐ€์žฅ ๋„๋ฆฌ ๋ฐฐํฌ๋œ ๋น„๋Œ€์นญ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. ๋ณด์•ˆ์€ ํฐ ์ •์ˆ˜๋ฅผ ์ธ์ˆ˜๋ถ„ํ•ดํ•˜๋Š” ์–ด๋ ค์›€์— ๊ธฐ๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค.

7.1 RSA ํ‚ค ์ƒ์„ฑ ๋ฐ ์•”ํ˜ธํ™”

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

# Generate RSA key pair
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=4096,  # 2048 minimum, 4096 recommended
)
public_key = private_key.public_key()

# Display key info
print(f"Key size: {private_key.key_size} bits")
print(f"Public exponent: {private_key.private_numbers().public_numbers.e}")

# Serialize keys (PEM format)
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.BestAvailableEncryption(b"my-password")
)
public_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
print(f"\nPublic key (first 80 chars):\n{public_pem.decode()[:80]}...")

# Encrypt with public key (using OAEP padding - ALWAYS use OAEP, never PKCS1v15)
message = b"Secret message for RSA encryption"
ciphertext = public_key.encrypt(
    message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
print(f"\nCiphertext: {ciphertext.hex()[:64]}...")
print(f"CT length: {len(ciphertext)} bytes")

# Decrypt with private key
plaintext = private_key.decrypt(
    ciphertext,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
print(f"Decrypted: {plaintext.decode()}")

7.2 RSA ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™”

import os
import json
import base64
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

class HybridEncryptor:
    """RSA + AES-GCM hybrid encryption."""

    def __init__(self, public_key=None, private_key=None):
        self.public_key = public_key
        self.private_key = private_key

    @classmethod
    def generate_keypair(cls):
        """Generate a new RSA key pair and return an encryptor."""
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=4096,
        )
        return cls(
            public_key=private_key.public_key(),
            private_key=private_key,
        )

    def encrypt(self, plaintext: bytes) -> dict:
        """Encrypt data using hybrid encryption."""
        if not self.public_key:
            raise ValueError("Public key required for encryption")

        # 1. Generate random AES-256 key
        aes_key = AESGCM.generate_key(bit_length=256)

        # 2. Encrypt data with AES-GCM
        nonce = os.urandom(12)
        aesgcm = AESGCM(aes_key)
        encrypted_data = aesgcm.encrypt(nonce, plaintext, None)

        # 3. Encrypt AES key with RSA public key
        encrypted_key = self.public_key.encrypt(
            aes_key,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )

        # 4. Package everything together
        return {
            "encrypted_key": base64.b64encode(encrypted_key).decode(),
            "nonce": base64.b64encode(nonce).decode(),
            "ciphertext": base64.b64encode(encrypted_data).decode(),
        }

    def decrypt(self, package: dict) -> bytes:
        """Decrypt hybrid-encrypted data."""
        if not self.private_key:
            raise ValueError("Private key required for decryption")

        # 1. Decrypt AES key with RSA private key
        encrypted_key = base64.b64decode(package["encrypted_key"])
        aes_key = self.private_key.decrypt(
            encrypted_key,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )

        # 2. Decrypt data with AES-GCM
        nonce = base64.b64decode(package["nonce"])
        ciphertext = base64.b64decode(package["ciphertext"])
        aesgcm = AESGCM(aes_key)
        return aesgcm.decrypt(nonce, ciphertext, None)

# Usage
encryptor = HybridEncryptor.generate_keypair()

# Encrypt a large message
large_message = b"A" * 100000  # 100 KB - too large for raw RSA
package = encryptor.encrypt(large_message)

print("Hybrid Encryption Package:")
print(f"  Encrypted key length: {len(base64.b64decode(package['encrypted_key']))} bytes")
print(f"  Nonce: {package['nonce']}")
print(f"  Ciphertext length: {len(base64.b64decode(package['ciphertext']))} bytes")

# Decrypt
decrypted = encryptor.decrypt(package)
assert decrypted == large_message
print(f"\nDecrypted successfully: {len(decrypted)} bytes match original")

8. ํƒ€์› ๊ณก์„  ์•”ํ˜ธํ•™

ECC๋Š” ํ›จ์”ฌ ์ž‘์€ ํ‚ค ํฌ๊ธฐ๋กœ RSA์™€ ๋™์ผํ•œ ๋ณด์•ˆ์„ ์ œ๊ณตํ•˜์—ฌ ๋” ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              RSA vs ํƒ€์› ๊ณก์„  ํ‚ค ํฌ๊ธฐ                                โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๋ณด์•ˆ ์ˆ˜์ค€        โ”‚ RSA ํ‚ค ํฌ๊ธฐ      โ”‚ ECC ํ‚ค ํฌ๊ธฐ                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 128-bit          โ”‚ 3072 bits        โ”‚ 256 bits (P-256/secp256r1)     โ”‚
โ”‚ 192-bit          โ”‚ 7680 bits        โ”‚ 384 bits (P-384/secp384r1)     โ”‚
โ”‚ 256-bit          โ”‚ 15360 bits       โ”‚ 521 bits (P-521/secp521r1)     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ECC๋Š” ๋™๋“ฑํ•œ ๋ณด์•ˆ์„ ์œ„ํ•ด ~10-15๋ฐฐ ์ž‘์Šต๋‹ˆ๋‹ค!                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

8.1 ECDSA (ํƒ€์› ๊ณก์„  ๋””์ง€ํ„ธ ์„œ๋ช… ์•Œ๊ณ ๋ฆฌ์ฆ˜)

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes

# Generate key pair using P-256 curve (NIST recommended)
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()

print(f"Curve: {private_key.curve.name}")
print(f"Key size: {private_key.curve.key_size} bits")

# Sign a message
message = b"This message is signed with ECDSA"
signature = private_key.sign(
    message,
    ec.ECDSA(hashes.SHA256())
)
print(f"\nSignature: {signature.hex()[:64]}...")
print(f"Signature length: {len(signature)} bytes")  # ~70-72 bytes for P-256

# Verify
try:
    public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
    print("Signature valid!")
except Exception:
    print("Signature invalid!")

# Tampered message fails
try:
    public_key.verify(signature, b"tampered message", ec.ECDSA(hashes.SHA256()))
    print("ERROR: Should have failed!")
except Exception:
    print("Tampered message: signature verification failed (expected)")

8.2 Ed25519 (ํ˜„๋Œ€ ์„œ๋ช… ์•Œ๊ณ ๋ฆฌ์ฆ˜)

Ed25519๋Š” Curve25519๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ˜„๋Œ€ EdDSA ์„œ๋ช… ์Šคํ‚ด์ž…๋‹ˆ๋‹ค. ๊ฒฐ์ •๋ก ์ ์ด๊ณ (์„œ๋ช… ์ค‘ ๋žœ๋ค nonce ๋ถˆํ•„์š”), ๋น ๋ฅด๋ฉฐ, ์‚ฌ์ด๋“œ ์ฑ„๋„ ๊ณต๊ฒฉ์— ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค.

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

# Generate Ed25519 key pair
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()

# Sign (no hash algorithm parameter needed - it uses SHA-512 internally)
message = b"Ed25519 is the recommended signature algorithm for new systems"
signature = private_key.sign(message)

print(f"Signature: {signature.hex()}")
print(f"Signature length: {len(signature)} bytes")  # Always 64 bytes

# Verify
try:
    public_key.verify(signature, message)
    print("Ed25519 signature valid!")
except Exception as e:
    print(f"Invalid: {e}")

# Key sizes are small
from cryptography.hazmat.primitives.serialization import (
    Encoding, PublicFormat, PrivateFormat, NoEncryption
)

pub_bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
priv_bytes = private_key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
print(f"\nPublic key:  {len(pub_bytes)} bytes ({len(pub_bytes) * 8} bits)")
print(f"Private key: {len(priv_bytes)} bytes ({len(priv_bytes) * 8} bits)")
print(f"Signature:   {len(signature)} bytes ({len(signature) * 8} bits)")
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              ์„œ๋ช… ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋น„๊ต                                      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚              โ”‚ RSA-2048 โ”‚ECDSA P256โ”‚ Ed25519  โ”‚ Ed448               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๊ณต๊ฐœ ํ‚ค ํฌ๊ธฐ โ”‚ 256 B    โ”‚ 64 B     โ”‚ 32 B     โ”‚ 57 B                โ”‚
โ”‚ ์„œ๋ช… ํฌ๊ธฐ    โ”‚ 256 B    โ”‚ ~72 B    โ”‚ 64 B     โ”‚ 114 B               โ”‚
โ”‚ ์„œ๋ช… ์†๋„    โ”‚ ๋А๋ฆผ     โ”‚ ๋น ๋ฆ„     โ”‚ ๋งค์šฐ ๋น ๋ฆ„โ”‚ ๋น ๋ฆ„                โ”‚
โ”‚ ๊ฒ€์ฆ ์†๋„    โ”‚ ๋น ๋ฆ„     โ”‚ ๋ณดํ†ต     โ”‚ ๋น ๋ฆ„     โ”‚ ๋น ๋ฆ„                โ”‚
โ”‚ ๊ฒฐ์ •๋ก ์      โ”‚ No       โ”‚ No*      โ”‚ Yes      โ”‚ Yes                 โ”‚
โ”‚ ์‚ฌ์ด๋“œ ์ฑ„๋„  โ”‚ ์ฃผ์˜     โ”‚ ์ฃผ์˜     โ”‚ ๋ณธ์งˆ์    โ”‚ ๋ณธ์งˆ์               โ”‚
โ”‚ ์ €ํ•ญ         โ”‚ ํ•„์š”     โ”‚ ํ•„์š”     โ”‚          โ”‚                     โ”‚
โ”‚ ํ‘œ์ค€         โ”‚ PKCS#1   โ”‚ FIPS     โ”‚ RFC 8032 โ”‚ RFC 8032            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ * RFC 6979๋Š” ๊ฒฐ์ •๋ก ์  ECDSA๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ ๋ชจ๋“  ๊ตฌํ˜„์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ โ”‚
โ”‚ ๊ถŒ์žฅ ์‚ฌํ•ญ: ์ƒˆ ์‹œ์Šคํ…œ์—๋Š” Ed25519 ์‚ฌ์šฉ                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

9. ํ‚ค ๊ตํ™˜

ํ‚ค ๊ตํ™˜ ํ”„๋กœํ† ์ฝœ์€ ๋‘ ๋‹น์‚ฌ์ž๊ฐ€ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฑ„๋„์„ ํ†ตํ•ด ๊ณต์œ  ๋น„๋ฐ€์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

9.1 Diffie-Hellman ํ‚ค ๊ตํ™˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               Diffie-Hellman ํ‚ค ๊ตํ™˜                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                      โ”‚
โ”‚   Alice                                        Bob                   โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€                                        โ”€โ”€โ”€                   โ”‚
โ”‚                                                                      โ”‚
โ”‚   1. ๊ฐœ์ธ ํ‚ค ์„ ํƒ: a                           1. ๊ฐœ์ธ ํ‚ค ์„ ํƒ: b    โ”‚
โ”‚   2. ๊ณ„์‚ฐ: A = g^a mod p                       2. ๊ณ„์‚ฐ: B = g^b      โ”‚
โ”‚                                                    mod p             โ”‚
โ”‚                                                                      โ”‚
โ”‚   3. A ์ „์†ก โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ A ์ˆ˜์‹                โ”‚
โ”‚      B ์ˆ˜์‹  โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. B ์ „์†ก            โ”‚
โ”‚                                                                      โ”‚
โ”‚   5. ๊ณ„์‚ฐ:                                     5. ๊ณ„์‚ฐ:              โ”‚
โ”‚      shared = B^a mod p                           shared = A^b mod pโ”‚
โ”‚             = (g^b)^a mod p                              = (g^a)^b  โ”‚
โ”‚             = g^(ab) mod p                                  mod p   โ”‚
โ”‚                                                          = g^(ab)   โ”‚
โ”‚                                                            mod p    โ”‚
โ”‚                                                                      โ”‚
โ”‚   ๋‘ ๋‹น์‚ฌ์ž ๋ชจ๋‘ ๋™์ผํ•œ ๊ณต์œ  ๋น„๋ฐ€์— ๋„๋‹ฌ: g^(ab) mod p              โ”‚
โ”‚                                                                      โ”‚
โ”‚   Eve๊ฐ€ ๋ณด๋Š” ๊ฒƒ: g, p, A=g^a, B=g^b                                โ”‚
โ”‚   A๋กœ๋ถ€ํ„ฐ a๋ฅผ ๊ณ„์‚ฐํ•˜๋ ค๋ฉด ์ด์‚ฐ ๋กœ๊ทธ ๋ฌธ์ œ๋ฅผ ํ’€์–ด์•ผ ํ•˜๋ฉฐ               โ”‚
โ”‚   ํฐ ์†Œ์ˆ˜์— ๋Œ€ํ•ด ๊ณ„์‚ฐ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ๋ฏฟ์–ด์ง‘๋‹ˆ๋‹ค.                โ”‚
โ”‚                                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

9.2 ECDH (ํƒ€์› ๊ณก์„  Diffie-Hellman)

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

def ecdh_key_exchange():
    """
    Demonstrate ECDH key exchange between Alice and Bob.
    Uses X25519-equivalent (P-256 shown here for compatibility).
    """
    # Alice generates her key pair
    alice_private = ec.generate_private_key(ec.SECP256R1())
    alice_public = alice_private.public_key()

    # Bob generates his key pair
    bob_private = ec.generate_private_key(ec.SECP256R1())
    bob_public = bob_private.public_key()

    # Alice computes shared secret using her private key + Bob's public key
    alice_shared = alice_private.exchange(ec.ECDH(), bob_public)

    # Bob computes shared secret using his private key + Alice's public key
    bob_shared = bob_private.exchange(ec.ECDH(), alice_public)

    # Both shared secrets are identical
    assert alice_shared == bob_shared
    print(f"ECDH shared secret: {alice_shared.hex()[:32]}...")
    print(f"Shared secret length: {len(alice_shared)} bytes")

    # Derive actual encryption key from shared secret using HKDF
    # (raw ECDH output should NOT be used directly as an encryption key)
    alice_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,  # 256-bit key for AES-256
        salt=None,
        info=b"ecdh-derived-key-v1",
    ).derive(alice_shared)

    bob_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b"ecdh-derived-key-v1",
    ).derive(bob_shared)

    assert alice_key == bob_key
    print(f"Derived AES key: {alice_key.hex()}")

    return alice_key

derived_key = ecdh_key_exchange()

9.3 X25519 ํ‚ค ๊ตํ™˜

X25519๋Š” ํ˜„๋Œ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ถŒ์žฅ๋˜๋Š” ECDH ๊ณก์„ ์ž…๋‹ˆ๋‹ค(TLS 1.3, WireGuard, Signal์—์„œ ์‚ฌ์šฉ).

from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

# Alice
alice_private = X25519PrivateKey.generate()
alice_public = alice_private.public_key()

# Bob
bob_private = X25519PrivateKey.generate()
bob_public = bob_private.public_key()

# Exchange
alice_shared = alice_private.exchange(bob_public)
bob_shared = bob_private.exchange(alice_public)

assert alice_shared == bob_shared
print(f"X25519 shared secret: {alice_shared.hex()}")

# Derive keys using HKDF
def derive_keys(shared_secret: bytes, context: bytes) -> dict:
    """Derive separate keys for encryption and MAC from shared secret."""
    # Encryption key
    enc_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=context + b"-enc",
    ).derive(shared_secret)

    # MAC key (for additional message authentication if needed)
    mac_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=context + b"-mac",
    ).derive(shared_secret)

    return {"encryption_key": enc_key, "mac_key": mac_key}

keys = derive_keys(alice_shared, b"session-2026-01-15")
print(f"Encryption key: {keys['encryption_key'].hex()}")
print(f"MAC key:        {keys['mac_key'].hex()}")

10. ๋””์ง€ํ„ธ ์„œ๋ช…

๋””์ง€ํ„ธ ์„œ๋ช…์€ ์ธ์ฆ, ๋ฌด๊ฒฐ์„ฑ ๋ฐ ๋ถ€์ธ ๋ฐฉ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ˆ„๊ตฌ๋‚˜ ๋ฉ”์‹œ์ง€๊ฐ€ ํŠน์ • ๊ฐœ์ธ ํ‚ค์˜ ์†Œ์œ ์ž์— ์˜ํ•ด ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉฐ ์ˆ˜์ •๋˜์ง€ ์•Š์•˜์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

10.1 ๋””์ง€ํ„ธ ์„œ๋ช… ์ž‘๋™ ๋ฐฉ์‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    ๋””์ง€ํ„ธ ์„œ๋ช… ํ”„๋กœ์„ธ์Šค                               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                      โ”‚
โ”‚  ์„œ๋ช… (์ž‘์„ฑ์ž๊ฐ€ ๊ฐœ์ธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ):                                โ”‚
โ”‚                                                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ Message  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  Hash    โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  Sign    โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Signature โ”‚  โ”‚
โ”‚  โ”‚          โ”‚     โ”‚ (SHA-256)โ”‚     โ”‚(Private  โ”‚     โ”‚           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚  Key)    โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                     โ”‚
โ”‚                                                                      โ”‚
โ”‚  ๊ฒ€์ฆ (์ž‘์„ฑ์ž์˜ ๊ณต๊ฐœ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ˆ„๊ตฌ๋‚˜):                         โ”‚
โ”‚                                                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                      โ”‚
โ”‚  โ”‚ Message  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚  Hash    โ”‚โ”€โ”€โ”€โ”€โ”€โ”                                โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚ (SHA-256)โ”‚     โ”‚                                โ”‚
โ”‚                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ–ผ                                โ”‚
โ”‚                                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                 โ”‚  Verify  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Valid /  โ”‚       โ”‚
โ”‚  โ”‚ Signature โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚(Public   โ”‚     โ”‚ Invalid  โ”‚       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                 โ”‚  Key)    โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ”‚                                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                         โ”‚
โ”‚                                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

10.2 ์‹ค์Šต: ๋ฌธ์„œ ์„œ๋ช… ์‹œ์Šคํ…œ

import json
import time
import base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import (
    Encoding, PublicFormat, PrivateFormat, NoEncryption
)
from dataclasses import dataclass, asdict
from typing import Optional

@dataclass
class SignedDocument:
    """A document with a digital signature."""
    content: str
    author: str
    timestamp: float
    public_key: str  # Base64-encoded public key
    signature: str   # Base64-encoded signature

class DocumentSigner:
    """Sign and verify documents using Ed25519."""

    def __init__(self):
        self.private_key = Ed25519PrivateKey.generate()
        self.public_key = self.private_key.public_key()

    def get_public_key_b64(self) -> str:
        """Get base64-encoded public key for sharing."""
        raw = self.public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
        return base64.b64encode(raw).decode()

    def sign_document(self, content: str, author: str) -> SignedDocument:
        """Sign a document and return it with the signature."""
        timestamp = time.time()

        # Create canonical message to sign
        message = self._canonical_message(content, author, timestamp)

        # Sign
        signature = self.private_key.sign(message)

        return SignedDocument(
            content=content,
            author=author,
            timestamp=timestamp,
            public_key=self.get_public_key_b64(),
            signature=base64.b64encode(signature).decode(),
        )

    @staticmethod
    def verify_document(doc: SignedDocument) -> dict:
        """Verify a signed document. Returns verification result."""
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

        try:
            # Reconstruct the canonical message
            message = DocumentSigner._canonical_message(
                doc.content, doc.author, doc.timestamp
            )

            # Decode public key and signature
            pub_bytes = base64.b64decode(doc.public_key)
            signature = base64.b64decode(doc.signature)

            # Load public key
            public_key = Ed25519PublicKey.from_public_bytes(pub_bytes)

            # Verify
            public_key.verify(signature, message)

            return {
                "valid": True,
                "author": doc.author,
                "signed_at": time.ctime(doc.timestamp),
                "public_key": doc.public_key[:20] + "...",
            }
        except Exception as e:
            return {
                "valid": False,
                "error": str(e),
            }

    @staticmethod
    def _canonical_message(content: str, author: str, timestamp: float) -> bytes:
        """Create a canonical byte representation for signing."""
        canonical = json.dumps({
            "content": content,
            "author": author,
            "timestamp": timestamp,
        }, sort_keys=True, separators=(',', ':')).encode()
        return canonical

# Create signers for Alice and Bob
alice_signer = DocumentSigner()
bob_signer = DocumentSigner()

# Alice signs a document
doc = alice_signer.sign_document(
    content="I, Alice, hereby agree to pay Bob $100.",
    author="Alice"
)
print("Signed Document:")
print(f"  Content:   {doc.content}")
print(f"  Author:    {doc.author}")
print(f"  Timestamp: {time.ctime(doc.timestamp)}")
print(f"  Signature: {doc.signature[:40]}...")

# Anyone can verify using Alice's public key (embedded in document)
result = DocumentSigner.verify_document(doc)
print(f"\nVerification: {result}")

# Tamper with the document
tampered_doc = SignedDocument(
    content="I, Alice, hereby agree to pay Bob $10000.",  # Changed!
    author=doc.author,
    timestamp=doc.timestamp,
    public_key=doc.public_key,
    signature=doc.signature,
)
tamper_result = DocumentSigner.verify_document(tampered_doc)
print(f"\nTampered verification: {tamper_result}")

11. ์ผ๋ฐ˜์ ์ธ ํ•จ์ •๊ณผ ํ”ผํ•˜๋Š” ๋ฐฉ๋ฒ•

11.1 ์•”ํ˜ธํ•™์˜ ์น˜๋ช…์ ์ธ ์ฃ„์•…

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          ์ฃผ์š” ์•”ํ˜ธํ•™์  ํ•จ์ • (๋ฐ ํ”ผํ•˜๋Š” ๋ฐฉ๋ฒ•)                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ #  โ”‚ ํ•จ์ •               โ”‚ ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ๋ฒ•                             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1  โ”‚ ECB ๋ชจ๋“œ ์‚ฌ์šฉ      โ”‚ AES-GCM ๋˜๋Š” ChaCha20-Poly1305 ์‚ฌ์šฉ      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 2  โ”‚ ๋™์ผํ•œ ํ‚ค๋กœ        โ”‚ ๋ฉ”์‹œ์ง€๋‹น ๋žœ๋ค nonce (๋˜๋Š” ์นด์šดํ„ฐ)        โ”‚
โ”‚    โ”‚ nonces/IV ์žฌ์‚ฌ์šฉ   โ”‚ ๋žœ๋ค ์•ˆ์ „ nonce๋ฅผ ์œ„ํ•ด XChaCha20 ์‚ฌ์šฉ    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 3  โ”‚ ์ธ์ฆ ์—†์ด ์•”ํ˜ธํ™”   โ”‚ ํ•ญ์ƒ AEAD ์‚ฌ์šฉ (GCM, Poly1305)           โ”‚
โ”‚    โ”‚                    โ”‚ ์›์‹œ CBC/CTR ์‚ฌ์šฉ ๊ธˆ์ง€                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 4  โ”‚ ์ž์ฒด ์•”ํ˜ธํ™” ๊ตฌํ˜„   โ”‚ ํ™•๋ฆฝ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ (cryptography,    โ”‚
โ”‚    โ”‚                    โ”‚ libsodium/NaCl, OpenSSL)                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 5  โ”‚ ์•ฝํ•˜๊ฑฐ๋‚˜ ์˜ˆ์ธก      โ”‚ os.urandom() ๋˜๋Š” secrets ๋ชจ๋“ˆ ์‚ฌ์šฉ     โ”‚
โ”‚    โ”‚ ๊ฐ€๋Šฅํ•œ ๋‚œ์ˆ˜        โ”‚ ์•”ํ˜ธํ™”์— random.random() ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 6  โ”‚ ์†Œ์Šค ์ฝ”๋“œ์—        โ”‚ ํ‚ค ๊ด€๋ฆฌ ์‚ฌ์šฉ (AWS KMS, Vault)            โ”‚
โ”‚    โ”‚ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ํ‚ค      โ”‚ ์ตœ์†Œํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 7  โ”‚ ๋น„๋ฐ€๋ฒˆํ˜ธ์—         โ”‚ ํ•ด์‹œ์—๋Š” SHA-256+, ๋น„๋ฐ€๋ฒˆํ˜ธ์—๋Š”          โ”‚
โ”‚    โ”‚ MD5/SHA-1 ์‚ฌ์šฉ     โ”‚ bcrypt/argon2 ์‚ฌ์šฉ                       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 8  โ”‚ PKCS#1 v1.5        โ”‚ ์•”ํ˜ธํ™”์—๋Š” RSA-OAEP ์‚ฌ์šฉ                 โ”‚
โ”‚    โ”‚ ํŒจ๋”ฉ์œผ๋กœ RSA ์‚ฌ์šฉ  โ”‚ ์„œ๋ช…์—๋Š” RSA-PSS ์‚ฌ์šฉ                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 9  โ”‚ ==๋กœ MAC ๋น„๊ต      โ”‚ ์ƒ์ˆ˜ ์‹œ๊ฐ„ ๋น„๊ต๋ฅผ ์œ„ํ•ด                    โ”‚
โ”‚    โ”‚                    โ”‚ hmac.compare_digest() ์‚ฌ์šฉ               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 10 โ”‚ ํ‚ค ๋กœํ…Œ์ด์…˜ ์—†์Œ   โ”‚ ํ‚ค ๋กœํ…Œ์ด์…˜ ์ผ์ • ๊ตฌํ˜„                    โ”‚
โ”‚    โ”‚                    โ”‚ ํ‚ค ๋ฒ„์ „ ๊ด€๋ฆฌ ์‚ฌ์šฉ                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

11.2 Nonce ์žฌ์‚ฌ์šฉ ์žฌ์•™

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# Demonstration: Why nonce reuse with CTR/GCM is catastrophic
key = os.urandom(32)
nonce = os.urandom(16)  # Same nonce used twice - BAD!

def ctr_encrypt(key, nonce, plaintext):
    cipher = Cipher(algorithms.AES(key), modes.CTR(nonce))
    encryptor = cipher.encryptor()
    return encryptor.update(plaintext) + encryptor.finalize()

# Two messages encrypted with the SAME key and nonce
msg1 = b"Attack at dawn!!!"  # 17 bytes
msg2 = b"Retreat at night!"  # 17 bytes

ct1 = ctr_encrypt(key, nonce, msg1)
ct2 = ctr_encrypt(key, nonce, msg2)

# XOR of two ciphertexts = XOR of two plaintexts!
# In CTR mode: ct = plaintext XOR keystream
# So: ct1 XOR ct2 = (msg1 XOR keystream) XOR (msg2 XOR keystream)
#                  = msg1 XOR msg2  (keystream cancels out!)

xor_result = bytes(a ^ b for a, b in zip(ct1, ct2))
expected = bytes(a ^ b for a, b in zip(msg1, msg2))

print("Nonce Reuse Attack Demonstration:")
print(f"  ct1 XOR ct2:  {xor_result.hex()}")
print(f"  msg1 XOR msg2: {expected.hex()}")
print(f"  Match: {xor_result == expected}")
print()
print("  An attacker who knows msg1 can recover msg2:")
recovered = bytes(a ^ b for a, b in zip(xor_result, msg1))
print(f"  Recovered msg2: {recovered.decode()}")
print()
print("  LESSON: Never reuse a nonce with the same key!")

11.3 ์•ˆ์ „ํ•œ ๋‚œ์ˆ˜ vs ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋‚œ์ˆ˜

import random
import secrets
import os

# INSECURE - never use for cryptography
# random.random() uses Mersenne Twister (MT19937)
# It is deterministic and predictable if you observe enough outputs
insecure_key = bytes([random.randint(0, 255) for _ in range(32)])
print(f"INSECURE key: {insecure_key.hex()}")
print(f"  Source: random.random() - Mersenne Twister (PREDICTABLE)")

# SECURE - use for cryptography
secure_key = os.urandom(32)
print(f"\nSECURE key:   {secure_key.hex()}")
print(f"  Source: os.urandom() - OS CSPRNG (/dev/urandom)")

# ALSO SECURE - Python 3.6+ secrets module
secure_token = secrets.token_bytes(32)
print(f"\nSECURE key:   {secure_token.hex()}")
print(f"  Source: secrets.token_bytes() - wraps os.urandom()")

# For URL-safe tokens
url_token = secrets.token_urlsafe(32)
print(f"\nURL-safe token: {url_token}")

# For comparison tokens (timing-safe)
a = secrets.token_bytes(32)
b = secrets.token_bytes(32)
print(f"\nConstant-time comparison: {secrets.compare_digest(a, b)}")

12. ํ˜„๋Œ€ ๊ถŒ์žฅ ์‚ฌํ•ญ

12.1 ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ ํƒ ๊ฐ€์ด๋“œ (2025+)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               ํ˜„๋Œ€ ์•”ํ˜ธํ•™ ๊ถŒ์žฅ ์‚ฌํ•ญ                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์‚ฌ์šฉ ์‚ฌ๋ก€       โ”‚ ๊ถŒ์žฅ ์•Œ๊ณ ๋ฆฌ์ฆ˜                                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๋Œ€์นญ ์•”ํ˜ธํ™”     โ”‚ AES-256-GCM (ํ•˜๋“œ์›จ์–ด ์‚ฌ์šฉ) ๋˜๋Š”                 โ”‚
โ”‚                 โ”‚ ChaCha20-Poly1305 (ํ•˜๋“œ์›จ์–ด ์—†์Œ / ๋ชจ๋ฐ”์ผ)       โ”‚
โ”‚                 โ”‚ ๋žœ๋ค nonce ํ•„์š” ์‹œ XChaCha20-Poly1305            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํ‚ค ๊ตํ™˜         โ”‚ X25519 (Curve25519์™€ ECDH)                       โ”‚
โ”‚                 โ”‚ ML-KEM (์–‘์ž ์ดํ›„, X25519์™€ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ)          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๋””์ง€ํ„ธ ์„œ๋ช…     โ”‚ Ed25519 (๋ฒ”์šฉ)                                   โ”‚
โ”‚                 โ”‚ Ed448 (๋” ๋†’์€ ๋ณด์•ˆ ๋งˆ์ง„)                        โ”‚
โ”‚                 โ”‚ ECDSA P-256 (๋ ˆ๊ฑฐ์‹œ ํ˜ธํ™˜์„ฑ)                      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํ•ด์‹ฑ            โ”‚ SHA-256 / SHA-3-256 (๋ฒ”์šฉ)                       โ”‚
โ”‚                 โ”‚ BLAKE3 (์†๋„ ์ค‘์š”)                               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ   โ”‚ Argon2id (์„ ํ˜ธ)                                  โ”‚
โ”‚                 โ”‚ bcrypt (๋„๋ฆฌ ์ง€์›๋จ)                             โ”‚
โ”‚                 โ”‚ scrypt (๋ฉ”๋ชจ๋ฆฌ ํ•˜๋“œ ๋Œ€์•ˆ)                        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํ‚ค ์œ ๋„         โ”‚ HKDF-SHA256 (๊ณ  ์—”ํŠธ๋กœํ”ผ ์ž…๋ ฅ์—์„œ)               โ”‚
โ”‚                 โ”‚ Argon2id (๋น„๋ฐ€๋ฒˆํ˜ธ์—์„œ)                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ TLS             โ”‚ TLS 1.3 with X25519 + AES-256-GCM                โ”‚
โ”‚                 โ”‚ ๋˜๋Š” ChaCha20-Poly1305                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ํ”ผํ•ด์•ผ ํ•  ๊ฒƒ    โ”‚ MD5, SHA-1, DES, 3DES, RC4, RSA-1024,           โ”‚
โ”‚                 โ”‚ ECB ๋ชจ๋“œ, PKCS#1 v1.5, ์‚ฌ์šฉ์ž ์ •์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

12.2 ํ‚ค ํฌ๊ธฐ ๊ถŒ์žฅ ์‚ฌํ•ญ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              ์ตœ์†Œ ํ‚ค ํฌ๊ธฐ (NIST / ANSSI 2025+)                       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ์•Œ๊ณ ๋ฆฌ์ฆ˜            โ”‚ ์ตœ์†Œ ํ‚ค ํฌ๊ธฐ                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ AES                 โ”‚ 128-bit (์–‘์ž ์ดํ›„ ๋งˆ์ง„์„ ์œ„ํ•ด 256-bit)      โ”‚
โ”‚ RSA (ํ•„์š” ์‹œ)       โ”‚ 3072-bit (4096 ๊ถŒ์žฅ)                         โ”‚
โ”‚ ECDSA / ECDH        โ”‚ P-256 / Curve25519 (256-bit)                โ”‚
โ”‚ EdDSA               โ”‚ Ed25519 (256-bit)                            โ”‚
โ”‚ ํ•ด์‹œ ์ถœ๋ ฅ           โ”‚ 256-bit (SHA-256, SHA-3-256, BLAKE2b-256)   โ”‚
โ”‚ HMAC ํ‚ค             โ”‚ ํ•ด์‹œ ์ถœ๋ ฅ ํฌ๊ธฐ์™€ ๋™์ผ (256-bit)              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

12.3 ์–‘์ž ์ดํ›„ ์•”ํ˜ธํ•™

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              ์–‘์ž ์ดํ›„ ์•”ํ˜ธํ•™ (PQC)                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                      โ”‚
โ”‚  ๋ฌธ์ œ: ์–‘์ž ์ปดํ“จํ„ฐ (Shor ์•Œ๊ณ ๋ฆฌ์ฆ˜)๋Š” ๋‹ค์Œ์„ ๊นจ๋œจ๋ฆด ๊ฒƒ์ž…๋‹ˆ๋‹ค:        โ”‚
โ”‚  - RSA (์ธ์ˆ˜๋ถ„ํ•ด)                                                   โ”‚
โ”‚  - ECDSA/ECDH (ํƒ€์› ๊ณก์„  ์ด์‚ฐ ๋กœ๊ทธ)                                โ”‚
โ”‚  - DH (์ด์‚ฐ ๋กœ๊ทธ)                                                   โ”‚
โ”‚                                                                      โ”‚
โ”‚  ์–‘์ž์— ์˜ํ–ฅ๋ฐ›์ง€ ์•Š์Œ:                                              โ”‚
โ”‚  - AES (Grover ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ์œ ํšจ ํ‚ค ํฌ๊ธฐ๋ฅผ ์ ˆ๋ฐ˜์œผ๋กœ:                 โ”‚
โ”‚    AES-256 โ†’ ~128๋น„ํŠธ ๋ณด์•ˆ, ์—ฌ์ „ํžˆ ์•ˆ์ „)                          โ”‚
โ”‚  - SHA-256 (์œ ์‚ฌํ•œ ์ ˆ๋ฐ˜, ์—ฌ์ „ํžˆ ์ ์ ˆํ•จ)                            โ”‚
โ”‚                                                                      โ”‚
โ”‚  NIST PQC ํ‘œ์ค€ (2024 ํ™•์ •):                                        โ”‚
โ”‚  โ”œโ”€โ”€ ML-KEM (CRYSTALS-Kyber) โ€” ํ‚ค ์บก์Аํ™”                          โ”‚
โ”‚  โ”œโ”€โ”€ ML-DSA (CRYSTALS-Dilithium) โ€” ๋””์ง€ํ„ธ ์„œ๋ช…                    โ”‚
โ”‚  โ”œโ”€โ”€ SLH-DSA (SPHINCS+) โ€” ํ•ด์‹œ ๊ธฐ๋ฐ˜ ์„œ๋ช…                         โ”‚
โ”‚  โ””โ”€โ”€ FN-DSA (FALCON) โ€” ๊ฒฉ์ž ๊ธฐ๋ฐ˜ ์„œ๋ช…                            โ”‚
โ”‚                                                                      โ”‚
โ”‚  ํ˜„์žฌ ๊ถŒ์žฅ ์‚ฌํ•ญ: ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๋ชจ๋“œ                                    โ”‚
โ”‚  - ํ‚ค ๊ตํ™˜์— X25519 + ML-KEM์„ ํ•จ๊ป˜ ์‚ฌ์šฉ                          โ”‚
โ”‚  - ๊ณ ์ „ ๋˜๋Š” PQ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๊นจ์ ธ๋„ ์—ฌ์ „ํžˆ ์•ˆ์ „                      โ”‚
โ”‚  - Chrome, Firefox, Cloudflare๊ฐ€ ์ด๋ฏธ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ PQ ์ง€์›          โ”‚
โ”‚                                                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

13. ์—ฐ์Šต ๋ฌธ์ œ

์—ฐ์Šต ๋ฌธ์ œ 1: ๋Œ€์นญ ์•”ํ˜ธํ™” (์ดˆ๊ธ‰)

๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜๋Š” Python ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”: 1. ํ‰๋ฌธ ๋ฌธ์ž์—ด๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๊ธฐ 2. PBKDF2๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ๋ถ€ํ„ฐ AES-256 ํ‚ค ์œ ๋„ (๋žœ๋ค salt ์‚ฌ์šฉ) 3. AES-GCM์œผ๋กœ ํ‰๋ฌธ ์•”ํ˜ธํ™” 4. salt + nonce + ciphertext๋ฅผ ํฌํ•จํ•˜๋Š” ๋‹จ์ผ base64 ์ธ์ฝ”๋”ฉ ๋ฌธ์ž์—ด ๋ฐ˜ํ™˜ 5. ํ•ด๋‹นํ•˜๋Š” ๋ณตํ˜ธํ™” ํ•จ์ˆ˜ ์ž‘์„ฑ

ํžŒํŠธ:

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
# Use iterations=600000, salt_size=16, nonce_size=12

์—ฐ์Šต ๋ฌธ์ œ 2: ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•”ํ˜ธํ™” (์ค‘๊ธ‰)

PGP์™€ ์œ ์‚ฌํ•œ ์•”ํ˜ธํ™”์˜ ๊ฐ„๋‹จํ•œ ๋ฒ„์ „์„ ๊ตฌํ˜„ํ•˜์„ธ์š”: 1. Alice๊ฐ€ RSA-4096 ํ‚ค ์Œ ์ƒ์„ฑ 2. Bob์ด RSA-4096 ํ‚ค ์Œ ์ƒ์„ฑ 3. Alice๊ฐ€ Bob์—๊ฒŒ ์„œ๋ช…๋˜๊ณ  ์•”ํ˜ธํ™”๋œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋ ค๊ณ  ํ•จ: - Alice์˜ ๊ฐœ์ธ ํ‚ค๋กœ ๋ฉ”์‹œ์ง€ ์„œ๋ช… - ๋žœ๋ค AES-256 ํ‚ค ์ƒ์„ฑ - AES-GCM์œผ๋กœ ๋ฉ”์‹œ์ง€ ์•”ํ˜ธํ™” - Bob์˜ ๊ณต๊ฐœ RSA ํ‚ค๋กœ AES ํ‚ค ์•”ํ˜ธํ™” - ํŒจํ‚ค์ง€: encrypted_key + nonce + ciphertext + signature + alice_public_key 4. Bob์ด ํŒจํ‚ค์ง€๋ฅผ ๋ฐ›์•„์„œ: - ์ž์‹ ์˜ ๊ฐœ์ธ RSA ํ‚ค๋กœ AES ํ‚ค ๋ณตํ˜ธํ™” - AES-GCM์œผ๋กœ ๋ฉ”์‹œ์ง€ ๋ณตํ˜ธํ™” - Alice์˜ ๊ณต๊ฐœ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ช… ๊ฒ€์ฆ

์—ฐ์Šต ๋ฌธ์ œ 3: ํ‚ค ๊ตํ™˜ ํ”„๋กœํ† ์ฝœ (์ค‘๊ธ‰)

Alice์™€ Bob ๊ฐ„์˜ ์•ˆ์ „ํ•œ ์ฑ„ํŒ… ์‹œ๋ฎฌ๋ ˆ์ด์…˜: 1. ๋‘ ๋‹น์‚ฌ์ž ๋ชจ๋‘ X25519 ํ‚ค ๊ตํ™˜ ์ˆ˜ํ–‰ 2. HKDF๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ๋ฐฉํ–ฅ์— ๋Œ€ํ•œ ๋ณ„๋„ ํ‚ค ์œ ๋„ (Alice-to-Bob, Bob-to-Alice) 3. ๊ฐ ๋ฉ”์‹œ์ง€๋Š” ๊ณ ์œ ํ•œ nonce๋ฅผ ๋ฐ›์Œ (์นด์šดํ„ฐ ์‚ฌ์šฉ) 4. ๋ฉ”์‹œ์ง€๋Š” ChaCha20-Poly1305๋กœ ์•”ํ˜ธํ™”๋จ 5. ์žฌ์ƒ ๊ณต๊ฒฉ์„ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์‹œ์ง€ ์นด์šดํ„ฐ ํฌํ•จ

์—ฐ์Šต ๋ฌธ์ œ 4: Nonce ์žฌ์‚ฌ์šฉ ๊ณต๊ฒฉ (๊ณ ๊ธ‰)

๋™์ผํ•œ ํ‚ค์™€ nonce๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ AES-CTR๋กœ ์•”ํ˜ธํ™”๋œ ๋‘ ๊ฐœ์˜ ์•”ํ˜ธ๋ฌธ์ด ์ฃผ์–ด์กŒ์„ ๋•Œ:

ct1 = bytes.fromhex("a1b2c3d4e5f6071829")
ct2 = bytes.fromhex("b4a3d2c5f4e7162738")

๊ทธ๋ฆฌ๊ณ  plaintext1์ด b"plaintext"์ž„์„ ์•Œ ๋•Œ: 1. plaintext2 ๋ณต๊ตฌ 2. ์ด ๊ณต๊ฒฉ์ด ์ˆ˜ํ•™์ ์œผ๋กœ ์™œ ์ž‘๋™ํ•˜๋Š”์ง€ ์„ค๋ช… 3. ์ด ์ทจ์•ฝ์ ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ• ์„ค๋ช…

์—ฐ์Šต ๋ฌธ์ œ 5: ๋””์ง€ํ„ธ ์„œ๋ช… ๊ฒ€์ฆ (๊ณ ๊ธ‰)

๊ฐ„๋‹จํ•œ ์ฝ”๋“œ ์„œ๋ช… ์‹œ์Šคํ…œ ๊ตฌ์ถ•: 1. "๋ฐœํ–‰์ž"๊ฐ€ Ed25519๋กœ Python ์Šคํฌ๋ฆฝํŠธ ์„œ๋ช… 2. "์‹คํ–‰์ž"๊ฐ€ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ์ „์— ์„œ๋ช… ๊ฒ€์ฆ 3. ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฐœ ํ‚ค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์œ ์ง€ 4. ํ‚ค ๋กœํ…Œ์ด์…˜ ์ฒ˜๋ฆฌ (์ด์ „ ์„œ๋ช…์€ ์ด์ „ ํ‚ค๋กœ ์—ฌ์ „ํžˆ ์œ ํšจํ•ด์•ผ ํ•จ) 5. ํƒ€์ž„์Šคํƒฌํ”„ ๊ฒ€์ฆ ์ถ”๊ฐ€ (30์ผ ์ด์ƒ ๋œ ์„œ๋ช… ๊ฑฐ๋ถ€)

์—ฐ์Šต ๋ฌธ์ œ 6: ์•”ํ˜ธํ•™์  ๊ฐ์‚ฌ (๊ณ ๊ธ‰)

๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ๋ชจ๋“  ์•”ํ˜ธํ•™์  ์ทจ์•ฝ์ ์„ ์‹๋ณ„ํ•˜์„ธ์š”. ์ตœ์†Œ 8๊ฐœ์˜ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

import hashlib
import base64
from Crypto.Cipher import AES  # PyCryptodome

def encrypt_message(password, message):
    key = hashlib.md5(password.encode()).digest()  # 128-bit key from MD5
    iv = b'\x00' * 16  # Static IV
    cipher = AES.new(key, AES.MODE_CBC, iv)

    # Manual padding
    pad_len = 16 - (len(message) % 16)
    padded = message + chr(pad_len) * pad_len

    encrypted = cipher.encrypt(padded.encode())
    return base64.b64encode(encrypted).decode()

def verify_password(stored_hash, password):
    return hashlib.sha256(password.encode()).hexdigest() == stored_hash

๊ฐ ์ทจ์•ฝ์ ์— ๋Œ€ํ•ด ๋‹ค์Œ์„ ์„ค๋ช…ํ•˜์„ธ์š”: - ๋ฌด์—‡์ด ์ž˜๋ชป๋˜์—ˆ๋Š”์ง€ - ์™œ ์œ„ํ—˜ํ•œ์ง€ - ์–ด๋–ป๊ฒŒ ์ˆ˜์ •ํ•˜๋Š”์ง€


14. ์ฐธ๊ณ  ์ž๋ฃŒ

  • Ferguson, Schneier, Kohno. Cryptography Engineering. Wiley, 2010.
  • Bernstein, D.J. "Curve25519: New Diffie-Hellman Speed Records". 2006.
  • NIST SP 800-175B: Guideline for Using Cryptographic Standards
  • NIST Post-Quantum Cryptography - https://csrc.nist.gov/projects/post-quantum-cryptography
  • Python cryptography library docs - https://cryptography.io/
  • Latacora, "Cryptographic Right Answers" (2018, updated regularly)
  • RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
  • RFC 8439: ChaCha20 and Poly1305 for IETF Protocols

์ด์ „: 01. Security ๊ธฐ์ดˆ | ๋‹ค์Œ: 03. Hashing๊ณผ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ

to navigate between lessons