암호의 기초
암호화 과정
- 암호(Cryptography) : 중요 정보를 다른 사람들이 해석할 수 없게 하는 방법
- 평문(Plain Text) : 암호화되기 전의 메시지
- 암호문(Cipher Text) : 암호화되고 난 후 변경된 메시지
- 암호화 또는 암호화 과정 : 평문을 암호문으로 바꾸는 과정
- 암호화 알고리즘 : 평문을 어떤 방식으로 암호문으로 변경할지 결정
- 암호화 키(Encryption Key) : 허락받지 않은 외부인이 암호문을 강제적으로 해독(Crypt-analysis)하는
것을 막음

복호화 과정
- 복호화(Decrypt): 암호문을 복호화 키(Decryption Key)를 이용하여 평문으로 바꾸는 과정

| 구분 | 대칭 암호화 알고리즘 | 비대칭 암호화 알고리즘 |
|---|
| 키의 상호 관계 | 암호화 키 = 복호화 키 | 암호화 키 ≠ 복호화 키 |
암호의 역사 : 고전 암호학
스키테일(Scytale) 암호
- BC400년 스파르타에서 군사용으로 사용하던 암호화 방식
- 전치법(Transposition) 알고리즘 : 특정 순서에 따라 평문 배열을 재조정

시저 암호
- BC100년경에 로마의 줄리어스 시저가 군사적인 목적으로 사용한 암호
- 단일 치환(Mono-Alphabetic Substitution) 알고리즘: 알파벳 한 글자를 다른 한 글자로 대체
- 전사 공격 (Brute-force Attack) 에 취약

단일 치환 암호: 모노 알파베틱 (Mono-alphabetic) 암호
- 알파벳 26글자를 각각 다른 알파벳에 대응시키는 방식으로 암호화
- 단일 치환 암호 방식이지만, 알고리즘의 개선을 통해 보안성을 강화시킴
- 전사 공격에 다소 강하기 때문에, 10세기까지 사용됨
- 빈도 분석법(Frequency Analysis)에 취약


다중 치환(Polygram Substitution)
- 한 글자가 암호화 키와의 매핑에 따라 여러 가지 글자로 대체되어 암호화되는 방식
- 대표적인 예: 비즈네르(Vigenere) 암호

- 암호화의 예
- 암호화하려는 단어: aaa
- 암호화 키: BDG
- 빈도 분석법(Frequency Analysis)에 다소 강함
- 플레이페어(Playfair) 암호, 에니그마(Enigma) 등으로 발전됨
- 컴퓨터의 고속 병렬 처리 연산 등으로 현대에는 해독이 비교적 어렵지 않게 가능해짐


대칭 암호화 방식
DES
- 대칭키(Symmetric key) 암호화 방식
- 암호화 키와 복호화 키가 같은 암호화 방식
- 암호화 및 복호화에 걸리는 시간이 짧으며, 비교적 간단한 방식으로 구현 가능
- 현대에 가장 많이 사용되는 암호 방식 중 하나
- DES(Data Encryption Standard)
- 1997년 미국의 연방 정보의 표준 암호로 채택됨
- 64비트 평문을 64비트 암호문으로 암호화
- 키의 크기: 56비트(오류 검출을 위해 8비트가 사용)
- 전사 공격으로 DES의 해독이 가능해짐에 따라, 1998년 11월 이후 공식적 사용 중단
암호화 과정 개요
- 전처리(Pre-Processing) 단계: 초기 치환(Initial Permutations) 실행
- 16번의 라운드 (Round) 수행
- 후처리(Post-Processing) 단계: 최종 치환(Final Permutations)

1 라운드
- 64비트를 왼쪽 L(32비트)과 오른쪽 R(32비트)로 나눔
- L은 F와의 논리합을 계산
- R은 기존의 값을 그대로 사용
- L과 R의 위치를 바꿈

2~17 라운드
- 이전 라운드에서 만들어진 L과 R을 사용
- 암호화 키 K에서 만들어진 F() 함수와 논리합 연산
- L과 R 위치를 서로 바꿈

마지막 라운드
- 중간 연산은 앞의 라운드와 같음
- 최종적으로 계산된 L과 R을 합쳐서 하나의 블록으로 만듦

