Updated for challenge 9
[cipher-tools.git] / cipher / caesar.py
1 from support.utilities import *
2 from support.language_models import *
3
4 from logger import logger
5
6 def caesar_encipher_letter(accented_letter, shift):
7 """Encipher a letter, given a shift amount
8
9 >>> caesar_encipher_letter('a', 1)
10 'b'
11 >>> caesar_encipher_letter('a', 2)
12 'c'
13 >>> caesar_encipher_letter('b', 2)
14 'd'
15 >>> caesar_encipher_letter('x', 2)
16 'z'
17 >>> caesar_encipher_letter('y', 2)
18 'a'
19 >>> caesar_encipher_letter('z', 2)
20 'b'
21 >>> caesar_encipher_letter('z', -1)
22 'y'
23 >>> caesar_encipher_letter('a', -1)
24 'z'
25 >>> caesar_encipher_letter('A', 1)
26 'B'
27 >>> caesar_encipher_letter('é', 1)
28 'f'
29 """
30 # letter = unaccent(accented_letter)
31 # if letter in string.ascii_letters:
32 # if letter in string.ascii_uppercase:
33 # alphabet_start = ord('A')
34 # else:
35 # alphabet_start = ord('a')
36 # return chr(((ord(letter) - alphabet_start + shift) % 26) +
37 # alphabet_start)
38 # else:
39 # return letter
40
41 letter = unaccent(accented_letter)
42 if letter in string.ascii_letters:
43 cipherletter = unpos(pos(letter) + shift)
44 if letter in string.ascii_uppercase:
45 return cipherletter.upper()
46 else:
47 return cipherletter
48 else:
49 return letter
50
51 def caesar_decipher_letter(letter, shift):
52 """Decipher a letter, given a shift amount
53
54 >>> caesar_decipher_letter('b', 1)
55 'a'
56 >>> caesar_decipher_letter('b', 2)
57 'z'
58 """
59 return caesar_encipher_letter(letter, -shift)
60
61 def caesar_encipher(message, shift):
62 """Encipher a message with the Caesar cipher of given shift
63
64 >>> caesar_encipher('abc', 1)
65 'bcd'
66 >>> caesar_encipher('abc', 2)
67 'cde'
68 >>> caesar_encipher('abcxyz', 2)
69 'cdezab'
70 >>> caesar_encipher('ab cx yz', 2)
71 'cd ez ab'
72 >>> caesar_encipher('Héllo World!', 2)
73 'Jgnnq Yqtnf!'
74 """
75 enciphered = [caesar_encipher_letter(l, shift) for l in message]
76 return cat(enciphered)
77
78 def caesar_decipher(message, shift):
79 """Decipher a message with the Caesar cipher of given shift
80
81 >>> caesar_decipher('bcd', 1)
82 'abc'
83 >>> caesar_decipher('cde', 2)
84 'abc'
85 >>> caesar_decipher('cd ez ab', 2)
86 'ab cx yz'
87 >>> caesar_decipher('Jgnnq Yqtnf!', 2)
88 'Hello World!'
89 """
90 return caesar_encipher(message, -shift)
91
92
93 def caesar_break(message, fitness=Pletters):
94 """Breaks a Caesar cipher using frequency analysis
95
96 >>> caesar_break('ibxcsyorsaqcheyklxivoexlevmrimwxsfiqevvmihrsasrxliwyrh' \
97 'ecjsppsamrkwleppfmergefifvmhixscsymjcsyqeoixlm') # doctest: +ELLIPSIS
98 (4, -130.849989015...)
99 >>> caesar_break('wxwmaxdgheetgwuxztgptedbgznitgwwhpguxyhkxbmhvvtlbhgtee' \
100 'raxlmhiixweblmxgxwmhmaxybkbgztgwztsxwbgmxgmert') # doctest: +ELLIPSIS
101 (19, -128.82410410...)
102 >>> caesar_break('yltbbqnqnzvguvaxurorgenafsbezqvagbnornfgsbevpnaabjurer' \
103 'svaquvzyvxrnznazlybequrvfohgriraabjtbaruraprur') # doctest: +ELLIPSIS
104 (13, -126.25403935...)
105 """
106 sanitised_message = sanitise(message)
107 best_shift = 0
108 best_fit = float('-inf')
109 for shift in range(26):
110 plaintext = caesar_decipher(sanitised_message, shift)
111 fit = fitness(plaintext)
112 logger.debug('Caesar break attempt using key {0} gives fit of {1} '
113 'and decrypt starting: {2}'.format(shift, fit,
114 plaintext[:50]))
115 if fit > best_fit:
116 best_fit = fit
117 best_shift = shift
118 logger.info('Caesar break best fit: key {0} gives fit of {1} and '
119 'decrypt starting: {2}'.format(best_shift, best_fit,
120 caesar_decipher(sanitised_message, best_shift)[:50]))
121 return best_shift, best_fit