Tags: cryptography
Rating: 0
# HalfTimePad
## Desc
There is a hidden message in this service that contains flag.
Here is the leaked source code of program: [cipher.go]()
ez pz!
```bash
curl halftimepad.roboepics.com
```
## Solution
This is what leaked `cipher.go` does: it has a function called `HalfTimePad` which its objective is to encrypt global `plain` variable by XORing it with a genereted byte string for each request. The byte string is being generated by choosing random bytes for random length (between 20 and 100) and cycling it until it has same length as the plain text.
```
----------------------------------------
| Plain Text |
----------------------------------------
----------------------------------------
| key | key | key | key | key | key | k|
----------------------------------------
(XOR) ========================================
----------------------------------------
| Cipher Text |
----------------------------------------
```
Notice that we know the plain text contains the flag and the flag contains `xeroctf{` and if we know the length of the key (`l`) and index of `xeroctf{` we could obtain a part of the key by XORing cipher text with `xeroctf{`, hence we could obtain parts of plain text. These parts should be meaningful and of course printable. So we can find `l` and `i` by bruteforcing all possible cases.
```
,_ i _,
`v'
----------------------------------------
Plain: | xeroctf{ |
----------------------------------------
----------------------------------------
Key: | #found## | #found## | ###|
----------------------------------------
(XOR) ========================================
----------------------------------------
Cipher: | #found## #found## #fo|
----------------------------------------
```
Here is a python script for finding candidate `(l, i)`s:
```python
import base64
import string
# Sample cipher
cipher = """"x2jJ4MpMB7EwWkl61QHoywcmK5V0TrxsbZykIyHZB6FCWv07K01lTZWgNczh6Gv0uaICnV3Yn4MVk3Fvl5AQPpBaecutA9ddc8EV0ytvUPCpt50AFYHtz9K+/hDLgFs2/PKvMoz/8A8MujZaCGiJYuXAETAl9nMGoG1tk7tiJ8NUuEpY/j1/Wy8xmaJ5143WTZ2dgiLua//NgFScXGPEvRJojR12hb0+4XEAviP4VF1Im+nqkxRbhKHR0qvpSZ+QUHnj+K84l7+OBQ2hNAgddM4brdEGLXG/YQa5djmBu2Zgmx20D0f+LG4TXhaX7nyYpvh2urPsD8lHntqYWodkYpboKnWzXHiP+SfLTTTLI61VXWrOmQ=="""
cipher = base64.b64decode(cipher)
flag_header = b"xeroctf{"
def wrapping(lst, n):
return [lst[i:i+n] for i in range(0, len(lst), n)]
def decipher(key, cipher):
d = b''
for i in range(len(cipher)):
d += bytes([cipher[i] ^ key[i%len(key)]])
return d
def isprintable(s):
return all(c < 127 and chr(c) in string.printable for c in s)
for n in range(20, 101):
for i in range(len(cipher)-len(flag_header)):
part = cipher[i:i+len(flag_header)]
dekey = bytes([part[j] ^ flag_header[j] for j in range(len(part))])
key = [0] * n
ikey = set([])
for j in range(len(dekey)):
index = (j + i) % n
key[index] = dekey[j]
ikey.add(index)
txt = decipher(key, cipher)
ws = wrapping(txt, n)
f = True
for w in ws:
for k in ikey:
if not isprintable(w[k:k+1]):
f = False
if f:
for w in ws:
s = "".join([chr(w[i]) for i in ikey if i < len(w)])
print(s)
```
By checking candidates, we will find that `i = 129`.
Now considering `crypto.go` chooses random `l` between 20 and 100, for every `l` we can extract distinct places of the plain text by almost the same process:
```
,_ i _,
`v'
----------------------------------------
Plain: |#found2#nd1# xeroctf{ #fo|
----------------------------------------
----------------------------------------
Key1: | #found1# | #found1# | ###|
----------------------------------------
----------------------------------------
Key2: |#found2# |#found2# |
----------------------------------------
. . .
. . .
. . .
```
```python
plain = None
while True:
cipher = input().strip()
cipher = base64.b64decode(cipher)
if plain is None:
plain = ['?'] * len(cipher)
for n in range(20, 100):
i = 129
part = cipher[i:i+len(flag_header)]
dekey = bytes([part[j] ^ flag_header[j] for j in range(len(part))])
key = [0] * n
ikey = set([])
for j in range(len(dekey)):
index = (j + i) % n
key[index] = dekey[j]
ikey.add(index)
txt = decipher(key, cipher)
ws = wrapping(txt, n)
f = True
for w in ws:
for k in ikey:
if not isprintable(w[k:k+1]):
f = False
if not f:
continue
changed = False
pplain = plain[:]
for j in range(len(pplain)):
if pplain[j] != '?':
continue
if j % n in ikey:
pplain[j] = chr(txt[j])
changed = True
if changed:
print("".join(pplain))
print("Accept?")
if input() in ["y", "Y"]:
plain = pplain
```
It's enough to extract the first 100 bytes of the plain text, so we can reveal the key completely.