이 문제는 OTP와 관련된 문제이다.
OTP는 인터넷 뱅킹에서 주로 쓰이는 One-Time Password라는 말도 있고..
암호학 책을 읽다보면 One-Time Pad라는 말도 있는데
One Time Password는 TOTP(Timebased) HOTP(HMAC based)로 나뉜다. TOTP는 시간을 기반으로 일회용 비밀번호를 생성하고 HOTP는 해시 함수를 이용하여 생성하는데
One Time Pad는 그냥 평문의 길이와 키의 길이가 같고 일회용으로 키가 사용되며 xor챕터에서 주로 나왔던 걸로 기억한다.
정의상 이번 문제에서는 One Time Password 같긴 한데 cryptohack에서는 OneTime Pad라고 말하니까 이것도 맞나보다.
분석
#!/usr/bin/env python3
import time
from Crypto.Util.number import long_to_bytes
import hashlib
from utils import listener
FLAG = b'crypto{????????????????????}'
def generate_key():
current_time = int(time.time())
key = long_to_bytes(current_time)
return hashlib.sha256(key).digest()
def encrypt(b):
key = generate_key()
assert len(b) <= len(key), "Data package too large to encrypt"
ciphertext = b''
for i in range(len(b)):
ciphertext += bytes([b[i] ^ key[i]])
return ciphertext.hex()
class Challenge():
def __init__(self):
self.before_input = "Gotta go fast!\n"
def challenge(self, your_input):
if not 'option' in your_input:
return {"error": "You must send an option to this server"}
elif your_input['option'] == 'get_flag':
return {"encrypted_flag": encrypt(FLAG)}
elif your_input['option'] == 'encrypt_data':
input_data = bytes.fromhex(your_input['input_data'])
return {"encrypted_data": encrypt(input_data)}
else:
return {"error": "Invalid option"}
"""
When you connect, the 'challenge' function will be called on your JSON
input.
"""
listener.start_server(port=13372)
서버와 관련된 코드를 주는 것 같다.
사실 처음에 nc부터 해서 뭣도 모르고 이것저것 넣어보고 (심지어 .json파일 만들고 경로도 넣어봄)
입력이 {"option":"get_flag"} 이렇게 json 형식대로 입력해야 한다.
generate_key에서는 현재 시간을 입력받고 그 시간을 시드로 하여 hash값을 생성한다.
encrypt에서는 키를 생성하고 xor로 암호화한다.
우리가 option으로 get_flag를 받으면 해당 시간을 시드로 하여 암호화된 flag를 받을 수 있고
같은 초 내에(시간을 int로 하였기 때문) 해당 flag를 다시 encrypt하면 xor의 자기반전 속성에 의해 되돌아온다.
json을 처리하는 거에 대해서 고민을 하기도 했는데 그냥 gpt한테 물어보면서 했다.
그리고 최근에 치환암호 해독해보려고 json을 다뤄서 조금 나은 듯.
풀이
import time
from Crypto.Util.number import long_to_bytes
import hashlib
from pwn import *
import json
def generate_key():
current_time = int(time.time())
key = long_to_bytes(current_time)
return hashlib.sha256(key).digest()
def encrypt(b):
key = generate_key()
assert len(b) <= len(key), "Data package too large to encrypt"
ciphertext = b''
for i in range(len(b)):
ciphertext += bytes([b[i] ^ key[i]])
return ciphertext.hex()
p = remote('socket.cryptohack.org', 13372)
p.recvuntil(b'!\n')
payload = b'{"option":"get_flag"}'
p.sendline(payload)
get_flag = json.loads(p.recvline())['encrypted_flag']
payload = '{"option":"encrypt_data", "input_data":"'+get_flag+'"}'
p.sendline(payload)
flag = json.loads(p.recvline())['encrypted_data']
flag = bytes.fromhex(flag)
print(flag)
포너블 잘하면 다른 것도 잘한다는게 맞는 듯.
그냥 문자열로 해서 입력을 json 형식으로 넣고
받은 것만 json.loads를 사용해서 딕셔너리로 만들었다.