LoginSignup
1
0

More than 3 years have passed since last update.

Home Rolled Crypto(Writeup)

Last updated at Posted at 2021-04-11

問題

https://2021.angstromctf.com/challenges

Aplet made his own block cipher! Can you break it?
nc crypto.2021.chall.actf.co 21602

source
#!/usr/bin/python
import binascii
from random import choice

class Cipher:
    BLOCK_SIZE = 16
    ROUNDS = 3
    def __init__(self, key):
        assert(len(key) == self.BLOCK_SIZE*self.ROUNDS)
        self.key = key

    def __block_encrypt(self, block):
        enc = int.from_bytes(block, "big")
        for i in range(self.ROUNDS):
            k = int.from_bytes(self.key[i*self.BLOCK_SIZE:(i+1)*self.BLOCK_SIZE], "big")
            enc &= k
            enc ^= k
        return hex(enc)[2:].rjust(self.BLOCK_SIZE*2, "0")


    def __pad(self, msg):
        if len(msg) % self.BLOCK_SIZE != 0:
            return msg + (bytes([0]) * (self.BLOCK_SIZE - (len(msg) % self.BLOCK_SIZE)))
        else:
            return msg

    def encrypt(self, msg):
        m = self.__pad(msg)
        e = ""
        for i in range(0, len(m), self.BLOCK_SIZE):
            e += self.__block_encrypt(m[i:i+self.BLOCK_SIZE])
        return e.encode()

key = binascii.unhexlify("".join([choice(list("abcdef0123456789")) for a in range(Cipher.BLOCK_SIZE*Cipher.ROUNDS*2)]))

with open("flag", "rb") as f:
    flag = f.read()

cipher = Cipher(key)


while True:
    a = input("Would you like to encrypt [1], or try encrypting [2]? ")
    if a == "1":

        p = input("What would you like to encrypt: ")
        try:
            print(cipher.encrypt(binascii.unhexlify(p)).decode())
        except:
            print("Invalid input. ")
    elif a == "2":
        for i in range(10):
            p = "".join([choice(list("abcdef0123456789")) for a in range(64)])
            print("Encrypt this:", p)
            e = cipher.encrypt(binascii.unhexlify(p)).decode()
            c = input()
            if e != c:
                print("L")
                exit()
        print("W")
        print(flag.decode())            

    elif a.lower() == "quit":
        print("Bye")
        exit()
    else:
        print("Invalid input. ")

解法

sourceの解釈

16バイト毎に、3ラウンドの暗号化を行う
↑を実現させるために48バイトの鍵をランダムに生成
暗号化するメッセージはパディングされる
暗号化はビット毎に行われる

考え方

xorとandを3度繰り返していることから、暗号文から元の秘密鍵を見つけることはできない。
しかし、ビット毎に{0,1}->{0,1}の暗号化が分かれば手元で同じ暗号化の結果が得られる。
64バイトの平文に対する暗号文を当てればフラグが得られるので、
64バイトの0b0...0,0b1...1を質問し、得られた暗号文テーブルを用いて手元で暗号化を行う。
 

コード

solution
from pwn import *
from base64 import b64decode, b64encode

BLOCK_SIZE = 16
ROUNDS = 3
io = remote("crypto.2021.chall.actf.co",21602)

table=[]

for b in ("00","ff"):
    io.sendlineafter("[2]? ", "1")
    io.sendlineafter("encrypt: ", b*BLOCK_SIZE*2)
    rcv = io.recvline().decode().strip()
    i = int(rcv,16)
    print('{:0256b}'.format(i))
    table.append('{:0256b}'.format(i))

io.sendlineafter("[2]? ", "2")
for _ in range(10):
    io.recvuntil("this:")
    rcv = io.recvline().decode().strip()
    i = int(rcv,16)
    ans = ""
    for i, c in enumerate('{:0256b}'.format(i)):
        ans += table[int(c)][i]
    rcv=('{:064x}'.format(int(ans,2)))
    io.sendline(rcv)

io.recvline()
print(io.recvline().decode().strip()) # flag

ans
[+] Opening connection to crypto.2021.chall.actf.co on port 21602: Done
1100101000100101000000000011011100001101010011010001100001010111101010001000110010000000010111000101100101000011000001100001000011001010001001010000000000110111000011010100110100011000010101111010100010001100100000000101110001011001010000110000011000010000
0000101000000101000000000001011000001100010001010000000001010101101000001000010000000000010011000101100101000010000001100001000000001010000001010000000000010110000011000100010100000000010101011010000010000100000000000100110001011001010000100000011000010000
actf{no_bit_shuffling_is_trivial}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0