Загрузка данных
import os
def read_file(filepath):
with open(filepath, 'rb') as f:
return f.read()
def xor_bytes(data, key):
"""Побайтовый XOR"""
return bytes([d ^ k for d, k in zip(data, key * (len(data) // len(key) + 1))])
def decrypt():
# Читаем зашифрованный файл (он в hex)
hex_content = read_file('output.txt').decode('utf-8').strip()
# Превращаем hex обратно в байты
encrypted_data = bytes.fromhex(hex_content)
known_plaintext = b"DUCKERZ{"
print(f"Размер зашифрованных данных: {len(encrypted_data)} байт")
print(f"Известное начало: {known_plaintext}")
print("-" * 50)
# Перебираем возможные длины ключа от 12 до 44
for key_size in range(12, 45):
# Проверяем, делится ли размер данных на key_size (так как данные повторялись)
# В enc.py: len(expanded_data) = len(input_data) * key_size
# Значит, len(encrypted_data) должно делиться на key_size без остатка
if len(encrypted_data) % key_size != 0:
continue
# Вычисляем предполагаемую длину исходного файла
original_length = len(encrypted_data) // key_size
# Берем первый блок зашифрованных данных длиной original_length
# (так как данные повторялись, первый блок содержит весь исходный текст)
first_block = encrypted_data[:original_length]
# Восстанавливаем ключ, используя известный текст
# Ключ = Зашифрованный_байт XOR Исходный_байт
# Нам нужно ровно len(known_plaintext) байт ключа
if len(first_block) < len(known_plaintext):
continue
key_part = bytes([first_block[i] ^ known_plaintext[i] for i in range(len(known_plaintext))])
# Теперь нам нужно восстановить весь ключ.
# Мы знаем, что ключ повторялся.
# Если key_size > len(known_plaintext), нам нужно найти остальные байты ключа.
# Но мы можем просто расшифровать первый блок, используя key_part,
# повторенный до нужной длины.
# Восстанавливаем полный ключ:
# Так как key_part - это первые 8 байт ключа, а ключ имеет длину key_size,
# нам нужно найти остальные key_size - 8 байт.
# Мы можем сделать это, зная, что исходный текст - это текст (например, ASCII).
# Но проще: XOR первого блока с key_part даст нам исходный текст.
# Однако, если key_size > 8, key_part не покроет весь ключ.
# Давайте просто расшифруем первый блок, используя key_part,
# циклически повторенный.
# Но это сработает только если key_size <= len(known_plaintext).
# Если key_size > 8, нам нужно восстановить остальную часть ключа.
# Альтернативный подход: мы знаем, что исходный текст - это ASCII.
# Мы можем перебрать оставшиеся байты ключа, чтобы получить читаемый текст.
# Но это сложно.
# Более простой способ:
# 1. Восстанавливаем первые 8 байт ключа.
# 2. Если key_size > 8, мы не знаем остальные байты.
# 3. Но мы можем предположить, что исходный текст - это флаг.
# 4. Мы можем просто расшифровать данные, используя key_part,
# повторенный до длины first_block. Это даст правильный результат
# только для первых 8 байт, остальное будет мусором.
# СТОП. Давайте подумаем иначе.
# В enc.py: expanded_key = repeat_data(encryption_key, len(input_data))
# Это значит, что ключ повторялся, чтобы покрыть весь input_data.
# Затем expanded_data = repeat_data(input_data, key_size)
# Затем encrypt(expanded_data, expanded_key)
# Это значит, что для первого блока (длиной original_length):
# Зашифрованный_блок = input_data XOR expanded_key[:original_length]
# expanded_key[:original_length] - это encryption_key, повторенный
# до длины original_length.
# Мы знаем первые 8 байт input_data.
# Значит, мы знаем первые 8 байт encryption_key.
# Если key_size <= 8, мы знаем весь ключ.
# Если key_size > 8, мы знаем только первые 8 байт ключа.
# НО! Мы можем восстановить весь ключ, если знаем, что исходный текст
# состоит из читаемых символов. Мы можем перебрать оставшиеся байты ключа.
# Это долго, но возможно.
# Однако, есть более простой путь.
# Мы можем просто расшифровать данные, используя key_part,
# повторенный до длины first_block, и посмотреть, что получится.
# Если key_size <= 8, результат будет правильным.
# Если key_size > 8, результат будет правильным только для первых 8 байт.
# Давайте попробуем просто вывести результат для каждого key_size.
# Если key_size <= 8, мы увидим читаемый текст.
# Если key_size > 8, мы увидим мусор после 8-го байта.
# Но подождите! В enc.py есть еще одна деталь:
# expanded_data = repeat_data(input_data, key_size)
# Это значит, что input_data повторялся key_size раз.
# Следовательно, первый блок (длиной original_length) - это
# просто input_data, повторенный key_size раз? Нет.
# original_length = len(input_data).
# expanded_data = input_data * key_size.
# Длина expanded_data = len(input_data) * key_size.
# encrypted_data = expanded_data XOR expanded_key.
# expanded_key = encryption_key * len(input_data).
# Длина expanded_key = len(input_data) * key_size? Нет.
# expanded_key = repeat_data(encryption_key, len(input_data)).
# repeat_data(data, factor) возвращает data * factor.
# Значит, expanded_key = encryption_key * len(input_data).
# Длина expanded_key = key_size * len(input_data).
# Отлично! Длины совпадают.
# Теперь: encrypted_data = (input_data * key_size) XOR (encryption_key * len(input_data))
# Это значит, что encrypted_data состоит из повторяющихся блоков?
# Давайте проверим:
# Первые len(input_data) байт: input_data XOR (encryption_key * len(input_data))[:len(input_data)]
# Это input_data XOR encryption_key (повторенный).
# Следующие len(input_data) байт: input_data XOR (encryption_key * len(input_data))[len(input_data):2*len(input_data)]
# Это input_data XOR encryption_key (повторенный), начиная с другого места.
# Так как encryption_key повторяется, это может быть сдвиг.
# НО! Мы можем просто взять первый блок длиной original_length.
# Это input_data XOR encryption_key (повторенный до original_length).
# Если мы знаем первые 8 байт input_data, мы знаем первые 8 байт encryption_key.
# Если key_size <= 8, мы знаем весь ключ.
# Если key_size > 8, мы знаем только часть ключа.
# Хорошая новость: в задании сказано "возможно будет написано DUCKERZ{флаг}".
# Возможно, ключ короткий (12-16 байт).
# Давайте просто попробуем расшифровать, используя key_part,
# повторенный до длины first_block, и выведем результат.
# Если ключ длиннее 8 байт, мы увидим мусор после 8-го байта.
# Но мы можем попробовать восстановить остальную часть ключа,
# предполагая, что текст - это ASCII.
# Для простоты: давайте выведем результат для всех key_size.
# Если key_size <= 8, мы увидим читаемый текст.
# Если key_size > 8, мы увидим "DUCKERZ{" и мусор.
# Но подождите! Мы можем восстановить весь ключ, если знаем,
# что исходный текст - это флаг. Флаги обычно состоят из ASCII.
# Мы можем перебрать оставшиеся байты ключа (key_size - 8 байт)
# так, чтобы расшифрованный текст был ASCII.
# Это 256^(key_size - 8) вариантов. Для key_size=12 это 256^4 = 4 миллиарда. Слишком много.
# Однако, мы можем использовать тот факт, что encrypted_data состоит
# из повторяющихся блоков.
# Давайте посмотрим на структуру:
# encrypted_data = (input_data * key_size) XOR (encryption_key * len(input_data))
# Это значит, что encrypted_data[i] = input_data[i % len(input_data)] XOR encryption_key[i % key_size]
# Мы знаем input_data[0:8] = "DUCKERZ{".
# Мы можем найти encryption_key[0:8].
# Теперь, если мы посмотрим на encrypted_data[original_length + j] для j от 0 до 7:
# Это input_data[j] XOR encryption_key[(original_length + j) % key_size]
# Но input_data[j] = input_data[j % len(input_data)] = input_data[j] (так как j < len(input_data))
# И мы знаем input_data[j] для j < 8.
# Значит, мы можем найти encryption_key[(original_length + j) % key_size].
# Это дает нам больше байт ключа!
# Этот процесс можно продолжать, пока мы не восстановим весь ключ.
# Но это сложно реализовать в одном скрипте.
# ДАВАЙТЕ СДЕЛАЕМ ПРОЩЕ:
# Мы знаем, что key_size от 12 до 44.
# Мы знаем первые 8 байт ключа.
# Мы можем просто перебрать оставшиеся байты ключа,
# но только если key_size <= 12 (тогда осталось 4 байта, 256^4 = 4e9 - все еще много).
# СТОП. Давайте перечитаем enc.py.
# expanded_key = repeat_data(encryption_key, len(input_data))
# Это encryption_key * len(input_data).
# Длина = key_size * len(input_data).
# expanded_data = repeat_data(input_data, key_size)
# Это input_data * key_size.
# Длина = len(input_data) * key_size.
# Они совпадают!
# Теперь: encrypted_data = expanded_data XOR expanded_key.
# encrypted_data[i] = expanded_data[i] XOR expanded_key[i].
# expanded_data[i] = input_data[i % len(input_data)].
# expanded_key[i] = encryption_key[i % key_size].
# Значит, encrypted_data[i] = input_data[i % L] XOR encryption_key[i % K],
# где L = len(input_data), K = key_size.
# Мы знаем input_data[0:8] = "DUCKERZ{".
# Мы можем найти encryption_key[0:8].
# Теперь, если мы возьмем i = L + j, где j от 0 до 7:
# encrypted_data[L + j] = input_data[(L + j) % L] XOR encryption_key[(L + j) % K]
# = input_data[j] XOR encryption_key[(L + j) % K].
# Мы знаем input_data[j] (так как j < 8).
# Значит, мы можем найти encryption_key[(L + j) % K].
# Это дает нам ключ по модулю K.
# Продолжая этот процесс, мы можем восстановить весь ключ.
# Но для этого нам нужно знать L (длину исходного файла).
# L = len(encrypted_data) // K.
# Давайте реализуем это!
# 1. Вычисляем L.
L = len(encrypted_data) // key_size
# 2. Восстанавливаем ключ.
key = [None] * key_size
known_plain = b"DUCKERZ{"
# Заполняем ключ на основе известного текста
for i in range(len(known_plain)):
if i < L:
key[i % key_size] = encrypted_data[i] ^ known_plain[i]
# Теперь используем тот факт, что данные повторяются.
# encrypted_data[i] = input_data[i % L] XOR key[i % K]
# Если мы знаем input_data[i % L], мы можем найти key[i % K].
# Мы знаем input_data[0:8].
# Мы можем найти key для всех индексов, которые встречаются
# при i = 0..7, L+0..L+7, 2L+0..2L+7, и т.д.
# Это даст нам key[(j) % K] для j = 0..7, L..L+7, 2L..2L+7, ...
# Так как L и K могут быть не взаимно просты, это может покрыть не все индексы.
# Но обычно покрывает достаточно.
# Давайте просто попробуем найти ключ, перебирая оставшиеся байты.
# Если key_size <= 16, это реально.
# Для key_size > 16 это может быть долго.
# Упростим: просто попробуем расшифровать, используя известные байты ключа,
# и посмотрим на результат. Если ключ неполный, мы увидим мусор.
# Но мы можем сделать так:
# Мы знаем, что исходный текст - это ASCII (флаг).
# Мы можем перебрать оставшиеся байты ключа, проверяя, что результат - ASCII.
# Это 256^(K - known_bytes) вариантов.
# Если K=12, known_bytes=8, осталось 4 байта: 256^4 = 4.3e9. Слишком много.
# Если K=12, но мы можем найти больше байт ключа через повторение.
# Давайте реализуем поиск ключа через повторение.
# Мы знаем key[i] для i = 0..7.
# Мы можем найти key[(L + j) % K] для j = 0..7.
# Это добавит новые индексы.
# Продолжаем, пока не найдем все K байт.
# Реализация:
key = [None] * key_size
known_plain = b"DUCKERZ{"
# Заполняем начальные байты
for i in range(len(known_plain)):
if i < L:
key[i % key_size] = encrypted_data[i] ^ known_plain[i]
# Итеративно ищем остальные байты
# Мы знаем input_data[0:8].
# Мы можем найти key для всех i, где i % L < 8.
# i % L < 8 означает i = m*L + j, где j < 8.
# Тогда key[i % K] = encrypted_data[i] ^ input_data[j].
# Мы можем перебрать m от 0 до (len(encrypted_data) // L) - 1.
# Это даст нам key для всех i % K, где i = m*L + j.
for m in range(len(encrypted_data) // L):
for j in range(len(known_plain)):
i = m * L + j
if i < len(encrypted_data):
idx = i % key_size
if key[idx] is None:
key[idx] = encrypted_data[i] ^ known_plain[j]
# Проверяем, все ли байты ключа найдены
if None in key:
# Не все байты найдены. Пропускаем этот key_size.
continue
# Если все байты найдены, расшифровываем первый блок
decrypted = bytes([encrypted_data[i] ^ key[i % key_size] for i in range(L)])
# Проверяем, начинается ли с DUCKERZ{
if decrypted.startswith(known_plain):
print(f"Найден ключ! Длина ключа: {key_size}")
print(f"Ключ (hex): {bytes(key).hex()}")
print(f"Расшифрованный текст:")
try:
print(decrypted.decode('utf-8'))
except:
print(decrypted)
print("-" * 50)
if __name__ == "__main__":
decrypt()