Некоторые режимы блочных шифров, такие как OFB, CTR или CFB, превращают блочный шифр в потоковый. Идея потоковых шифров заключается в том, чтобы генерировать псевдослучайный ключевой поток (keystream), который затем XOR‘ится с открытым текстом. Одно из преимуществ потоковых шифров — они могут работать с данными произвольной длины, без необходимости добавления паддинга.

OFB — довольно редкий режим шифрования, который сегодня не имеет существенных преимуществ по сравнению с CTR. В этом задании рассматривается необычное свойство режима OFB.

Исходный код

from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/symmetry/encrypt/<plaintext>/<iv>/')
def encrypt(plaintext, iv):
    plaintext = bytes.fromhex(plaintext)
    iv = bytes.fromhex(iv)
    if len(iv) != 16:
        return {"error": "IV length must be 16"}

    cipher = AES.new(KEY, AES.MODE_OFB, iv)
    encrypted = cipher.encrypt(plaintext)
    ciphertext = encrypted.hex()

    return {"ciphertext": ciphertext}


@chal.route('/symmetry/encrypt_flag/')
def encrypt_flag():
    iv = os.urandom(16)

    cipher = AES.new(KEY, AES.MODE_OFB, iv)
    encrypted = cipher.encrypt(FLAG.encode())
    ciphertext = iv.hex() + encrypted.hex()

    return {"ciphertext": ciphertext}

Что такое OFB

Это режим использования блочного шифра в качестве синхронного потокового шифра. Похож на CFB за исключением того, что n битов предыдущего выходного блока сдвигаются в крайние правые позиции очереди. В OFB шифрование и дешифровка идентичны — это и есть «симметрия» из названия задачи.

OFB scheme

Проще говоря, мы получаем поток данных, с которыми нам нужно XOR‘ить открытый текст.

Анализ

В шифротекст добавляется iv:

ciphertext = iv.hex() + encrypted.hex()

Сам по себе это нормальная практика — без iv получатель не расшифрует. Проблема в другом: функция encrypt() принимает произвольный iv и открытый текст с тем же ключом. Это позволяет нам передать полученный шифротекст обратно в encrypt() с тем же iv — и получить открытый текст, потому что в OFB повторный XOR с тем же keystream отменяет шифрование: A ^ K ^ K = A (подробнее о свойствах XOR — в лабе XOR Properties).

Реализация

Реализую обёртки для API, которые буду использовать для получения шифротекста и шифрования:

import requests

URL = 'http://aes.cryptohack.org/symmetry'


def encrypt_flag():
    url = f'{URL}/encrypt_flag/'
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()['ciphertext']

    return ''


def encrypt(plaintext: bytes, iv: bytes) -> str:
    url = f'{URL}/encrypt/{plaintext.hex()}/{iv.hex()}/'
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()['ciphertext']

    return ''

Теперь нужно извлечь iv и зашифровать текст ещё раз:

import requests

URL = 'http://aes.cryptohack.org/symmetry'


def encrypt_flag():
    url = f'{URL}/encrypt_flag/'
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()['ciphertext']

    return ''


def encrypt(plaintext: bytes, iv: bytes) -> str:
    url = f'{URL}/encrypt/{plaintext.hex()}/{iv.hex()}/'
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()['ciphertext']

    return ''


def main():
    raw = bytes.fromhex(encrypt_flag())
    iv, ciphertext = raw[:16], raw[16:]

    flag = bytes.fromhex(encrypt(ciphertext, iv)).decode()
    print(flag)

if __name__ == '__main__':
    main()

Запущу:

cu63:Symmetry/ $ python solver.py
crypto{0fb_15_5ymm37r1c4l_!!!11!}