Vinegar
Vinegar
Platform: spbctf | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-07-02 | Status: Solved Techniques: chi_squared_test, index_of_coincidence, semantic_clue_from_title, vigenere_cryptanalysis
Summary
Task: a long Russian Cyrillic ciphertext with no spaces, titled 'Vinegar'. Solution: recognize the title as a pun on Vigenere, find key length 12 via Index of Coincidence, recover the key 'криптография' by chi-squared frequency analysis over the 33-letter Russian alphabet, and read the flag phrase embedded at the end of the plaintext.
Vinegar — spbctf
Description
Это вообще можно прочитать?!!7!?7!171?!?
(Translation: "Can this even be read?!")
Given a vinegar.txt file containing a 3596-character Russian Cyrillic blob
with no spaces or punctuation, using all 33 letters of the Russian alphabet
(including ё). Goal: recover the plaintext and extract the flag.
Analysis
The key insight is the task title: "Vinegar" is a pun on "Vigenère" (vinegar ≈ Vigenère). This immediately identifies the cipher as a Vigenère cipher operating over the 33-letter Russian alphabet:
абвгдеёжзийклмнопрстуфхцчшщъыьэюя (ё placed after е, index 6)
Because the ciphertext preserves only letters (no spacing/case), classical cryptanalysis applies directly:
- Key length is found with the Index of Coincidence (IC): splitting the
text into
klencolumns and averaging per-column IC, the value peaks whenklenis a multiple of the true key length. Here IC peaked at 6/12/18/24, strongest at 12 (IC ≈ 0.0553), so the key length is 12. - Key bytes are recovered per column with a chi-squared goodness-of-fit test against expected Russian letter frequencies: for each column try all 33 shifts and keep the one minimizing chi-square.
The recovered key is криптография (Russian for "cryptography"), and the
plaintext is a Russian article about Attack-Defense CTF mechanics. The flag
phrase is embedded at the very end, immediately after the word флаг.
Solution
python read-only • copy allowed#!/usr/bin/env python3 from collections import Counter d = open('vinegar.txt', encoding='utf-8').read().strip() ALPH = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя' # 33 letters, ё after е n = len(ALPH) idx = {c: i for i, c in enumerate(ALPH)} def ic(s): c = Counter(s); N = len(s) return sum(v * (v - 1) for v in c.values()) / (N * (N - 1)) if N > 1 else 0 # 1) Key-length detection: average per-column IC peaks at multiples of 12 for klen in range(1, 25): cols = [''] * klen for i, ch in enumerate(d): cols[i % klen] += ch print(klen, round(sum(ic(c) for c in cols) / klen, 4)) # 2) Russian letter frequencies for chi-squared key recovery freq = {'о':0.1097,'е':0.0845,'а':0.0801,'и':0.0735,'н':0.0670,'т':0.0626, 'с':0.0547,'р':0.0473,'в':0.0454,'л':0.0440,'к':0.0349,'м':0.0321,'д':0.0298, 'п':0.0281,'у':0.0262,'я':0.0201,'ы':0.0190,'ь':0.0174,'г':0.0170,'з':0.0165, 'б':0.0159,'ч':0.0144,'й':0.0121,'х':0.0097,'ж':0.0094,'ш':0.0073,'ю':0.0064, 'ц':0.0048,'щ':0.0036,'э':0.0032,'ф':0.0026,'ъ':0.0004,'ё':0.0004} F = [freq.get(c, 0.0001) for c in ALPH] def best_shift(col): best = None; bs = 0 for s in range(n): cnt = Counter(ALPH[(idx[ch] - s) % n] for ch in col) N = len(col) chi = sum((cnt[c] - F[i] * N) ** 2 / (F[i] * N) for i, c in enumerate(ALPH)) if best is None or chi < best: best = chi; bs = s return bs # 3) Recover key and decrypt klen = 12 cols = [''] * klen for i, ch in enumerate(d): cols[i % klen] += ch key = ''.join(ALPH[best_shift(c)] for c in cols) # -> 'криптография' plain = ''.join(ALPH[(idx[ch] - idx[key[i % klen]]) % n] for i, ch in enumerate(d)) print('key =', key) print('tail =', plain[-60:]) # ...продумывать тактику в соответствии с ним флаг спбцтф проводит лучшие тренировки
The plaintext ends with ...ним флаг спбцтф проводит лучшие тренировки. The
flag is the phrase following флаг, concatenated without spaces:
REDACTED ("spbctf conducts the best trainings").