Условие
Разработчик зашифровал секрет: WUFOREVYX09GRkVSX1MxMxTVBMM19GTEBH==. Утверждает, что шифрование стойкое. Докажите обратное.
Решение
Шаг 1. Выглядит как Base64
Признаки Base64:
- Алфавит:
A-Z,a-z,0-9,+,/. - В конце знаки
=(паддинг). - Длина кратна 4.
Строка WUFOREVYX09GRkVSX1MxMxTVBMM19GTEBH== содержит только A-Z, 0-9, _ (а это уже base64url) и завершается ==. Похоже на base64url.
Замена: _ → /, - → + для классического base64. Здесь только _, заменяем на /.
import base64
s = "WUFOREVYX09GRkVSX1MxMxTVBMM19GTEBH==".replace("_", "/").replace("-", "+")
decoded = base64.b64decode(s)
print(decoded)
# b'YANDEXOFFER_S13?...' # пример выводаПосле декодирования получаем байтовую строку. Если в ней читаемый ASCII вроде YANDEX_OFFER_S1... — это просто base64 без шифрования.
Доказательство нестойкости: base64 — это кодирование, не шифрование. Декодирование не требует ключа, его умеет любой компьютер.
Шаг 2. Если внутри ASCII с XOR
Если декодированные байты не похожи на текст (но содержат печатаемые символы вперемешку), частая следующая ступень — XOR с коротким ключом.
Проверка: посчитать частоту байтов. Если у XOR с однобайтовым ключом — частоты сдвинуты, но распределение похоже на распределение английских/русских текстов.
Шаг 3. Атака на однобайтовый XOR
Перебираем все 256 ключей:
import base64
cipher = base64.b64decode("WUFOREVYX09GRkVSX1MxMxTVBMM19GTEBH==".replace("_", "/"))
for key in range(256):
plain = bytes(b ^ key for b in cipher)
# Эвристика «осмысленности»: доля печатаемого ASCII.
printable = sum(1 for b in plain if 32 <= b < 127)
if printable >= 0.9 * len(plain):
print(f"key={key:02x}: {plain}")Для многобайтового XOR — атака разбиения на колонки:
- Угадываем длину ключа
k(тестируем 1..40). - Разбиваем ciphertext на
kколонок. - В каждой колонке — однобайтовый XOR; решается по частотам.
- Складываем ключ.
Это классический «cryptopals challenge 6».
Шаг 4. Если AES в ECB
Если декодированные байты длиной кратной 16 — может быть AES-ECB. Признак ECB: одинаковые блоки в plaintext дают одинаковые блоки в ciphertext. Если в исходном тексте повторяются 16-байтные паттерны — в ciphertext они видны.
def detect_ecb(data, blk=16):
blocks = [data[i:i+blk] for i in range(0, len(data), blk)]
return len(blocks) != len(set(blocks))
if detect_ecb(decoded):
print("ECB-mode AES — нестойко")Шаг 5. Доказательство
«Шифрование» нестойкое, потому что:
- Base64 — не шифрование. Декодируется без ключа. Любой junior может это сделать.
- Одно- и многобайтовый XOR ломаются за секунды частотным анализом.
- AES-ECB уязвим: одинаковые блоки plain → одинаковые ciphertext, утечка структуры (известный мем «ECB Penguin»).
Демонстрация (полная)
import base64
cipher_b64 = "WUFOREVYX09GRkVSX1MxMxTVBMM19GTEBH=="
# 1. Расшифруем как base64url
raw = base64.b64decode(cipher_b64.replace("_", "/"))
print("base64-decoded:", raw)
# 2. Если выглядит как ASCII — мы уже взломали
if all(32 <= b < 127 for b in raw):
print("Это просто base64, без шифрования.")
else:
# 3. Однобайтовый XOR
best = None
for k in range(256):
plain = bytes(b ^ k for b in raw)
score = sum(1 for b in plain if 32 <= b < 127)
if best is None or score > best[0]:
best = (score, k, plain)
print(f"Best single-byte XOR (key={best[1]:#x}):", best[2])Подводные камни
- Base64 != шифрование. Это первое, что нужно сказать.
- Base64url использует
-и_вместо+и/. Не забыть конвертировать. - Padding
=/==. Иногда обрезают; добавляйте при декодировании:s += '=' * (-len(s) % 4). bytesvsstr. Декодирование возвращаетbytes. Если печатаете без декодинга в UTF-8 — будетb'...'.- Многобайтовый XOR требует длины ключа > 1 — детектируйте через расстояние Хэмминга между блоками.
- AES-CBC с
iv=0тоже нестоек к chosen-plaintext, но базовые атаки хитрее. - «Custom encryption» часто комбинирует base64 → XOR → base64. Раскручивайте слоями.
Эталонный ответ
Это base64, не шифрование: декодируется без ключа. Если внутри base64 ещё есть XOR, ломается частотным анализом за секунды (одно- или многобайтовый ключ через cryptopals challenge 6). Если AES-ECB — выдаётся повторяющимися блоками. Любая «домашняя криптография» нестойка по умолчанию; используйте AES-GCM/ChaCha20-Poly1305 со случайным IV/nonce.