0ef50dfd53e384479f5a8428c4760c673c1874ae
[szyfrow.git] / affine.py
1 from szyfrow.support.utilities import *
2 from szyfrow.support.language_models import *
3
4 modular_division_table = {
5 (multiplier, (multiplier * plaintext) % 26): plaintext
6 for plaintext in range(26)
7 for multiplier in range(26)
8 }
9
10
11 def affine_encipher_letter(accented_letter, multiplier=1, adder=0, one_based=True):
12 """Encipher a letter, given a multiplier and adder
13
14 >>> cat(affine_encipher_letter(l, 3, 5, True) \
15 for l in string.ascii_letters)
16 'hknqtwzcfiloruxadgjmpsvybeHKNQTWZCFILORUXADGJMPSVYBE'
17 >>> cat(affine_encipher_letter(l, 3, 5, False) \
18 for l in string.ascii_letters)
19 'filoruxadgjmpsvybehknqtwzcFILORUXADGJMPSVYBEHKNQTWZC'
20 """
21 letter = unaccent(accented_letter)
22 if letter in string.ascii_letters:
23 letter_number = pos(letter)
24 if one_based: letter_number += 1
25 cipher_number = (letter_number * multiplier + adder) % 26
26 if one_based: cipher_number -= 1
27 if letter in string.ascii_uppercase:
28 return unpos(cipher_number).upper()
29 else:
30 return unpos(cipher_number)
31 else:
32 return letter
33
34 def affine_decipher_letter(letter, multiplier=1, adder=0, one_based=True):
35 """Encipher a letter, given a multiplier and adder
36
37 >>> cat(affine_decipher_letter(l, 3, 5, True) \
38 for l in 'hknqtwzcfiloruxadgjmpsvybeHKNQTWZCFILORUXADGJMPSVYBE')
39 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
40 >>> cat(affine_decipher_letter(l, 3, 5, False) \
41 for l in 'filoruxadgjmpsvybehknqtwzcFILORUXADGJMPSVYBEHKNQTWZC')
42 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
43 """
44 if letter in string.ascii_letters:
45 cipher_number = pos(letter)
46 if one_based: cipher_number += 1
47 # plaintext_number = (
48 # modular_division_table[multiplier]
49 # [(cipher_number - adder) % 26])
50 plaintext_number = (
51 modular_division_table[multiplier, (cipher_number - adder) % 26]
52 )
53 if one_based: plaintext_number -= 1
54 if letter in string.ascii_uppercase:
55 return unpos(plaintext_number).upper()
56 else:
57 return unpos(plaintext_number)
58 else:
59 return letter
60
61 def affine_encipher(message, multiplier=1, adder=0, one_based=True):
62 """Encipher a message
63
64 >>> affine_encipher('hours passed during which jerico tried every ' \
65 'trick he could think of', 15, 22, True)
66 'lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg jfaoe ls omytd jlaxe mh'
67 """
68 enciphered = [affine_encipher_letter(l, multiplier, adder, one_based)
69 for l in message]
70 return cat(enciphered)
71
72 def affine_decipher(message, multiplier=1, adder=0, one_based=True):
73 """Decipher a message
74
75 >>> affine_decipher('lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg ' \
76 'jfaoe ls omytd jlaxe mh', 15, 22, True)
77 'hours passed during which jerico tried every trick he could think of'
78 """
79 enciphered = [affine_decipher_letter(l, multiplier, adder, one_based)
80 for l in message]
81 return cat(enciphered)
82
83
84
85 def affine_break(message, fitness=Pletters):
86 """Breaks an affine cipher using frequency analysis
87
88 >>> affine_break('lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg jfaoe ls ' \
89 'omytd jlaxe mh jm bfmibj umis hfsul axubafkjamx. ls kffkxwsd jls ' \
90 'ofgbjmwfkiu olfmxmtmwaokttg jlsx ls kffkxwsd jlsi zg tsxwjl. jlsx ' \
91 'ls umfjsd jlsi zg hfsqysxog. ls dmmdtsd mx jls bats mh bkbsf. ls ' \
92 'bfmctsd kfmyxd jls lyj, mztanamyu xmc jm clm cku tmmeaxw kj lai ' \
93 'kxd clm ckuxj.') # doctest: +ELLIPSIS
94 ((15, 22, True), -340.601181913...)
95 """
96 sanitised_message = sanitise(message)
97 best_multiplier = 0
98 best_adder = 0
99 best_one_based = True
100 best_fit = float("-inf")
101 for one_based in [True, False]:
102 for multiplier in [x for x in range(1, 26, 2) if x != 13]:
103 for adder in range(26):
104 plaintext = affine_decipher(sanitised_message,
105 multiplier, adder, one_based)
106 fit = fitness(plaintext)
107 if fit > best_fit:
108 best_fit = fit
109 best_multiplier = multiplier
110 best_adder = adder
111 best_one_based = one_based
112
113 return (best_multiplier, best_adder, best_one_based), best_fit