트리플 DES(Triple DES)
- DES의 대안으로 제시된 DES의 비도(암호화 강도)를 강화한 대칭키 암호 방식
- DES를 3번 연속 실행, 삼중 DES 혹은 3DES라고도 불림
- 2번의 DES 암호화 중간에 DES 복호화가 1번을 실행함
- 기존 DES와의 호환성 유지 목적
- 수학적으로 DES 알고리즘보다 2배의 암호화 강도를 가진다고 평가
- 충분한 암호 강도가 아니기 때문에, AES가 권장됨


AES(Advanced Encryption Standard)
- 1997년 미국 NIST의 암호 공모에서 새로운 대칭 암호의 표준으로 채택된 대칭키 암호 방식
- 암호 공모 조건
- 안전성(Security)
- 비용(Cost)
- 구현 효율성(Implementation)
- 특징
- 128비트 평문을 128비트 암호문으로 암호화
- 키의 크기: 3가지 (128, 192, 256비트)
- 10번의 라운드 (Round) 수행

AES의 각 라운드 구성
- 대치(Substitution) 연산 – SubBytes
- 치환(Permutation) 연산 – ShiftRows
- 혼합(Mixing) 연산 – MixColumns
- 열 단위 연산 수행(constant matrix를 곱함)
- AddRoundKey 연산

- 첫 번째 라운드 시작 전,
AddRoundKey 연산을 한 번 수행한다.
- 마지막 라운드에서는 세 번째 연산인
MixColumns는 수행하지 않는다.
그 밖의 대칭 암호
- SEED
- 국산 128비트 블록 암호 알고리즘
- 전자상거래, 금융, 무선통신 등에서 전송되는 중요 정보 보호 목적
- SEED 128 - 128비트 키, SEED 256 - 256비트 키
- ARIA
- 국산 128비트 블록 암호화 알고리즘
- 경량 환경 및 하드웨어에서의 효율성 향상을 위해 개발
- AES와 마찬가지로 128/192/256비트 암호화 키를 지원
- IDEA - 유럽에서 많이 사용
- RC5 - 미국 RSA 연구소에서 개발
블록 암호 모드

- 대칭 암호화 방식은 하나의 평문을 블록 단위로 나누어서 암호화 또는 복호화를 수행
- 예) 300비트 평문을 AES로 암호화 하는 경우
→ AES의 블록 크기: 128비트
블록 개수: 3개
3번째 블록에 저장되는 데이터 크기: 44비트
- 패딩(Padding): 블록의 남은 부분에 데이터를 채워 넣는 것

- ECB(Electric Code Book) 모드
- 각 평문 블록을 암호화한 것이 그대로 암호문 블록이 되는 방식
- 장점 - 계산이 단순
- 단점 – 보안적으로 취약 (변조 가능)
→ 중간 블록을 단순히 바꿔치기만 해도 동작함

- CBC(Cipher Block Chaining) 모드
- 암호문 블록을 마치 체인처럼 연결하는 모드
→ 현재의 평문 블록과 현재 블록 바로 직전에 암호화된 암호 블록을 XOR 연산한 후 암호화
- 초기화 벡터(IV: Initial Vector): 최초 블록을 암호화할 때 XOR 연산에 사용되는 블록
- 장점 - 보안적으로 안전 (변조 불가)
- 단점 - 계산이 다소 복잡
☞ 제약사항 : 중간에 있는 블록을 복호화하기 위해서
반드시 첫 블록부터 복호화를 해야함
| 구분 | 장점 | 단점 | 비고 |
|---|
| ECB 모드 | - 간단 - 병렬 처리 가능 (암호화, 복호화 모두) | - 평문의 반복이 암호문에 반영됨 - 암호문 블록의 삭제/교체에 취약 - 평문 변조 가능 | 보안상 취약함 사용 권장하지 않음 |
| CBC 모드 | - 평문 반복이 암호문에 반영되지 않음 - 암호문 블록 삭제/교체에 의한 평문 변조 불가 | - 암호화 시 병렬 처리 불가능 | 권장 |
비대칭 암호화 방식
- 비대칭 암호화 방식의 필요성
- 대칭 암호화 방식의 '키 배포' 문제에 대한 보완책 필요

