# Part I: Keyed Caesar
# Example usage:
# encrypt('Hello cipher world!',[1,19,6,4])
# decrypt('MMRIYUIYCPJLRCSLYQBRV',[7,24,3])
# Note the these functions are not strictly inverses of each other,
# as the decryption function does not restore spaces or punctuation.
import re
def encrypt(text, key):
# Turn text to uppercase
text = text.upper()
# Remove spaces and punctuation
text = re.sub(r'[^A-Z]','',text)
# Turn text into numbers, A=0, ...Z=25
numbers = []
for letter in text:
numbers.append(ord(letter) - 65)
# Apply the key:
# For the nth letter of the plaintext, apply the nth key (modulo the key length)
# And hence shift the number that many places
encnum = []
for (pos, num) in enumerate(numbers):
shift = key[pos % len(key)]
encnum.append((num + shift) % 26)
# Turn the numbers back into letters
enc = []
for num in encnum:
enc.append(chr(num + 65))
return ''.join(enc)
def decrypt(enc, key):
# Turn encrypted text into numbers, A=0, ...Z=25
encnum = []
for letter in enc:
encnum.append(ord(letter) - 65)
# Apply the key in reverse:
# For the nth letter of the plaintext, apply the nth key (modulo the key length)
# And hence shift the number that many places in the negative direction
numbers = []
for (pos, num) in enumerate(encnum):
shift = key[pos % len(key)]
numbers.append((num - shift + 26) % 26)
# Turn the numbers back into letters
text = []
for num in numbers:
text.append(chr(num + 65))
return ''.join(text)
IyBQYXJ0IEk6IEtleWVkIENhZXNhcgojIEV4YW1wbGUgdXNhZ2U6CiMgZW5jcnlwdCgnSGVsbG8gY2lwaGVyIHdvcmxkIScsWzEsMTksNiw0XSkKIyBkZWNyeXB0KCdNTVJJWVVJWUNQSkxSQ1NMWVFCUlYnLFs3LDI0LDNdKQojIE5vdGUgdGhlIHRoZXNlIGZ1bmN0aW9ucyBhcmUgbm90IHN0cmljdGx5IGludmVyc2VzIG9mIGVhY2ggb3RoZXIsCiMgYXMgdGhlIGRlY3J5cHRpb24gZnVuY3Rpb24gZG9lcyBub3QgcmVzdG9yZSBzcGFjZXMgb3IgcHVuY3R1YXRpb24uCgppbXBvcnQgcmUKCmRlZiBlbmNyeXB0KHRleHQsIGtleSk6CgkjIFR1cm4gdGV4dCB0byB1cHBlcmNhc2UKCXRleHQgPSB0ZXh0LnVwcGVyKCkKCSMgUmVtb3ZlIHNwYWNlcyBhbmQgcHVuY3R1YXRpb24KCXRleHQgPSByZS5zdWIocidbXkEtWl0nLCcnLHRleHQpCgkjIFR1cm4gdGV4dCBpbnRvIG51bWJlcnMsIEE9MCwgLi4uWj0yNQoJbnVtYmVycyA9IFtdCglmb3IgbGV0dGVyIGluIHRleHQ6CgkJbnVtYmVycy5hcHBlbmQob3JkKGxldHRlcikgLSA2NSkKCSMgQXBwbHkgdGhlIGtleToKCSMgRm9yIHRoZSBudGggbGV0dGVyIG9mIHRoZSBwbGFpbnRleHQsIGFwcGx5IHRoZSBudGgga2V5IChtb2R1bG8gdGhlIGtleSBsZW5ndGgpCgkjIEFuZCBoZW5jZSBzaGlmdCB0aGUgbnVtYmVyIHRoYXQgbWFueSBwbGFjZXMKCWVuY251bSA9IFtdCglmb3IgKHBvcywgbnVtKSBpbiBlbnVtZXJhdGUobnVtYmVycyk6CgkJc2hpZnQgPSBrZXlbcG9zICUgbGVuKGtleSldCgkJZW5jbnVtLmFwcGVuZCgobnVtICsgc2hpZnQpICUgMjYpCgkjIFR1cm4gdGhlIG51bWJlcnMgYmFjayBpbnRvIGxldHRlcnMKCWVuYyA9IFtdCglmb3IgbnVtIGluIGVuY251bToKCQllbmMuYXBwZW5kKGNocihudW0gKyA2NSkpCgkKCXJldHVybiAnJy5qb2luKGVuYykKCmRlZiBkZWNyeXB0KGVuYywga2V5KToKCSMgVHVybiBlbmNyeXB0ZWQgdGV4dCBpbnRvIG51bWJlcnMsIEE9MCwgLi4uWj0yNQoJZW5jbnVtID0gW10KCWZvciBsZXR0ZXIgaW4gZW5jOgoJCWVuY251bS5hcHBlbmQob3JkKGxldHRlcikgLSA2NSkKCSMgQXBwbHkgdGhlIGtleSBpbiByZXZlcnNlOgoJIyBGb3IgdGhlIG50aCBsZXR0ZXIgb2YgdGhlIHBsYWludGV4dCwgYXBwbHkgdGhlIG50aCBrZXkgKG1vZHVsbyB0aGUga2V5IGxlbmd0aCkKCSMgQW5kIGhlbmNlIHNoaWZ0IHRoZSBudW1iZXIgdGhhdCBtYW55IHBsYWNlcyBpbiB0aGUgbmVnYXRpdmUgZGlyZWN0aW9uCgludW1iZXJzID0gW10KCWZvciAocG9zLCBudW0pIGluIGVudW1lcmF0ZShlbmNudW0pOgoJCXNoaWZ0ID0ga2V5W3BvcyAlIGxlbihrZXkpXQoJCW51bWJlcnMuYXBwZW5kKChudW0gLSBzaGlmdCArIDI2KSAlIDI2KQoJIyBUdXJuIHRoZSBudW1iZXJzIGJhY2sgaW50byBsZXR0ZXJzCgl0ZXh0ID0gW10KCWZvciBudW0gaW4gbnVtYmVyczoKCQl0ZXh0LmFwcGVuZChjaHIobnVtICsgNjUpKQoJCglyZXR1cm4gJycuam9pbih0ZXh0KQ==