Tags: crypto 

Rating: 5.0

We have two methods to handle this challenge.

1、This crypto algorithm processes 12 bits at one time with ECB mode. And the length of the ciphertext is just 6 bytes. It's feasible to brute-force 24 bits or 3 bytes at one time.

2、Following the designing art of DES, we can build up the decryption algorihm by reversing the subkey order and exchanging the left and right part of the input and the output of the fiestel network.

Following is the code,
```
def S1box(S1):
box = [[0b101, 0b010, 0b001, 0b110, 0b011, 0b100, 0b111, 0b000],
[0b001, 0b100, 0b110, 0b010, 0b000, 0b111, 0b101, 0b011]]
return box[(S1>>3) & 0x1][S1 & 0x7]

def S2box(S2):
box = [[0b100, 0b000, 0b110, 0b101, 0b111, 0b001, 0b011, 0b010],
[0b101, 0b011, 0b000, 0b111, 0b110, 0b010, 0b001, 0b100]]
return box[(S2>>3) & 0x1][S2 & 0x7]

def getsubkey(key, index, n):
result = 0
for i in range(8):
tmp = (((key >> ((n - 1 - (index + i)) % n) & 1) << (7 - i)))
result = result | tmp
return result

def encrypt(s, key, offset):
#1, Choose a plaintext that is divisible into 12bit 'blocks'
plain = s

#2, Choose a key at least 8bits in length

#3, For each block from i=0 while i<N perform the following operations
# Currently no padding
N = len(plain) * 8 / 12
R = 2
result = 0
pint = int(plain.encode('hex'), 16)
for i in range(N):
#4, Repeat the following operations on block i, from r=0 while r<R
p = (pint >> (12 * (N - 1 - i))) & 0xfff
Lr = (p >> 6) & 0x3f
Rr = p & 0x3f
for r in range(R):
tmp = ((Rr << 2) & 0xc0) | (Rr & 0x3) | ((Rr & 0x8) << 1) | ((Rr & 0x8) >> 1) | ((Rr & 0x4) << 1) | ((Rr & 0x4) << 3)
subkey = getsubkey(int(key.encode('hex'),16), (i + offset) * R + r, len(key) * 8)
tmp = tmp ^ subkey
S1 = (tmp >> 4) & 0xf
S2 = tmp & 0xf
tmp = (S1box(S1) << 3) | S2box(S2)
tmp = tmp ^ Lr
Lr = Rr
Rr = tmp
result = (result << 12) | ((Lr << 6) | Rr)
return getstr(result)

def decrypt(s, key):
plain = s
N = len(plain) * 8 / 12
R = 2
result = 0
pint = int(plain.encode('hex'), 16)
for i in range(N):
p = (pint >> (12 * (N - 1 - i))) & 0xfff
Rr = (p >> 6) & 0x3f # exchange the left and right part
Lr = p & 0x3f
for r in range(R - 1, -1, -1): # reverse subkey order
tmp = ((Rr << 2) & 0xc0) | (Rr & 0x3) | ((Rr & 0x8) << 1) | ((Rr & 0x8) >> 1) | ((Rr & 0x4) << 1) | ((Rr & 0x4) << 3)
subkey = getsubkey(int(key.encode('hex'),16), i * R + r, len(key) * 8)
tmp = tmp ^ subkey
S1 = (tmp >> 4) & 0xf
S2 = tmp & 0xf
tmp = (S1box(S1) << 3) | S2box(S2)
tmp = tmp ^ Lr
Lr = Rr
Rr = tmp
result = (result << 12) | ((Rr << 6) | Lr) # exchange the left and right part
return getstr(result)

def getstr(intnum):
intstr = hex(intnum).lstrip('0x').rstrip('L')
if len(intstr) % 2 == 1:
intstr = '0' + intstr
intstr = intstr.decode('hex')
return intstr

enc = 0b011001010010001010001100010110000001000110000101
encstr = getstr(enc)

import string, itertools, time

# method 1, brute force
flag = ''
time1 = time.time()
for i in range(2):
for item in itertools.product(string.printable, repeat=3):
if encrypt(''.join(item), 'Mu', i * 2) == getstr((enc >> (1 - i) * 24) & 0xffffff):
flag += ''.join(item)
break
time2 = time.time()
print 'method 1: flag is Gigem{' + flag + '}, passed time:', time2 - time1

# method 2, decrypt
time1 = time.time()
flag = decrypt(encstr, 'Mu')
time2 = time.time()
print 'method 2: flag is Gigem{' + flag + '}, passed time:', time2 - time1

```

Output ,
```
C:\>python solver.py
method 1: flag is Gigem{MiN0n!}, passed time: 25.5679998398
method 2: flag is Gigem{MiN0n!}, passed time: 0.00100016593933
```

hacker-jakFeb. 27, 2018, 10:03 p.m.

Very cool. I like that you gave two potential solutions.
I developed this challenge, and I am glad you posted both methods.