Собесов

Security Engineering Яндекса — нестойкое шифрование, докажи

Кейсы и метрикиCryptographyСредняяMiddle

Условие

Разработчик зашифровал секрет: 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 — атака разбиения на колонки:

  1. Угадываем длину ключа k (тестируем 1..40).
  2. Разбиваем ciphertext на k колонок.
  3. В каждой колонке — однобайтовый XOR; решается по частотам.
  4. Складываем ключ.

Это классический «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. Доказательство

«Шифрование» нестойкое, потому что:

  1. Base64 — не шифрование. Декодируется без ключа. Любой junior может это сделать.
  2. Одно- и многобайтовый XOR ломаются за секунды частотным анализом.
  3. 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])

Подводные камни

  1. Base64 != шифрование. Это первое, что нужно сказать.
  2. Base64url использует - и _ вместо + и /. Не забыть конвертировать.
  3. Padding =/==. Иногда обрезают; добавляйте при декодировании: s += '=' * (-len(s) % 4).
  4. bytes vs str. Декодирование возвращает bytes. Если печатаете без декодинга в UTF-8 — будет b'...'.
  5. Многобайтовый XOR требует длины ключа > 1 — детектируйте через расстояние Хэмминга между блоками.
  6. AES-CBC с iv=0 тоже нестоек к chosen-plaintext, но базовые атаки хитрее.
  7. «Custom encryption» часто комбинирует base64 → XOR → base64. Раскручивайте слоями.

Эталонный ответ

Это base64, не шифрование: декодируется без ключа. Если внутри base64 ещё есть XOR, ломается частотным анализом за секунды (одно- или многобайтовый ключ через cryptopals challenge 6). Если AES-ECB — выдаётся повторяющимися блоками. Любая «домашняя криптография» нестойка по умолчанию; используйте AES-GCM/ChaCha20-Poly1305 со случайным IV/nonce.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти