9c521b94e2385bc4292c1519a3b8a75c7be9263e
[cipher-tools.git] / cipher / affine.py
1 from support.utilities import *
2 from support.language_models import *
3 from logger import logger
4
5
6 # modular_division_table = [[0]*26 for _ in range(26)]
7 # for a in range(26):
8 # for b in range(26):
9 # c = (a * b) % 26
10 # modular_division_table[b][c] = a
11
12
13 modular_division_table = {
14 (multiplier, (multiplier * plaintext) % 26): plaintext
15 for plaintext in range(26)
16 for multiplier in range(26)
17 }
18
19
20
21 def affine_encipher_letter(accented_letter, multiplier=1, adder=0, one_based=True):
22 """Encipher a letter, given a multiplier and adder
23
24 >>> cat(affine_encipher_letter(l, 3, 5, True) \
25 for l in string.ascii_letters)
26 'hknqtwzcfiloruxadgjmpsvybeHKNQTWZCFILORUXADGJMPSVYBE'
27 >>> cat(affine_encipher_letter(l, 3, 5, False) \
28 for l in string.ascii_letters)
29 'filoruxadgjmpsvybehknqtwzcFILORUXADGJMPSVYBEHKNQTWZC'
30 """
31 # letter = unaccent(accented_letter)
32 # if letter in string.ascii_letters:
33 # if letter in string.ascii_uppercase:
34 # alphabet_start = ord('A')
35 # else:
36 # alphabet_start = ord('a')
37 # letter_number = ord(letter) - alphabet_start
38 # if one_based: letter_number += 1
39 # cipher_number = (letter_number * multiplier + adder) % 26
40 # if one_based: cipher_number -= 1
41 # return chr(cipher_number % 26 + alphabet_start)
42 # else:
43 # return letter
44 letter = unaccent(accented_letter)
45 if letter in string.ascii_letters:
46 letter_number = pos(letter)
47 if one_based: letter_number += 1
48 cipher_number = (letter_number * multiplier + adder) % 26
49 if one_based: cipher_number -= 1
50 if letter in string.ascii_uppercase:
51 return unpos(cipher_number).upper()
52 else:
53 return unpos(cipher_number)
54 else:
55 return letter
56
57 def affine_decipher_letter(letter, multiplier=1, adder=0, one_based=True):
58 """Encipher a letter, given a multiplier and adder
59
60 >>> cat(affine_decipher_letter(l, 3, 5, True) \
61 for l in 'hknqtwzcfiloruxadgjmpsvybeHKNQTWZCFILORUXADGJMPSVYBE')
62 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
63 >>> cat(affine_decipher_letter(l, 3, 5, False) \
64 for l in 'filoruxadgjmpsvybehknqtwzcFILORUXADGJMPSVYBEHKNQTWZC')
65 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
66 """
67 # if letter in string.ascii_letters:
68 # if letter in string.ascii_uppercase:
69 # alphabet_start = ord('A')
70 # else:
71 # alphabet_start = ord('a')
72 # cipher_number = ord(letter) - alphabet_start
73 # if one_based: cipher_number += 1
74 # plaintext_number = (
75 # modular_division_table[multiplier]
76 # [(cipher_number - adder) % 26])
77 # if one_based: plaintext_number -= 1
78 # return chr(plaintext_number % 26 + alphabet_start)
79 # else:
80 # return letter
81 if letter in string.ascii_letters:
82 cipher_number = pos(letter)
83 if one_based: cipher_number += 1
84 # plaintext_number = (
85 # modular_division_table[multiplier]
86 # [(cipher_number - adder) % 26])
87 plaintext_number = (
88 modular_division_table[multiplier, (cipher_number - adder) % 26]
89 )
90 if one_based: plaintext_number -= 1
91 if letter in string.ascii_uppercase:
92 return unpos(plaintext_number).upper()
93 else:
94 return unpos(plaintext_number)
95 else:
96 return letter
97
98 def affine_encipher(message, multiplier=1, adder=0, one_based=True):
99 """Encipher a message
100
101 >>> affine_encipher('hours passed during which jerico tried every ' \
102 'trick he could think of', 15, 22, True)
103 'lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg jfaoe ls omytd jlaxe mh'
104 """
105 enciphered = [affine_encipher_letter(l, multiplier, adder, one_based)
106 for l in message]
107 return cat(enciphered)
108
109 def affine_decipher(message, multiplier=1, adder=0, one_based=True):
110 """Decipher a message
111
112 >>> affine_decipher('lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg ' \
113 'jfaoe ls omytd jlaxe mh', 15, 22, True)
114 'hours passed during which jerico tried every trick he could think of'
115 """
116 enciphered = [affine_decipher_letter(l, multiplier, adder, one_based)
117 for l in message]
118 return cat(enciphered)
119
120
121
122 def affine_break(message, fitness=Pletters):
123 """Breaks an affine cipher using frequency analysis
124
125 >>> affine_break('lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg jfaoe ls ' \
126 'omytd jlaxe mh jm bfmibj umis hfsul axubafkjamx. ls kffkxwsd jls ' \
127 'ofgbjmwfkiu olfmxmtmwaokttg jlsx ls kffkxwsd jlsi zg tsxwjl. jlsx ' \
128 'ls umfjsd jlsi zg hfsqysxog. ls dmmdtsd mx jls bats mh bkbsf. ls ' \
129 'bfmctsd kfmyxd jls lyj, mztanamyu xmc jm clm cku tmmeaxw kj lai ' \
130 'kxd clm ckuxj.') # doctest: +ELLIPSIS
131 ((15, 22, True), -340.601181913...)
132 """
133 sanitised_message = sanitise(message)
134 best_multiplier = 0
135 best_adder = 0
136 best_one_based = True
137 best_fit = float("-inf")
138 for one_based in [True, False]:
139 for multiplier in [x for x in range(1, 26, 2) if x != 13]:
140 for adder in range(26):
141 plaintext = affine_decipher(sanitised_message,
142 multiplier, adder, one_based)
143 fit = fitness(plaintext)
144 logger.debug('Affine break attempt using key {0}x+{1} ({2}) '
145 'gives fit of {3} and decrypt starting: {4}'.
146 format(multiplier, adder, one_based, fit,
147 plaintext[:50]))
148 if fit > best_fit:
149 best_fit = fit
150 best_multiplier = multiplier
151 best_adder = adder
152 best_one_based = one_based
153 logger.info('Affine break best fit with key {0}x+{1} ({2}) gives fit of '
154 '{3} and decrypt starting: {4}'.format(
155 best_multiplier, best_adder, best_one_based, best_fit,
156 affine_decipher(sanitised_message, best_multiplier,
157 best_adder, best_one_based)[:50]))
158 return (best_multiplier, best_adder, best_one_based), best_fit