1582
1582
Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: date_diff_key_derivation, gregorian_reform_correction, global_xor_offset_bruteforce, repeating_key_xor_decrypt
Summary
Walkthrough of the CUSTOM Crypto 1582. Task: 39-byte XOR ciphertext plus a journal of 15 dates in 1582; key is the day-differences between adjacent dates. Solution: derive a repeating XOR key from date diffs, apply a global 0x40 offset, an
Description
Осень 1582 года. Архивариус в Риме нашёл повреждённый журнал дат и зашифрованный файл. Он утверждает, что файл зашифрован XOR, а ключ спрятан в разнице между соседними датами.
English summary: An archivist in Rome (Autumn 1582) found a damaged journal of dates and an encrypted file. The file is XOR-encrypted, and the key is hidden in the differences between adjacent dates.
Provided files:
journal.txt— a Russian-language journal listing 15 dates in 1582 (June through December). A header note says to use the dates exactly as written.cipher.bin— 39 bytes:133d25061f1e3d3d221e3e2137220e312724392b193c232d2b1175660e2c233c3807776675732f
The 15 dates:
1582-06-21, 1582-07-08, 1582-07-16, 1582-07-18, 1582-07-23,
1582-08-03, 1582-08-27, 1582-09-02, 1582-09-21, 1582-10-04,
1582-10-15, 1582-11-02, 1582-11-16, 1582-11-20, 1582-12-12
Analysis
The description spells out the scheme directly: XOR cipher, key = differences between adjacent dates. With 15 dates there are 14 adjacent differences, giving a 14-byte repeating key for the 39-byte ciphertext.
Computing the naive day-differences (treating each date as an ordinary proleptic calendar date):
[17, 8, 2, 5, 11, 24, 6, 19, 13, 11, 18, 14, 4, 22]
These small integers are not in the printable ASCII range, so a plain cipher[i] ^ key[i%14] will not yield text. The diff values are concentrated around 1–24, while flag characters live around 0x30–0x7e. The standard fix is a global XOR offset: brute-force a single constant k over all 256 values and look for printable output containing {. The winning constant is 0x40, which lifts the small diff keystream into the letter/digit range.
Applying the naive key with the 0x40 offset:
B u g C T F { n o U l o s t _ y e a r s _ o n f y _ 1 0 _ d a y s _ 1 5 8 8 }
^ ^ ^^
This is a near-flag — almost correct but with wrong bytes at exactly the positions that fall on key index 9 (and its repeat). That is the tell: the date differences are right everywhere except where the calendar itself is irregular.
The key insight — the Gregorian calendar reform of October 1582. When the Gregorian calendar was adopted, October 4, 1582 was directly followed by October 15, 1582 — the ten intermediate days (Oct 5–14) never existed. So the real elapsed time between 1582-10-04 and 1582-10-15 is 1 day, not 11.
The journal pair 1582-10-04 → 1582-10-15 is at index 9 of the key. Correcting that one value 11 → 1 fixes precisely the bytes that were corrupted in the near-flag. The flag text itself — REDACTED — confirms the theme: the 10 lost days of October 1582.
Corrected key:
[17, 8, 2, 5, 11, 24, 6, 19, 13, 1, 18, 14, 4, 22]
Solution
- Parse the 15 dates and compute the 14 adjacent day-differences → repeating XOR key.
- Brute-force the global XOR offset over 0–255; the printable result containing
{appears at offset0x40. (Or just recognizeBugfromBugCTF{...}to recover the offset via known-plaintext.) - The naive key produces a corrupted near-flag, with errors localized to key index 9 — pointing at the
1582-10-04 → 1582-10-15pair. - Apply the historical correction: that interval is 1 day (Gregorian reform), not 11.
- Re-decrypt with the corrected key to get the flag.
python read-only • copy allowed#!/usr/bin/env python3 from datetime import date ct = bytes.fromhex('133d25061f1e3d3d221e3e2137220e312724392b193c232d2b1175660e2c233c3807776675732f') dates = ['1582-06-21','1582-07-08','1582-07-16','1582-07-18','1582-07-23', '1582-08-03','1582-08-27','1582-09-02','1582-09-21','1582-10-04', '1582-10-15','1582-11-02','1582-11-16','1582-11-20','1582-12-12'] ds = [date(*map(int, d.split('-'))) for d in dates] key = [(ds[i+1] - ds[i]).days for i in range(len(ds) - 1)] # naive key: [17, 8, 2, 5, 11, 24, 6, 19, 13, 11, 18, 14, 4, 22] # Gregorian calendar reform of Oct 1582: Oct 4 -> Oct 15 is really 1 day, # not 11 (the days Oct 5-14 never existed). Index 9 corresponds to that gap. key[9] = 1 # corrected key: [17, 8, 2, 5, 11, 24, 6, 19, 13, 1, 18, 14, 4, 22] # Global XOR offset 0x40 maps the small diff keystream into the ASCII range. pt = bytes(ct[i] ^ key[i % len(key)] ^ 0x40 for i in range(len(ct))) print(pt.decode()) # BugCTF{REDACTED}
Recovering the 0x40 offset without guessing (known-plaintext / brute):
python read-only • copy allowedfor k in range(256): out = bytes(ct[i] ^ key[i % len(key)] ^ k for i in range(len(ct))) if all(32 <= b < 127 for b in out) and b'{' in out: print(hex(k), out.decode()) # 0x40 BugCTF{...}
Key Takeaways / Lessons
- Mastering date_diff_key_derivation, gregorian_reform_correction, global_xor_offset_bruteforce, repeating_key_xor_decrypt was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-29 • CUSTOM • 1582 • Difficulty: Medium