디피-헬만 키 교환(Diffie–Hellman Key Exchange)
- 소수(prime)를 이용한 나머지(mod) 연산을 사용
→ 통신을 통해 미리 배포되지 않은 새로운 비밀키 s를 공유하는 것이 목표
- 과정
① 공개된 숫자 p와 g 설정
② 앨리스 : 자신의 개인키 a에서 숫자 A를 계산
③ 밥 : 자신의 개인키 b에서 B를 계산
④ 앨리스 : 밥이 전달한 B와 자신의 개인키 a를 이용하여 비밀키 s를 계산
⑤ 밥 : 앨리스가 보내준 A와 자신의 개인키 b를 이용하여 비밀키 s를 계산

🔐 디피-헬만 키 교환 절차 요약
-
공개 설정
- 공개 소수 p=23, 생성자 g=5
- 앨리스의 개인키 a=6, 밥의 개인키 b=15
-
앨리스의 계산
- A=gamodp=56mod23=8
-
밥의 계산
- B=gbmodp=515mod23=19
-
앨리스의 비밀키 계산
- S=Bamodp=196mod23=2
-
밥의 비밀키 계산
- S=Abmodp=815mod23=2
✅ 결과: 두 사람의 비밀키가 동일하게 2로 일치!
→ 제3자 없이 안전하게 공통 비밀키 공유 성공
RSA
비대칭 암호화 방식 1
- 공개키로 암호화, 개인키로 복호화
- 암호화된 메시지는 밥의 개인키로만 복호화 가능 → 기밀성 보장
- 다른 사람이 중간에서 도청할 수 없음
- 중간에서 편지를 가로챘더라도 암호화된 메시지를 복호화할 수 없음

비대칭 암호화 방식 2
- 개인키로 암호화, 공개키로 복호화
- 앨리스의 공개키로만 복호화 가능 → 부인 방지 (Non-repudiation) 보장
- 앨리스가 자신이 보낸 사실을 ‘부인’ → 앨리스의 공개키로만 복호화가 가능하다는 사실을 통해 ‘부인’에
대한 증거를 제시
- 전자상거래에서 기업 간 주문이나 계약에서도 중요한 법적 증거 역할


🔐 RSA 키 생성 과정
✅ Step 1. 큰 소수 p와 q를 선택해 곱한다
-
두 개의 서로 다른 큰 소수 p, q를 랜덤하게 선택
-
이 둘을 곱해서 N을 만든다:
-
이 N은 나중에 공개키와 개인키에 공통으로 사용됨
-
예시:
p=61, q=53⇒N=61×53=3233
✅ Step 2. 오일러 피 함수(ϕ) 계산
✅ Step 3. 공개키 지수 e 선택
-
1<e<ϕ(N) 사이의 수 중에서
ϕ(N)과 서로소인 수를 하나 선택
→ 서로소: 최대공약수가 1인 수
-
이 e는 나중에 **공개키 (e, N)**로 사용됨
-
예시:
e=19 (780과 서로소)
✅ Step 4. 개인키 d 계산
-
d는 e×d≡1modϕ(N)을 만족하는 수
-
즉, d는 **e**의 모듈로 역원 (modular inverse)
-
유클리드 알고리즘으로 계산하거나 직접 찾아도 됨
-
예시:
e=19, ϕ(N)=780
→ 19×d≡1mod780
→ d=739 이 만족함
🔑 최종 키 구성
-
공개키 (e, N) = (19, 3233)
→ 누구에게나 알려도 됨
-
개인키 (d, N) = (739, 3233)
→ 절대 유출되면 안 됨
🔐 RSA 암호화와 복호화 방식
✅ 공개키로 암호화하기
-
공식:
c=memodN
여기서,
- m: 평문 메시지
- e: 공개키 지수
- N: 공개 모듈러 값
- c: 암호문
-
예시:
- 평문 m=65
- 공개키 (e,N)=(19,3233)
c=6519mod3233=232
✅ 개인키로 복호화하기
-
공식:
m=cdmodN
여기서,
- c: 암호문
- d: 개인키 지수
- N: 공개 모듈러 값
- m: 복호화된 평문
-
예시:
- 암호문 c=232
- 개인키 (d,N)=(739,3233)
m=232739mod3233=65
✅ 결과 요약
- 평문 65 → 암호화 → 232
- 암호문 232 → 복호화 → 65
→ 암호화-복호화가 정확히 동작함을 확인!
이것이 RSA의 핵심 원리입니다.
그 외의 비대칭 암호화 방식들
- 타원 곡선 암호(ECC: Elliptic Curve Cryptosystems)
- RSA에 비해 키의 크기가 작으면서도 높은 보안성을 제공 가능
- RSA의 키 크기가 1,024비트인 보안 수준 → ECC: 160비트 정도 (RSA의 약 1/6)
- 암호화 및 복호화 속도가 RSA보다 빠름
- 자원 사용 효율성이 높아 작은 하드웨어(스마트카드, 휴대전화)에서도 잘 동작
- 그 외
🔒 암호화 방식 비교
| 항목 | 대칭 암호화 방식 | 비대칭 암호화 방식 |
|---|
| 키의 상호 관계 | 암호화 키 = 복호화 키 | 암호화 키 ≠ 복호화 키 |
| 안전한 키 길이 | 128비트 이상 | 2048비트 이상 |
| 키의 구성 | 비밀키 | 공개키, 개인키 |
| 예시 알고리즘 | DES, 3DES, AES | RSA, ECC |
| 제품 서비스 | 기밀성 | 기밀성, 부인 방지, 인증 |
| 목적 | 데이터 암호화 | 키 교환 |
| 단점 | 별도의 키 교환 필요 | 공개키가 노출되면 중간자 공격에 취약 |
| 암호화 속도 | 빠르다 | 느리다 |
- 대칭키: 빠르고 간단하지만 키 교환이 문제
- 비대칭키: 안전하게 키를 교환할 수 있지만 속도가 느림
전자서명
개념
- 서명(Signature)
- 누군가가 문서를 기록했다는 증거
- 역할 : 서류에 대한 인증 및 부인방지
- 일반 종이 서류 : 서명자가 손으로 직접 적은 싸인 혹은 도장이 서명 역할

