# Part II: Hardened Keyed Caesar
# Example usage:
# encrypt('Ciphers are hard!',[1,2,3,4],3);
# decrypt('GQBXULQCTKROFV',[1,2,3,4],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, inc):
# 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)]
# Add the incerement times the position (starting at 1)
shift += ((pos + 1) * inc) % 26
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, inc):
# 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)]
# Add the incerement times the position (starting at 1)
shift += ((pos + 1) * inc) % 26
numbers.append((num - shift + 52) % 26)
# Turn the numbers back into letters
text = []
for num in numbers:
text.append(chr(num + 65))
return ''.join(text)
IyBQYXJ0IElJOiBIYXJkZW5lZCBLZXllZCBDYWVzYXIKIyBFeGFtcGxlIHVzYWdlOgojIGVuY3J5cHQoJ0NpcGhlcnMgYXJlIGhhcmQhJyxbMSwyLDMsNF0sMyk7CiMgZGVjcnlwdCgnR1FCWFVMUUNUS1JPRlYnLFsxLDIsMyw0XSwzKTsKIyBOb3RlIHRoZSB0aGVzZSBmdW5jdGlvbnMgYXJlIG5vdCBzdHJpY3RseSBpbnZlcnNlcyBvZiBlYWNoIG90aGVyLAojIGFzIHRoZSBkZWNyeXB0aW9uIGZ1bmN0aW9uIGRvZXMgbm90IHJlc3RvcmUgc3BhY2VzIG9yIHB1bmN0dWF0aW9uLgoKaW1wb3J0IHJlCgpkZWYgZW5jcnlwdCh0ZXh0LCBrZXksIGluYyk6CgkjIFR1cm4gdGV4dCB0byB1cHBlcmNhc2UKCXRleHQgPSB0ZXh0LnVwcGVyKCkKCSMgUmVtb3ZlIHNwYWNlcyBhbmQgcHVuY3R1YXRpb24KCXRleHQgPSByZS5zdWIocidbXkEtWl0nLCcnLHRleHQpCgkjIFR1cm4gdGV4dCBpbnRvIG51bWJlcnMsIEE9MCwgLi4uWj0yNQoJbnVtYmVycyA9IFtdCglmb3IgbGV0dGVyIGluIHRleHQ6CgkJbnVtYmVycy5hcHBlbmQob3JkKGxldHRlcikgLSA2NSkKCSMgQXBwbHkgdGhlIGtleToKCSMgRm9yIHRoZSBudGggbGV0dGVyIG9mIHRoZSBwbGFpbnRleHQsIGFwcGx5IHRoZSBudGgga2V5IChtb2R1bG8gdGhlIGtleSBsZW5ndGgpCgkjIEFuZCBoZW5jZSBzaGlmdCB0aGUgbnVtYmVyIHRoYXQgbWFueSBwbGFjZXMKCWVuY251bSA9IFtdCglmb3IgKHBvcywgbnVtKSBpbiBlbnVtZXJhdGUobnVtYmVycyk6CgkJc2hpZnQgPSBrZXlbcG9zICUgbGVuKGtleSldCgkJIyBBZGQgdGhlIGluY2VyZW1lbnQgdGltZXMgdGhlIHBvc2l0aW9uIChzdGFydGluZyBhdCAxKQoJCXNoaWZ0ICs9ICgocG9zICsgMSkgKiBpbmMpICUgMjYKCQllbmNudW0uYXBwZW5kKChudW0gKyBzaGlmdCkgJSAyNikKCSMgVHVybiB0aGUgbnVtYmVycyBiYWNrIGludG8gbGV0dGVycwoJZW5jID0gW10KCWZvciBudW0gaW4gZW5jbnVtOgoJCWVuYy5hcHBlbmQoY2hyKG51bSArIDY1KSkKCQoJcmV0dXJuICcnLmpvaW4oZW5jKQoKZGVmIGRlY3J5cHQoZW5jLCBrZXksIGluYyk6CgkjIFR1cm4gZW5jcnlwdGVkIHRleHQgaW50byBudW1iZXJzLCBBPTAsIC4uLlo9MjUKCWVuY251bSA9IFtdCglmb3IgbGV0dGVyIGluIGVuYzoKCQllbmNudW0uYXBwZW5kKG9yZChsZXR0ZXIpIC0gNjUpCgkjIEFwcGx5IHRoZSBrZXkgaW4gcmV2ZXJzZToKCSMgRm9yIHRoZSBudGggbGV0dGVyIG9mIHRoZSBwbGFpbnRleHQsIGFwcGx5IHRoZSBudGgga2V5IChtb2R1bG8gdGhlIGtleSBsZW5ndGgpCgkjIEFuZCBoZW5jZSBzaGlmdCB0aGUgbnVtYmVyIHRoYXQgbWFueSBwbGFjZXMgaW4gdGhlIG5lZ2F0aXZlIGRpcmVjdGlvbgoJbnVtYmVycyA9IFtdCglmb3IgKHBvcywgbnVtKSBpbiBlbnVtZXJhdGUoZW5jbnVtKToKCQlzaGlmdCA9IGtleVtwb3MgJSBsZW4oa2V5KV0KCQkjIEFkZCB0aGUgaW5jZXJlbWVudCB0aW1lcyB0aGUgcG9zaXRpb24gKHN0YXJ0aW5nIGF0IDEpCgkJc2hpZnQgKz0gKChwb3MgKyAxKSAqIGluYykgJSAyNgoJCW51bWJlcnMuYXBwZW5kKChudW0gLSBzaGlmdCArIDUyKSAlIDI2KQoJIyBUdXJuIHRoZSBudW1iZXJzIGJhY2sgaW50byBsZXR0ZXJzCgl0ZXh0ID0gW10KCWZvciBudW0gaW4gbnVtYmVyczoKCQl0ZXh0LmFwcGVuZChjaHIobnVtICsgNjUpKQoJCglyZXR1cm4gJycuam9pbih0ZXh0KQ==