AES 암호화 알고리즘
- Advanced Encryption Standard, 블록 암호
- Block Size: 16 Bytes
- Key size: 16 Bytes(AES128) / 24 Bytes(AES192) / 32 Bytes(AESE256)
- plaintext 길이가 16*k보다 작을 때 padding을 붙인다
* 주로 PKCS#7 표준을 사용함 (padding 6개 필요하면 b'\x06'으로 채우고, 2개 필요하면 b'\x02'로 채우는)
* plaintext 길이가 16의 배수이면 padding b'\x10'*16 을 뒤에 붙인다
- plaintext의 길이가 block size보다 클 때 어떻게? => mode of operation
* ECB(Electronic CodeBook) mode
- 보안 취약점으로 사용 거의 안 함
* CBC(Cipher Block Chaining) mode
- IV 필요, 제일 많이 사용되는 mode
* OFB(Output FeedBack) mode
- PT와 xor되는 key stream을 생성하는 느낌
* CTR(CounTeR) mode
바이트 <-> 16진수 변환 유용한 파이썬 함수
* 바이트 문자열 <-> 16진수 문자열
original_bytes = b'hello'
hex_string = original_bytes.hex() # '68656c6c6f'
converted_bytes = bytes.fromhex(hex_string) # b'hello'
* 바이트 문자열 <-> 16진수 바이트 문자열
from binascii import hexlify, unhexlify
original_bytes = b'hello'
hex_bytes = hexlify(original_bytes) # b'68656c6c6f'
converted_bytes = unhexlify(hex_bytes) # b'hello'
// p.recv() 하면 바이트 문자열로 받아와지므로 unhexlify가 편하더라
취약점 분석 예제 1 - ECB
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
key = open("/dev/urandom", "rb").read(16)
secret = open("secret.txt").read(32).encode() # 32bit key
while True:
try:
prefix = bytes.fromhex(input("Prefix(hex): "))
if len(prefix) > 64: # <= 64bit prefix
raise Exception
except:
print("Wrong prefix.")
continue
cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(prefix + secret, 16))
print("Encrypted Secret: ", ciphertext.hex())
- AES 암호화 알고리즘의 ECB mode로 암호화된다
- 각 블록별로 동일한 key를 가지고 암호화된다, PT에서 동일한 블록 = CT에서도 동일한 블록
- prefix 63바이트로 구성한다 = b'A'*31 + [추측값] + b'A'*31
- 처음 32바이트(b'A'*0x31+추측값)와 이후 32바이트(b'A'*31+비밀값의 첫바이트)의 암호화된 값을 비교해 leak 한다
- prefix를 63 ~ 32바이트까지 변화시키면서 secret 값 한 바이트씩 leak 한다
from binascii import hexlify, unhexlify
from pwn import *
p = remote("aes.sstf.site", 1335)
secret = b''
while len(secret)<32:
for i in range(256):
ch = bytes([i])
payload = secret.rjust(31,b'A') + ch
payload += b'A' * (31-len(secret))
p.recvuntil("Prefix(hex): ")
p.sendline(hexlify(payload))
p.recvuntil("Encrypted Secret: ")
ct = unhexlify(p.recvline()[:-1])
if ct[0:32] == ct[32:64]:
secret += ch
print(secret)
break
p.interactive()
취약점 분석 예제 2 - OFB
from Crypto.Cipher import AES
with open("/dev/urandom", "rb") as f:
key, iv = f.read(16), f.read(16)
secret = open("secret.txt").read(48).encode()
cipher = AES.new(key, AES.MODE_OFB, iv)
ciphertext = cipher.encrypt(secret)
print("Encrypted Secret: ", (iv + ciphertext).hex())
try:
iv = bytes.fromhex(input("IV(hex): "))
if len(iv) != 16:
raise Exception
msg = bytes.fromhex(input("Message(hex): "))
except:
print("Wrong input.")
exit()
cipher = AES.new(key, AES.MODE_OFB, iv)
ciphertext = cipher.encrypt(msg)
print("Encrypted Message: ", ciphertext.hex())
- 16B IV 값과 48B CT 값이 제공된다 (unhexlify만 하면 되겠네)
- 원하는 IV와 PT를 입력하여 CT를 1회 얻을 수 있다 // 동일한 key를 사용해 암호화 해준다
- secret 암호화에 사용된 IV와 48바이트 길이의 널바이트를 PT로 넣어주면(PT1=PT2=PT3=0), x xor 0 = x 이므로 생성된 CT를 통해 키 스트림 K1, K2, K3를 파악할 수 있다
- 각각의 키스트림과 CT1, CT2, CT3 의 xor 연산을 통해 secret 문자열을 구할 수 있겠다
from binascii import hexlify, unhexlify
from pwn import *
p = remote("aes.sstf.site", 1336)
p.recvuntil("Encrypted Secret: ")
res = unhexlify(p.recvline()[:-1])
iv = res[0:16] # 16B
ct = res[16:] # 48B
p.recvuntil("IV(hex): ")
p.sendline(hexlify(iv))
p.recvuntil("Message(hex): ")
p.sendline(hexlify(b'\x00'*48))
p.recvuntil("Encrypted Message: ")
keystream = unhexlify(p.recvline()[:-1])
secret = b''
for c, s in zip(ct,keystream):
secret += bytes([c^s])
print(secret)
취약점 분석 예제 3 - CBC
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from secret import key, flag
while True:
try:
iv = bytes.fromhex(input("IV(hex): "))
if len(iv) != 16:
raise Exception
msg = bytes.fromhex(input("CipherText(hex): "))
if len(msg) % 16:
raise Exception
except:
print("Wrong input.")
continue
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(msg)
try:
plaintext = unpad(plaintext, 16)
except: # check padding - vulnerable
print("Try again.")
continue
if plaintext == b"CBC Magic!":
print(flag)
break
else:
print("Wrong CipherText.")
- 원하는 IV와 CT를 입력받아 복호화를 진행한다
- 패딩이 올바르지 않은 경우 "Try again" 출력하고, 복호화된 값이 "CBC Magic!"과 같으면 플래그 출력 및 성공
Oracle Padding Attack
- 블록 암호 모드 중 padding을 필요로 하는 mode에서 발생하는 공격 기법
- 복호화시스템에 암호문을 넣었을 때 padding 형식이 맞는지 틀린지 유무를 응답해주는 경우 특정 plaintext를 도출하는 IV를 구하거나, 특정 IV로부터 plaintext를 도출할 수 있다.
대략적인 개념은 다음과 같다 (아래 쉬운설명 링크부터 보고 오는게 좋을듯)
- 어차피 encryption algorithm과 key는 모른다. 어떠어떠한 알고리즘에 의해 CT가 암호화되어 Intermediate Plaintext(IP)로 변해있을 것이다.
- IP의 값만 알면 PT로부터 IV를 구하거나, IV로부터 PT를 구할 수 있다
- PT = IP(고정된 값이다) xor IV 이므로 IV의 값에 따라 복호화시스템의 응답이 달라질 것이다
(padding 형식이 틀림 / padding 형식은 맞는데 원래 PT랑 다름 / padding 형식도 맞고 원래 PT랑도 같음)
=> blind SQLI 처럼 응답을 통해 IP를 LSB부터 한 바이트씩 구해나가자
- IV의 값을 LSB부터 한 바이트씩 0~255 브포 돌려가며 "padding 형식이 맞음" 응답이 나오는 케이스를 찾는다
=> padding 형식이 맞다는 것은 \x01, \x02\x02, \x03\x03\x03 ... 이런 형태라는 것.
=> 즉 끝에서 i개의 바이트가 \x0i\x0i ... \x0i 형태라는 것
cf) 여기서 든 의문
IV 맨 마지막 바이트 \x53에서 padding 형식 맞게 나왔다고 해보자.
근데 이게 ~~~\x01 로 성공한건지 ~~~\x02\x02 로 성공한건지 어케 알지
왜 PT의 마지막바이트가 \x01임을 확신하지
=> 내가 내린 결론: 그래서 IV payload를 구성할 때 보통 널로 나머지를 채워서 공격하나?
=> "IV[17-i] = 브포로 구한 값" 과 "PT[17-i] = '\x{i}' " 을 xor 하면 IT[17-i] 값을 구할 수 있다.
=> i = 1 ~ 16 에 대해 반복
=> 각 반복마다 IT 한 바이트씩 구하고, 구한 IT를 기반으로 IV를 재구성한다
쉬운 설명은
여기 보세요
확장: 여러 블록이고 주어진 PT를 바탕으로 IV & CT 구해야 한다면?
- CT_n으로 임의의 값 사용, 맨 오른쪽 블록부터 IP_n 을 구한 뒤 PT_n을 바탕으로 CT_n-1을 구한다
- CT_n-1로 구한 값 사용, IP_n-1 구한 뒤 PT_n-1을 바탕으로 CT_n-2 구한다
...
- CT_1로 구한 값 사용, IP_1 구한 뒤 PT_1 바탕으로 IV 구한다
// CT_n만 임의의 값 사용, 그 뒤로 CT_n-1 차례차례 구해나가고 IV도 구한다
확장: 여러 블록이고 주어진 CT, IV 바탕으로 PT 구해야 한다면?
- 얘는 순서 상관 없음
- 맨 오른쪽 블록에서 IP_n 구한 뒤 CT_n-1 바탕으로 PT_n 구한다
- IP_n-1 구한 뒤 CT_n-2 바탕으로 PT_n-1 구한다
...
- IP_1 구한 뒤 IV 바탕으로 PT_1 구한다
문제 풀이로 돌아와서
- 다행히 블록 한 개이고, PT = b'CBC Magic!\x06\x06\x06\x06\x06\x06' 으로 주어져있다
- CT = b'AAAAAAAAAAAAAAAA' 으로 고정시키고 (아무 값이나 상관없음)
- IV = b'\x00'*0xf + [브포 값] 부터 시작해서 IP를 한 바이트씩 구해나가면 되겠다
from pwn import *
from binascii import hexlify, unhexlify
p = remote("aes.sstf.site", 1337)
IP = b''
CT = b'A'*16 # Anything
PT = b'CBC Magic!' + b'\x06'*6
for i in range(1,17):
pay = b'\x00'*(16-1-len(IP))
for ch in range(256):
IV = pay + bytes([ch]) + bytes([ c^i for c in IP ])
p.recvuntil("IV(hex): ")
p.sendline(hexlify(IV))
p.recvuntil("CipherText(hex): ")
p.sendline(hexlify(CT))
if b"Try again" not in p.recvline(): # padding error
IP = bytes([ch^i]) + IP
print(len(IP))
break
IV = bytes([ pt^ip for pt, ip in zip(PT,IP) ])
p.recvuntil("IV(hex): ")
p.sendline(hexlify(IV))
p.recvuntil("CipherText(hex): ")
p.sendline(hexlify(CT))
p.interactive()
'security > 암호학' 카테고리의 다른 글
확장된 유클리드 호제법 구현 in python (0) | 2023.08.20 |
---|---|
RSA 암호화 알고리즘 정리 및 예제 (feat SSTF 2023) (0) | 2023.08.20 |
RC4 암호화 알고리즘 정리 및 예제 (feat SSTF2023) (0) | 2023.08.20 |
[Blitzs CTF] d + 파이썬 itertools!! (0) | 2023.06.25 |
[Dreamhack CTF] 3-Cipher : Caesar, RSA, AES (0) | 2023.06.24 |