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