개념: 개인키로 암호화, 공개키로 복호화
- 전자서명(Digital signature): 원본 메시지에 대한 해시 값을 서명자의 개인키로 암호화하는 것
- 제공 기능: 메시지에 대한 인증, 부인방지 및 메시지 무결성 검증
→ 오직 수신자의 개인키로만 복호화할 수 있기 때문에 가능

🔐 해시 함수
- 정보의 무결성(Integrity) 확인을 위한 목적으로 사용
- 입력 데이터를 고정된 길이의 해시값으로 변환
- 해시값만 보고 원래 입력을 역으로 추정하는 것은 계산상 불가능
- 동일한 입력 → 항상 동일한 해시값
- 아주 작은 입력 변화 → 완전히 다른 해시값 (눈사태 효과)
✅ 대표적인 해시 알고리즘 예시
공개키 기반 구조
- 전자서명의 약점
- 전자서명을 할 때 사용된 공개키가 정말로 송신자의 공개키인지 증명 필요
- 공개키 인증서(PKC, Public Key Certificate)
- 신뢰할 수 있는 인증 기관(CA, Certification Authority)을 이용하여 신뢰할 수 있는 안전한 공개키를 제공
- 공개키 기반 구조(PKI: Public Key Infrastructure)
- 공개키를 효과적으로 사용하여 안전한 암호화와 전자서명 기능 등을 제공하는 보안 환경(예: 인터넷 뱅킹에서
사용되는 공인인증서)

| 구성요소 | 주요 기능 |
|---|
| 인증기관(CA) | 인증 정책 수립, 인증서 관리, 인증서 폐기 목록 등록/관리/인증 Certification Authority |
| 등록기관(RA) | 사용자 신원 확인, 인증서 요구 승인, CA에 인증서 발급 요청, 인터페이스 제공 Registration Authority |
| 검증기관(VA) | 인증서 유효성 검증/확인 주체 Validation Authority |
| 인증서 폐기 목록(CRL) | 인증서 폐기 목록, 인증서 유효성 점검 Certification Revocation List |
| 디렉터리 서비스 | 인증서, 암호키에 대한 저장/검색/관리 |
| 인증서(X.509) | CA가 발행한 공개키 인증서 표준 포맷 |

인증서 사용 방법
- 공개키 인증서(Public Key Certificate): 사용자의 공개키에 사용자의 식별 정보를 추가하여 만든
일종의 전자 신분증
인증서 사용 Step 1: 인증서 등록 및 배포

인증서 사용 Step 2: 인증서 검증 및 공개키 사용

ⓐ 앨리스가 받은 밥의 공개키 확인 과정

