Word segmentation
[cipher-training.git] / cipher.py
1 """A set of ciphers with implementations for both enciphering and deciphering
2 them. See cipherbreak for automatic breaking of these ciphers
3 """
4
5 import string
6 import collections
7 from language_models import unaccent, sanitise
8
9
10 modular_division_table = [[0]*26 for _ in range(26)]
11 for a in range(26):
12 for b in range(26):
13 c = (a * b) % 26
14 modular_division_table[b][c] = a
15
16
17 def caesar_encipher_letter(accented_letter, shift):
18 """Encipher a letter, given a shift amount
19
20 >>> caesar_encipher_letter('a', 1)
21 'b'
22 >>> caesar_encipher_letter('a', 2)
23 'c'
24 >>> caesar_encipher_letter('b', 2)
25 'd'
26 >>> caesar_encipher_letter('x', 2)
27 'z'
28 >>> caesar_encipher_letter('y', 2)
29 'a'
30 >>> caesar_encipher_letter('z', 2)
31 'b'
32 >>> caesar_encipher_letter('z', -1)
33 'y'
34 >>> caesar_encipher_letter('a', -1)
35 'z'
36 >>> caesar_encipher_letter('A', 1)
37 'B'
38 >>> caesar_encipher_letter('é', 1)
39 'f'
40 """
41 letter = unaccent(accented_letter)
42 if letter in string.ascii_letters:
43 if letter in string.ascii_uppercase:
44 alphabet_start = ord('A')
45 else:
46 alphabet_start = ord('a')
47 return chr(((ord(letter) - alphabet_start + shift) % 26) +
48 alphabet_start)
49 else:
50 return letter
51
52 def caesar_decipher_letter(letter, shift):
53 """Decipher a letter, given a shift amount
54
55 >>> caesar_decipher_letter('b', 1)
56 'a'
57 >>> caesar_decipher_letter('b', 2)
58 'z'
59 """
60 return caesar_encipher_letter(letter, -shift)
61
62 def caesar_encipher(message, shift):
63 """Encipher a message with the Caesar cipher of given shift
64
65 >>> caesar_encipher('abc', 1)
66 'bcd'
67 >>> caesar_encipher('abc', 2)
68 'cde'
69 >>> caesar_encipher('abcxyz', 2)
70 'cdezab'
71 >>> caesar_encipher('ab cx yz', 2)
72 'cd ez ab'
73 >>> caesar_encipher('Héllo World!', 2)
74 'Jgnnq Yqtnf!'
75 """
76 enciphered = [caesar_encipher_letter(l, shift) for l in message]
77 return ''.join(enciphered)
78
79 def caesar_decipher(message, shift):
80 """Decipher a message with the Caesar cipher of given shift
81
82 >>> caesar_decipher('bcd', 1)
83 'abc'
84 >>> caesar_decipher('cde', 2)
85 'abc'
86 >>> caesar_decipher('cd ez ab', 2)
87 'ab cx yz'
88 >>> caesar_decipher('Jgnnq Yqtnf!', 2)
89 'Hello World!'
90 """
91 return caesar_encipher(message, -shift)
92
93 def affine_encipher_letter(accented_letter, multiplier=1, adder=0,
94 one_based=True):
95 """Encipher a letter, given a multiplier and adder
96 >>> ''.join([affine_encipher_letter(l, 3, 5, True) \
97 for l in string.ascii_uppercase])
98 'HKNQTWZCFILORUXADGJMPSVYBE'
99 >>> ''.join([affine_encipher_letter(l, 3, 5, False) \
100 for l in string.ascii_uppercase])
101 'FILORUXADGJMPSVYBEHKNQTWZC'
102 """
103 letter = unaccent(accented_letter)
104 if letter in string.ascii_letters:
105 if letter in string.ascii_uppercase:
106 alphabet_start = ord('A')
107 else:
108 alphabet_start = ord('a')
109 letter_number = ord(letter) - alphabet_start
110 if one_based: letter_number += 1
111 cipher_number = (letter_number * multiplier + adder) % 26
112 if one_based: cipher_number -= 1
113 return chr(cipher_number % 26 + alphabet_start)
114 else:
115 return letter
116
117 def affine_decipher_letter(letter, multiplier=1, adder=0, one_based=True):
118 """Encipher a letter, given a multiplier and adder
119
120 >>> ''.join([affine_decipher_letter(l, 3, 5, True) \
121 for l in 'HKNQTWZCFILORUXADGJMPSVYBE'])
122 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
123 >>> ''.join([affine_decipher_letter(l, 3, 5, False) \
124 for l in 'FILORUXADGJMPSVYBEHKNQTWZC'])
125 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
126 """
127 if letter in string.ascii_letters:
128 if letter in string.ascii_uppercase:
129 alphabet_start = ord('A')
130 else:
131 alphabet_start = ord('a')
132 cipher_number = ord(letter) - alphabet_start
133 if one_based: cipher_number += 1
134 plaintext_number = (
135 modular_division_table[multiplier]
136 [(cipher_number - adder) % 26]
137 )
138 if one_based: plaintext_number -= 1
139 return chr(plaintext_number % 26 + alphabet_start)
140 else:
141 return letter
142
143 def affine_encipher(message, multiplier=1, adder=0, one_based=True):
144 """Encipher a message
145
146 >>> affine_encipher('hours passed during which jerico tried every ' \
147 'trick he could think of', 15, 22, True)
148 'lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg jfaoe ls omytd jlaxe mh'
149 """
150 enciphered = [affine_encipher_letter(l, multiplier, adder, one_based)
151 for l in message]
152 return ''.join(enciphered)
153
154 def affine_decipher(message, multiplier=1, adder=0, one_based=True):
155 """Decipher a message
156
157 >>> affine_decipher('lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg ' \
158 'jfaoe ls omytd jlaxe mh', 15, 22, True)
159 'hours passed during which jerico tried every trick he could think of'
160 """
161 enciphered = [affine_decipher_letter(l, multiplier, adder, one_based)
162 for l in message]
163 return ''.join(enciphered)
164
165
166
167 if __name__ == "__main__":
168 import doctest
169 doctest.testmod()