Started on documentation
[szyfrow.git] / szyfrow / caesar.py
1 """Enciphering and deciphering using the [Caesar cipher](https://en.wikipedia.org/wiki/Caesar_cipher).
2 Also attempts to break messages that use a Caesar cipher.
3
4 The Caesar cipher operates one letter at a time. It converts each letter to a
5 number, then enciphers that number by adding the key. The result is taken mod
6 26 and converted back into a letter.
7
8 """
9
10 from szyfrow.support.utilities import *
11 from szyfrow.support.language_models import *
12
13 def caesar_encipher_letter(accented_letter, shift):
14 """Encipher a letter, given a shift amount.
15
16 Accented version of latin letters (such as é and ö) are converted to their
17 non-accented versions before encryption.
18
19 >>> caesar_encipher_letter('a', 1)
20 'b'
21 >>> caesar_encipher_letter('a', 2)
22 'c'
23 >>> caesar_encipher_letter('b', 2)
24 'd'
25 >>> caesar_encipher_letter('x', 2)
26 'z'
27 >>> caesar_encipher_letter('y', 2)
28 'a'
29 >>> caesar_encipher_letter('z', 2)
30 'b'
31 >>> caesar_encipher_letter('z', -1)
32 'y'
33 >>> caesar_encipher_letter('a', -1)
34 'z'
35 >>> caesar_encipher_letter('A', 1)
36 'B'
37 >>> caesar_encipher_letter('é', 1)
38 'f'
39 """
40 # letter = unaccent(accented_letter)
41 # if letter in string.ascii_letters:
42 # if letter in string.ascii_uppercase:
43 # alphabet_start = ord('A')
44 # else:
45 # alphabet_start = ord('a')
46 # return chr(((ord(letter) - alphabet_start + shift) % 26) +
47 # alphabet_start)
48 # else:
49 # return letter
50
51 letter = unaccent(accented_letter)
52 if letter in string.ascii_letters:
53 cipherletter = unpos(pos(letter) + shift)
54 if letter in string.ascii_uppercase:
55 return cipherletter.upper()
56 else:
57 return cipherletter
58 else:
59 return letter
60
61 def caesar_decipher_letter(letter, shift):
62 """Decipher a letter, given a shift amount
63
64 >>> caesar_decipher_letter('b', 1)
65 'a'
66 >>> caesar_decipher_letter('b', 2)
67 'z'
68 """
69 return caesar_encipher_letter(letter, -shift)
70
71 def caesar_encipher(message, shift):
72 """Encipher a message with the Caesar cipher of given shift
73
74 >>> caesar_encipher('abc', 1)
75 'bcd'
76 >>> caesar_encipher('abc', 2)
77 'cde'
78 >>> caesar_encipher('abcxyz', 2)
79 'cdezab'
80 >>> caesar_encipher('ab cx yz', 2)
81 'cd ez ab'
82 >>> caesar_encipher('Héllo World!', 2)
83 'Jgnnq Yqtnf!'
84 """
85 enciphered = [caesar_encipher_letter(l, shift) for l in message]
86 return cat(enciphered)
87
88 def caesar_decipher(message, shift):
89 """Decipher a message with the Caesar cipher of given shift
90
91 >>> caesar_decipher('bcd', 1)
92 'abc'
93 >>> caesar_decipher('cde', 2)
94 'abc'
95 >>> caesar_decipher('cd ez ab', 2)
96 'ab cx yz'
97 >>> caesar_decipher('Jgnnq Yqtnf!', 2)
98 'Hello World!'
99 """
100 return caesar_encipher(message, -shift)
101
102
103 def caesar_break(message, fitness=Pletters):
104 """Breaks a Caesar cipher using frequency analysis
105
106 It tries all possible keys, scores the fitness of the text decipherd with
107 each key, and returns the key that produces the most fit deciphered text.
108
109 >>> caesar_break('ibxcsyorsaqcheyklxivoexlevmrimwxsfiqevvmihrsasrxliwyrh' \
110 'ecjsppsamrkwleppfmergefifvmhixscsymjcsyqeoixlm') # doctest: +ELLIPSIS
111 (4, -130.849989015...)
112 >>> caesar_break('wxwmaxdgheetgwuxztgptedbgznitgwwhpguxyhkxbmhvvtlbhgtee' \
113 'raxlmhiixweblmxgxwmhmaxybkbgztgwztsxwbgmxgmert') # doctest: +ELLIPSIS
114 (19, -128.82410410...)
115 >>> caesar_break('yltbbqnqnzvguvaxurorgenafsbezqvagbnornfgsbevpnaabjurer' \
116 'svaquvzyvxrnznazlybequrvfohgriraabjtbaruraprur') # doctest: +ELLIPSIS
117 (13, -126.25403935...)
118 """
119 sanitised_message = sanitise(message)
120 best_shift = 0
121 best_fit = float('-inf')
122 for shift in range(26):
123 plaintext = caesar_decipher(sanitised_message, shift)
124 fit = fitness(plaintext)
125
126 if fit > best_fit:
127 best_fit = fit
128 best_shift = shift
129
130 return best_shift, best_fit