ⓑ 인증서 연결 구조

- 검증 방법
- 발급 기관의 이름을 추적하여 최상위 기관의 인증서를 찾음
- 그 후, 발급자 서명과 상위 기관 인증서의 공개키를 이용해 인증서 해시값 검증
- 신뢰체인: 각 인증서가 서로 신뢰 관계에 있는 상태

공개키 기반 구조 - 상호인증
상호인증(Crosscertification)
- 두 인증기관이 상대방의 공개키를 서로 인증해주는 인증서를 발급하여 사용하는 것
- 상호인증서(cross-certificate)

대칭키
- 대칭키는 암호화, 복호화에 사용하는 키가 동일함
- 비대칭키 암호화 방식에 비해 속도가 빠름
- 키 교환 과정에서 탈취, 노출 등의 위험이 있음
- 통신 상대가 많아지면 전부 키를 교환해야 하기 때문에 관리할 키가 많아 짐

from cryptography.fernet import Fernet
key = Fernet.generate_key()
print(f"[key] {key}")
cipher_suite = Fernet(key)
text = b"Hello, cryptography!"
cipher_text = cipher_suite.encrypt(text)
print(f"Cipher Text: {cipher_text}")
decrypted_text = cipher_suite.decrypt(cipher_text)
print(f"Decrypted Text: {decrypted_text}")
pip install cryptography
대칭키(AES)
다음은 이미지에 나온 파이썬 AES 암호화/복호화 코드입니다:
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_EAX)
data = b"Secret Message"
ciphertext, tag = cipher.encrypt_and_digest(data)
cipher_dec = AES.new(key, AES.MODE_EAX, cipher.nonce)
decrypted_data = cipher_dec.decrypt_and_verify(ciphertext, tag)
print("Original:", data)
print("Decrypted:", decrypted_data)
pip install pycryptodome
비대칭키
- 비대칭키는 암호화, 복호화에 사용하는 키가 다름
- 송신자, 수신자 모두 한 쌍의 키(개인키, 공개키)를 갖음
- 개인키는 비공개, 공개키는 모든 사람이 접근 가능(공개)
- 키 분배 및 키 관리가 용이
- 기밀성, 무결성, 부인 방지 제공
- 키 길이가 길고, 대칭키에 비해 암호화 속도가 느림

from nacl.public import PrivateKey, Box
sk = PrivateKey.generate()
pk = sk.public_key
receiver_sk = PrivateKey.generate()
receiver_pk = receiver_sk.public_key
sender_box = Box(sk, receiver_pk)
message = b"Hello PyNaCl"
encrypted = sender_box.encrypt(message)
receiver_box = Box(receiver_sk, pk)
decrypted = receiver_box.decrypt(encrypted)
print("Encrypted message:", encrypted)
print("Decrypted message:", decrypted)
pip install pynacl
비대칭키 적용
echo_client2_py
from nacl.public import PrivateKey, PublicKey, Box
import socket
sk = PrivateKey.generate()
pk = sk.public_key
sock = socket.socket()
sock.connect(("127.0.0.1", 9999))
sock.sendall(pk.encode())
receiver_pk = PublicKey(sock.recv(1024))
sender_box = Box(sk, receiver_pk)
while True:
message = input("입력: ")
encrypted = sender_box.encrypt(message.encode())
print("[A] 암호화 메시지:", encrypted)
sock.sendall(encrypted)
if message == "end":
break
sock.close()
echo_server2_py
from nacl.public import PrivateKey, PublicKey, Box
import socket
receiver_sk = PrivateKey.generate()
receiver_pk = receiver_sk.public_key
with socket.socket() as s:
s.bind(("", 9999))
s.listen()
print("서버 대기 중...")
conn, addr = s.accept()
print("클라이언트 연결됨:", addr)
client_pk_bytes = conn.recv(1024)
client_pk = PublicKey(client_pk_bytes)
conn.sendall(receiver_pk.encode())
receiver_box = Box(receiver_sk, client_pk)
while True:
try:
encrypted = conn.recv(4096)
if not encrypted:
break
decrypted = receiver_box.decrypt(encrypted)
print("[B] 암호화 메시지:", encrypted)
print("[B] 복호화 메시지:", decrypted.decode())
if decrypted.decode() == "end":
break
except Exception as e:
print("복호화 실패:", e)
break

