← Back to Writeups
CUSTOMEasyCrypto

RSAying

XESXOR9/10/20262 min read
#crypto#custom#easy#key-recovery-from-ciphertext-p#known-plaintext-attack-on-vige

RSAying

Platform: spbctf | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-03-09 | Status: Solved Techniques: Key recovery from ciphertext-plaintext pairs, Known plaintext attack on Vigenere cipher

Summary

Task: Vigenere-encrypted Wikipedia article about RSA. Solution: Known plaintext attack using B.E.I.=R.S.A. mapping to recover the key MISCTRACK.

RSAying — cryptokids20

Description

Vigenere-encrypted Wikipedia article about RSA cryptography.

Analysis

  • Task name "RSAying" hints that the content is related to RSA
  • Ciphertext looks like an encrypted article (structure, punctuation preserved)
  • The ciphertext contains "B.E.I." — this is encrypted "R.S.A." (known plaintext)
  • Vigenere is a polyalphabetic cipher with a repeating key

Solution

  1. Identifying the cipher type: Text structure and repeating patterns indicate Vigenere

  2. Known Plaintext Attack:

    • Known: "B.E.I." in ciphertext = "R.S.A." in plaintext
    • Calculate shifts: B→R (shift 16=P), E→S (shift 14=N), I→A (shift 8=I)
    • But this is only 3 characters, need more context ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  3. Key recovery:

    • Using several known text fragments about RSA
    • Key turned out to be: MISCTRACK (9 characters)
  4. Decryption:

    • Apply Vigenere decryption with key MISCTRACK
    • Get the full article text about RSA history
python
#!/usr/bin/env python3
"""
Vigenere Known Plaintext Attack
"""

def vigenere_decrypt(ciphertext, key):
    """Decrypt Vigenere cipher"""
    result = []
    key_len = len(key)
    key_idx = 0
    
    for c in ciphertext:
        if c.isalpha():
            shift = ord(key[key_idx % key_len].upper()) - ord('A')
            if c.isupper():
                result.append(chr((ord(c) - ord('A') - shift) % 26 + ord('A')))
            else:
                result.append(chr((ord(c) - ord('a') - shift) % 26 + ord('a')))
            key_idx += 1
        else:
            result.append(c)
    
    return ''.join(result)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def find_key_from_known_plaintext(ciphertext, plaintext):
    """Recover key from known plaintext-ciphertext pair"""
    key = []
    for c, p in zip(ciphertext, plaintext):
        if c.isalpha() and p.isalpha():
            shift = (ord(c.upper()) - ord(p.upper())) % 26
            key.append(chr(shift + ord('A')))
    return ''.join(key)

# Known: B.E.I. -> R.S.A.
# Partial key recovery
cipher_fragment = "BEI"
plain_fragment = "RSA"
partial_key = find_key_from_known_plaintext(cipher_fragment, plain_fragment)
print(f"Partial key from B.E.I.->R.S.A.: {partial_key}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Full key (found through analysis)
key = "MISCTRACK"

# Decrypt
ciphertext = "..."  # Full ciphertext here
plaintext = vigenere_decrypt(ciphertext, key)
print(plaintext)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