# [ångstromCTF 2021](https://2021.angstromctf.com/) Exclusive Cipher### References* Szymański, Ł. (2021). ångstromCTF 2021: Exclusive Cipher. szymanski.ninja. https://szymanski.ninja/en/ctfwriteups/2021/angstromctf/exclusive-cipher/## Question> Clam decided to return to classic cryptography and revisit the XOR cipher! Here's some hex encoded ciphertext:> ```> ae27eb3a148c3cf031079921ea3315cd27eb7d02882bf724169921eb3a469920e07d0b883bf63c018869a5090e8868e331078a68ec2e468c2bf13b1d9a20ea0208882de12e398c2df60211852deb021f823dda35079b2dda25099f35ab7d2182> 27e17d0a982bee7d098368f13503cd27f135039f68e62f1f9d3cea7c> ```> The key is 5 bytes long, and the flag is somewhere in the message.## AnalysisAssuming 2 hexadecimal digits are equivalent to 1 ASCII characters, a possible key can be found by XORing the ciphertext with the known 5-bytes long substring `actf{`.## [Solution](https://github.com/AppleGamer22/AppleGamer22/tree/master/angstromCTF/crypto/Exclusive_Cipher)In an XOR Cipher, it is known that `possible_key = ciphertext ^ known_cleartext`. The python script attached:1. slices the ciphertext to all possible 5 characters-long (assuming 2 hexadecimal digits are equivalent to 1 ASCII characters) sections,2. computes `possible_key = ciphertext ^ known_cleartext`, for a known substring of `actf{`,3. expands the key to the ASCII length of the message,4. rotates the key to deal with cases where the known clear text is not in an index that is a multiple of the key length.   * Thanks to [@Levon](https://hashnode.com/@Levon) for this suggestion.5. recomputes the XOR to possibly decode the message6. and prints the possible message as ASCII.### Initial Python Code```pythonfrom typing import Listfrom doctest import testmodfrom textwrap import wrap
def xor(s: List[int], t: List[int]) -> List[int]:	"""	:param s: list of non-negative integers	:param t: list of non-negative integers	:return: XOR of the ith number of both lists	"""	return [a ^ b for a, b in zip(s, t)]
def expand_key(short_key: List[int], size: int) -> List[int]:	"""	:param short_key: list of non-negative integers	:param size: positive integer	:return: short_key * (size // len(short_key)) + short_key[:size - len(key_expanded)]
	>>> expand_key([1, 2, 3, 4, 5], 9)	[1, 2, 3, 4, 5, 1, 2, 3, 4]	"""	assert size > len(short_key)	key_expanded = short_key * (size // len(short_key))	for ii in range(size - len(key_expanded)):		key_expanded.append(short_key[ii])	return key_expanded
ciphertext_text = input("hex-encoded ciphertext: ")known_cleartext = input("known cleartext (with length of key): ")hint = input("hint (such as 'flag'): ")
cipher_ascii = [int(letter, 16) for letter in wrap(ciphertext_text, 2)]known_cleartext_ascii = [ord(letter) for letter in known_cleartext]
for i in range(len(cipher_ascii) - len(known_cleartext)):	key = xor(cipher_ascii[i:i + len(known_cleartext)], known_cleartext_ascii)	expanded_key = expand_key(key, len(cipher_ascii))	message_ascii = xor(cipher_ascii, expanded_key)	message_text = "".join(map(chr, message_ascii))	if known_cleartext in message_text and hint in message_text:		print(f"key: {key} ('{''.join(map(chr, key))}')")		print(f"message: {message_text}")		print()```### Improved Python Code```pythonfrom typing import TypedDict, Listfrom textwrap import wrapfrom pwn import xor
class XORSolution(TypedDict):	key: List[int]	cleartext: str
def decode_xor(ciphertext_hex: str, known_cleartext: str, hint: str) -> List[XORSolution]:	output = []	cipher_ascii = bytes(int(letter, 16) for letter in wrap(ciphertext_hex, 2))	for i in range(len(cipher_ascii)):		key = list(xor(cipher_ascii[i:i + len(known_cleartext)], known_cleartext.encode()))		for ii in range(len(key)):			rotated_key = key[-ii:] + key[:-ii]			cleartext = str(xor(cipher_ascii, rotated_key))[2:-1]			if known_cleartext in cleartext and hint in cleartext:				output.append({"key": rotated_key, "cleartext": cleartext})	return output```### Python Script Output* A Python script that prints all valid solutions for the full ciphertext and the ciphertext without the first character:```pythonciphertext_hex1 = "ae27eb3a148c3cf031079921ea3315cd27eb7d02882bf724169921eb3a469920e07d0b883bf63c018869a5090e8868e331078a68ec2e468c2bf13b1d9a20ea0208882de12e398c2df60211852deb021f823dda35079b2dda25099f35ab7d218227e17d0a982bee7d098368f13503cd27f135039f68e62f1f9d3cea7c"known_cleartext1 = "actf{"hint1 = "flag"
for solution in decode_xor(ciphertext_hex1, known_cleartext1, hint1):	print(f"key: {solution['key']})")	print(f"message: {solution['cleartext']}")
for solution in decode_xor(ciphertext_hex1[2:], known_cleartext1, hint1):	print(f"key: {solution['key']})")	print(f"message: {solution['cleartext']}")```* The output of the screen described immediately above:```key: [237, 72, 133, 93, 102])message: Congratulations on decrypting the message! The flag is actf{who_needs_aes_when_you_have_xor}. Good luck on the other crypto!key: [72, 133, 93, 102, 237])message: ongratulations on decrypting the message! The flag is actf{who_needs_aes_when_you_have_xor}. Good luck on the other crypto!```**Flag**: `actf{who_needs_aes_when_you_have_xor}`