Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/crypto/classical.py
4045 views
1
r"""
2
Classical Cryptosystems
3
4
A convenient user interface to various classical ciphers. These include:
5
6
- affine cipher; see :class:`AffineCryptosystem`
7
- Hill or matrix cipher; see :class:`HillCryptosystem`
8
- shift cipher; see :class:`ShiftCryptosystem`
9
- substitution cipher; see :class:`SubstitutionCryptosystem`
10
- transposition cipher; see :class:`TranspositionCryptosystem`
11
- Vigenere cipher; see :class:`VigenereCryptosystem`
12
13
These classical cryptosystems support alphabets such as:
14
15
- the capital letters of the English alphabet; see
16
:func:`AlphabeticStrings() <sage.monoids.string_monoid.AlphabeticStrings>`
17
- the hexadecimal number system; see
18
:func:`HexadecimalStrings() <sage.monoids.string_monoid.HexadecimalStrings>`
19
- the binary number system; see
20
:func:`BinaryStrings() <sage.monoids.string_monoid.BinaryStrings>`
21
- the octal number system; see
22
:func:`OctalStrings() <sage.monoids.string_monoid.OctalStrings>`
23
- the radix-64 number system; see
24
:func:`Radix64Strings() <sage.monoids.string_monoid.Radix64Strings>`
25
26
AUTHORS:
27
28
- David Kohel (2007): initial version with the Hill, substitution,
29
transposition, and Vigenere cryptosystems.
30
31
- Minh Van Nguyen (2009-08): shift cipher, affine cipher
32
"""
33
34
#*****************************************************************************
35
# Copyright (C) 2007 David Kohel <[email protected]>
36
#
37
# Distributed under the terms of the GNU General Public License (GPL)
38
#
39
# http://www.gnu.org/licenses/
40
#*****************************************************************************
41
42
# TODO: check off this todo list:
43
# - methods to cryptanalyze the Hill, substitution, transposition, and
44
# Vigenere ciphers
45
46
from sage.monoids.string_monoid import (
47
StringMonoid_class,
48
AlphabeticStringMonoid)
49
from sage.monoids.string_monoid_element import StringMonoidElement
50
from sage.monoids.string_ops import strip_encoding
51
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
52
from sage.groups.perm_gps.permgroup_element import PermutationGroupElement
53
from sage.rings.integer import Integer
54
from sage.rings.integer_ring import ZZ
55
from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
56
from sage.rings.arith import xgcd
57
from random import randint
58
from sage.matrix.matrix_space import MatrixSpace
59
60
from cryptosystem import SymmetricKeyCryptosystem
61
from classical_cipher import (
62
AffineCipher,
63
HillCipher,
64
ShiftCipher,
65
SubstitutionCipher,
66
TranspositionCipher,
67
VigenereCipher)
68
69
class AffineCryptosystem(SymmetricKeyCryptosystem):
70
r"""
71
Create an affine cryptosystem.
72
73
Let `A = \{ a_0, a_1, a_2, \dots, a_{n-1} \}` be a non-empty alphabet
74
consisting of `n` unique elements. Define a mapping
75
`f : A \longrightarrow \ZZ / n\ZZ` from the alphabet `A` to
76
the set `\ZZ / n\ZZ` of integers modulo `n`, given by
77
`f(a_i) = i`. Thus we can identify each element of the alphabet `A`
78
with a unique integer `0 \leq i < n`. A key of the affine cipher is an
79
ordered pair of integers `(a, b) \in \ZZ / n\ZZ \times \ZZ / n\ZZ` such
80
that `\gcd(a, n) = 1`. Therefore the key space is
81
`\ZZ / n\ZZ \times \ZZ / n\ZZ`. Since we assume that `A` does not have
82
repeated elements, the mapping `f : A \longrightarrow \ZZ/ n\ZZ` is
83
bijective. Encryption and decryption functions are both affine functions.
84
Let `(a,b)` be a secret key, i.e. an element of the key space, and let
85
`p` be a plaintext character and consequently `p \in \ZZ / n\ZZ`. Then
86
the ciphertext character `c` corresponding to `p` is given by
87
88
.. MATH::
89
90
c \equiv ap + b \pmod{n}
91
92
Similarly, given a ciphertext character `c \in \ZZ / n\ZZ` and a secret
93
key `(a,b)`, we can recover the corresponding plaintext character as
94
follows:
95
96
.. MATH::
97
98
p \equiv a^{-1} (c - b) \pmod{n}
99
100
where `a^{-1}` is the inverse of `a` modulo `n`. Use the bijection
101
`f : A \longrightarrow \ZZ / n\ZZ` to convert `c` and `p` back to
102
elements of the alphabet `A`. Currently, only the following alphabet is
103
supported for the affine cipher:
104
105
- capital letters of the English alphabet as implemented in
106
:func:`AlphabeticStrings()
107
<sage.monoids.string_monoid.AlphabeticStrings>`
108
109
EXAMPLES:
110
111
Encryption and decryption over the capital letters of the English
112
alphabet::
113
114
sage: A = AffineCryptosystem(AlphabeticStrings()); A
115
Affine cryptosystem on Free alphabetic string monoid on A-Z
116
sage: P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
117
sage: P
118
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
119
sage: a, b = (9, 13)
120
sage: C = A.enciphering(a, b, P); C
121
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
122
sage: A.deciphering(a, b, C)
123
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
124
sage: A.deciphering(a, b, C) == P
125
True
126
127
We can also use functional notation to work through the previous
128
example::
129
130
sage: A = AffineCryptosystem(AlphabeticStrings()); A
131
Affine cryptosystem on Free alphabetic string monoid on A-Z
132
sage: P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
133
sage: P
134
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
135
sage: a, b = (9, 13)
136
sage: E = A(a, b); E
137
Affine cipher on Free alphabetic string monoid on A-Z
138
sage: C = E(P); C
139
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
140
sage: aInv, bInv = A.inverse_key(a, b)
141
sage: D = A(aInv, bInv); D
142
Affine cipher on Free alphabetic string monoid on A-Z
143
sage: D(C)
144
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
145
sage: D(C) == P
146
True
147
sage: D(C) == P == D(E(P))
148
True
149
150
Encrypting the ciphertext with the inverse key also produces the
151
plaintext::
152
153
sage: A = AffineCryptosystem(AlphabeticStrings())
154
sage: P = A.encoding("Encrypt with inverse key.")
155
sage: a, b = (11, 8)
156
sage: C = A.enciphering(a, b, P)
157
sage: P; C
158
ENCRYPTWITHINVERSEKEY
159
AVENMRJQSJHSVFANYAOAM
160
sage: aInv, bInv = A.inverse_key(a, b)
161
sage: A.enciphering(aInv, bInv, C)
162
ENCRYPTWITHINVERSEKEY
163
sage: A.enciphering(aInv, bInv, C) == P
164
True
165
166
For a secret key `(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ`, if `a = 1` then
167
any affine cryptosystem with key `(1, b)` for any `b \in \ZZ/n\ZZ` is
168
a shift cryptosystem. Here is how we can create a Caesar cipher using
169
an affine cipher::
170
171
sage: caesar = AffineCryptosystem(AlphabeticStrings())
172
sage: a, b = (1, 3)
173
sage: P = caesar.encoding("abcdef"); P
174
ABCDEF
175
sage: C = caesar.enciphering(a, b, P); C
176
DEFGHI
177
sage: caesar.deciphering(a, b, C) == P
178
True
179
180
Any affine cipher with keys of the form
181
`(a,0) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` is called a decimation cipher on
182
the Roman alphabet, or decimation cipher for short::
183
184
sage: A = AffineCryptosystem(AlphabeticStrings())
185
sage: P = A.encoding("A decimation cipher is a specialized affine cipher.")
186
sage: a, b = (17, 0)
187
sage: C = A.enciphering(a, b, P)
188
sage: P; C
189
ADECIMATIONCIPHERISASPECIALIZEDAFFINECIPHER
190
AZQIGWALGENIGVPQDGUAUVQIGAFGJQZAHHGNQIGVPQD
191
sage: A.deciphering(a, b, C) == P
192
True
193
194
Generate a random key for encryption and decryption::
195
196
sage: A = AffineCryptosystem(AlphabeticStrings())
197
sage: P = A.encoding("An affine cipher with a random key.")
198
sage: a, b = A.random_key()
199
sage: C = A.enciphering(a, b, P)
200
sage: A.deciphering(a, b, C) == P
201
True
202
203
TESTS:
204
205
The binary number system is currently not a supported alphabet of
206
this affine cryptosystem::
207
208
sage: AffineCryptosystem(BinaryStrings())
209
Traceback (most recent call last):
210
...
211
TypeError: A (= Free binary string monoid) is not supported as a cipher domain of this affine cryptosystem.
212
213
Nor are the octal, hexadecimal, and radix-64 number systems supported::
214
215
sage: AffineCryptosystem(OctalStrings())
216
Traceback (most recent call last):
217
...
218
TypeError: A (= Free octal string monoid) is not supported as a cipher domain of this affine cryptosystem.
219
sage: AffineCryptosystem(HexadecimalStrings())
220
Traceback (most recent call last):
221
...
222
TypeError: A (= Free hexadecimal string monoid) is not supported as a cipher domain of this affine cryptosystem.
223
sage: AffineCryptosystem(Radix64Strings())
224
Traceback (most recent call last):
225
...
226
TypeError: A (= Free radix 64 string monoid) is not supported as a cipher domain of this affine cryptosystem.
227
228
A secret key `(a,b)` must be an element of `\ZZ/n\ZZ \times \ZZ/n\ZZ` with
229
`\gcd(a,n) = 1`. This rules out the case `a = 0` irrespective of the
230
value of `b`. For the upper-case letters of the English alphabet, where
231
the alphabet size is `n = 26`, `a` cannot take on any even value::
232
233
sage: A = AffineCryptosystem(AlphabeticStrings())
234
sage: A(0, 1)
235
Traceback (most recent call last):
236
...
237
ValueError: (a, b) = (0, 1) is outside the range of acceptable values for a key of this affine cryptosystem.
238
sage: A(2, 1)
239
Traceback (most recent call last):
240
...
241
ValueError: (a, b) = (2, 1) is outside the range of acceptable values for a key of this affine cryptosystem.
242
243
REFERENCES:
244
245
.. [Sti06] Douglas R. Stinson. *Cryptography: Theory and Practice*.
246
3rd edition, Chapman \& Hall/CRC, 2006.
247
"""
248
249
def __init__(self, A):
250
r"""
251
See ``AffineCryptosystem`` for full documentation.
252
253
INPUT:
254
255
- ``A`` -- a string monoid over some alphabet; this is the non-empty
256
alphabet over which the plaintext and ciphertext spaces
257
are defined.
258
259
OUTPUT:
260
261
- An affine cryptosystem over the alphabet ``A``.
262
263
EXAMPLES:
264
265
Testing of dumping and loading objects::
266
267
sage: A = AffineCryptosystem(AlphabeticStrings())
268
sage: A == loads(dumps(A))
269
True
270
"""
271
# sanity check
272
if not isinstance(A, AlphabeticStringMonoid):
273
raise TypeError("A (= %s) is not supported as a cipher domain of this affine cryptosystem." % A)
274
# List L of invertible linear coefficients modulo n, where n is the
275
# alphabet size. Each e in L satisfies gcd(e, n) = 1.
276
from sage.rings.arith import gcd
277
n = A.ngens()
278
self._invertible_A = [i for i in xrange(n) if gcd(i, n) == 1]
279
# Initialize the affine cryptosystem with the plaintext, ciphertext,
280
# and key spaces.
281
SymmetricKeyCryptosystem.__init__(
282
self, A, A,
283
key_space=(IntegerModRing(A.ngens()), IntegerModRing(A.ngens())))
284
285
def __call__(self, a, b):
286
r"""
287
Create an affine cipher with secret key ``(a,b)``.
288
289
INPUT:
290
291
- ``(a,b)`` -- a secret key; this key is used for both encryption and
292
decryption. For the affine cryptosystem whose plaintext and
293
ciphertext spaces are `A`, a key is an ordered pair
294
`(a,b) \in \ZZ / n\ZZ \times \ZZ / n\ZZ` where `n` is the size or
295
cardinality of the set `A` and `\gcd(a,n) = 1`.
296
297
OUTPUT:
298
299
- An affine cipher with secret key ``(a,b)``.
300
301
EXAMPLES::
302
303
sage: A = AffineCryptosystem(AlphabeticStrings())
304
sage: P = A.encoding("Fine here, fine there."); P
305
FINEHEREFINETHERE
306
sage: a, b = (17, 3)
307
sage: E = A(a, b); E
308
Affine cipher on Free alphabetic string monoid on A-Z
309
sage: E(P)
310
KJQTSTGTKJQTOSTGT
311
sage: C = E(P)
312
sage: C
313
KJQTSTGTKJQTOSTGT
314
sage: aInv, bInv = A.inverse_key(a, b)
315
sage: D = A(aInv, bInv); D
316
Affine cipher on Free alphabetic string monoid on A-Z
317
sage: P == D(C)
318
True
319
sage: D(E(P))
320
FINEHEREFINETHERE
321
322
TESTS:
323
324
The key must be an ordered pair
325
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of the
326
plaintext and ciphertext spaces. Furthermore, `a` must be
327
relatively prime to `n`, i.e. `\gcd(a,n) = 1`::
328
329
sage: A = AffineCryptosystem(AlphabeticStrings())
330
sage: A(2, 3)
331
Traceback (most recent call last):
332
...
333
ValueError: (a, b) = (2, 3) is outside the range of acceptable values for a key of this affine cryptosystem.
334
"""
335
# Sanity check: the key K = (a,b) must be an element of
336
# ZZ/nZZ x ZZ/nZZ where n is the size of the plaintext and ciphertext
337
# spaces. For the affine cryptosystem, these two spaces are the
338
# same alphabet.
339
try:
340
n = self.alphabet_size()
341
# If a is an element of the multiplicative group G of ZZ/nZZ, then
342
# gcd(a,n) = 1. So here we don't need to explicitly test that
343
# a is coprime to n since we assume that the list
344
# self._invertible_A contains all the elements of G.
345
if (a in self._invertible_A) and (0 <= b < n):
346
return AffineCipher(self, key=(a,b))
347
else:
348
raise ValueError
349
except:
350
raise ValueError("(a, b) = (%s, %s) is outside the range of acceptable values for a key of this affine cryptosystem." % (a, b))
351
352
def _repr_(self):
353
r"""
354
Return the string representation of ``self``.
355
356
EXAMPLES::
357
358
sage: A = AffineCryptosystem(AlphabeticStrings()); A
359
Affine cryptosystem on Free alphabetic string monoid on A-Z
360
"""
361
# The affine cipher has the plaintext and ciphertext spaces defined
362
# over the same non-empty alphabet. The cipher domain is the same
363
# as the alphabet used for the plaintext and ciphertext spaces.
364
return "Affine cryptosystem on %s" % self.cipher_domain()
365
366
def rank_by_chi_square(self, C, pdict):
367
r"""
368
Use the chi-square statistic to rank all possible keys. Currently,
369
this method only applies to the capital letters of the English
370
alphabet.
371
372
ALGORITHM:
373
374
Consider a non-empty alphabet `A` consisting of `n`
375
elements, and let `C` be a ciphertext encoded using elements of
376
`A`. The plaintext `P` corresponding to `C` is also encoded using
377
elements of `A`. Let `M` be a candidate decipherment of `C`,
378
i.e. `M` is the result of attempting to decrypt `C` using a key
379
`(a,b)` which is not necessarily the same key used to encrypt `P`.
380
Suppose `F_A(e)` is the characteristic frequency probability of
381
`e \in A` and let `F_M(e)` be the message frequency probability with
382
respect to `M`. The characteristic frequency probability
383
distribution of an alphabet is the expected frequency probability
384
distribution for that alphabet. The message frequency probability
385
distribution of `M` provides a distribution of the ratio of character
386
occurrences over message length. One can interpret the
387
characteristic frequency probability `F_A(e)` as the expected
388
probability, while the message frequency probability `F_M(e)` is
389
the observed probability. If `M` is of length `L`, then the observed
390
frequency of `e \in A` is
391
392
.. MATH::
393
394
O_M(e)
395
=
396
F_M(e) \cdot L
397
398
and the expected frequency of `e \in A` is
399
400
.. MATH::
401
402
E_A(e)
403
=
404
F_A(e) \cdot L
405
406
The chi-square rank `R_{\chi^2}(M)` of `M` corresponding to a key
407
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` is given by
408
409
.. MATH::
410
411
R_{\chi^2}(M)
412
=
413
\sum_{e \in A} \frac {\big( O_M(e) - E_A(e) \big)^2}
414
{E_A(e)}
415
416
Cryptanalysis by exhaustive key search produces a candidate
417
decipherment `M_{a,b}` for each possible key `(a,b)`. For a set
418
`D = \big\{M_{a_1,b_1}, M_{a_2,b_2}, \dots, M_{a_k,b_k} \big\}`
419
of all candidate decipherments corresponding to a ciphertext `C`,
420
the smaller is the rank `R_{\chi^2}(M_{a_i,b_i})` the more likely
421
that `(a_i,b_i)` is the secret key. This key ranking method is
422
based on the Pearson chi-square test [PearsonTest09]_.
423
424
INPUT:
425
426
- ``C`` -- The ciphertext, a non-empty string. The ciphertext
427
must be encoded using the upper-case letters of the English
428
alphabet.
429
430
- ``pdict`` -- A dictionary of key, possible plaintext
431
pairs. This should be the output of :func:`brute_force` with
432
``ranking="none"``.
433
434
OUTPUT:
435
436
- A list ranking the most likely keys first. Each element of the
437
list is a tuple of key, possible plaintext pairs.
438
439
EXAMPLES:
440
441
Use the chi-square statistic to rank all possible keys and their
442
corresponding decipherment::
443
444
sage: A = AffineCryptosystem(AlphabeticStrings())
445
sage: a, b = (3, 7)
446
sage: P = A.encoding("Line.")
447
sage: C = A.enciphering(a, b, P)
448
sage: Plist = A.brute_force(C)
449
sage: Rank = A.rank_by_chi_square(C, Plist)
450
sage: Rank[:10] # display only the top 10 candidate keys
451
<BLANKLINE>
452
[((1, 1), NETS),
453
((3, 7), LINE),
454
((17, 20), STAD),
455
((5, 2), SLOT),
456
((5, 5), HADI),
457
((9, 25), TSLI),
458
((17, 15), DELO),
459
((15, 6), ETUN),
460
((21, 8), ELID),
461
((7, 17), HCTE)]
462
463
As more ciphertext is available, the reliability of the chi-square
464
ranking function increases::
465
466
sage: A = AffineCryptosystem(AlphabeticStrings())
467
sage: a, b = (11, 24)
468
sage: P = A.encoding("Longer message is more information for cryptanalysis.")
469
sage: C = A.enciphering(a, b, P)
470
sage: Plist = A.brute_force(C)
471
sage: Rank = A.rank_by_chi_square(C, Plist)
472
sage: Rank[:10] # display only the top 10 candidate keys
473
<BLANKLINE>
474
[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
475
((17, 9), INURFSBFLLHRFDLBNSFDUYNSBHEDNUYNSTSVGEHUHIVLDL),
476
((9, 18), RMFIUHYUOOSIUWOYMHUWFBMHYSVWMFBMHGHETVSFSREOWO),
477
((15, 12), VSTACPUCOOGACYOUSPCYTBSPUGNYSTBSPEPIRNGTGVIOYO),
478
((3, 22), PAFOYLKYGGSOYEGKALYEFTALKSBEAFTALILCVBSFSPCGEG),
479
((25, 3), OHSRNADNPPFRNVPDHANVSCHADFEVHSCHAJABWEFSFOBPVP),
480
((7, 25), GHYNVIPVRRLNVFRPHIVFYEHIPLAFHYEHIDITQALYLGTRFR),
481
((5, 2), NEHCIVKISSUCIWSKEVIWHFEVKUPWEHFEVOVABPUHUNASWS),
482
((15, 25), IFGNPCHPBBTNPLBHFCPLGOFCHTALFGOFCRCVEATGTIVBLB),
483
((9, 6), BWPSERIEYYCSEGYIWREGPLWRICFGWPLWRQRODFCPCBOYGY)]
484
485
TESTS:
486
487
The ciphertext cannot be an empty string::
488
489
sage: A.rank_by_chi_square("", Plist)
490
Traceback (most recent call last):
491
...
492
AttributeError: 'str' object has no attribute 'parent'
493
sage: A.rank_by_chi_square(A.encoding(""), Plist)
494
Traceback (most recent call last):
495
...
496
ValueError: The ciphertext must be a non-empty string.
497
sage: A.rank_by_chi_square(A.encoding(" "), Plist)
498
Traceback (most recent call last):
499
...
500
ValueError: The ciphertext must be a non-empty string.
501
502
The ciphertext must be encoded using the capital letters of the
503
English alphabet as implemented in
504
:func:`AlphabeticStrings()
505
<sage.monoids.string_monoid.AlphabeticStrings>`::
506
507
sage: H = HexadecimalStrings()
508
sage: A.rank_by_chi_square(H.encoding("shift"), Plist)
509
Traceback (most recent call last):
510
...
511
TypeError: The ciphertext must be capital letters of the English alphabet.
512
sage: B = BinaryStrings()
513
sage: A.rank_by_chi_square(B.encoding("shift"), Plist)
514
Traceback (most recent call last):
515
...
516
TypeError: The ciphertext must be capital letters of the English alphabet.
517
518
The dictionary ``pdict`` cannot be empty::
519
520
sage: A.rank_by_chi_square(C, {})
521
Traceback (most recent call last):
522
...
523
KeyError: (1, 0)
524
"""
525
# NOTE: the code here is very similar to that in the method
526
# rank_by_chi_square() of the class ShiftCryptosystem. The most
527
# significant change in the code below is in how the secret key (a,b)
528
# is processed.
529
530
# sanity check
531
from sage.monoids.string_monoid import AlphabeticStrings
532
if not isinstance(C.parent(), AlphabeticStringMonoid):
533
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
534
if str(C) == "":
535
raise ValueError("The ciphertext must be a non-empty string.")
536
537
# compute the rank of each key
538
AS = AlphabeticStrings()
539
# the alphabet in question
540
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
541
StrAlph = str(Alph)
542
# message length
543
L = len(C)
544
# expected frequency tally
545
EA = AS.characteristic_frequency()
546
for e in EA:
547
EA[e] *= L
548
# Compute the rank R_{chi^2}(M) of M with secret key (a,b).
549
Rank = []
550
for a in self._invertible_A:
551
for b in xrange(self.alphabet_size()):
552
# observed frequency tally
553
OM = pdict[(a, b)].frequency_distribution().function()
554
for e in Alph:
555
if e in OM:
556
OM[e] *= L
557
else:
558
OM.setdefault(e, 0.0)
559
# the rank R_{chi^2}(M) of M with secret key (a,b)
560
RMab = [(OM[AS(e)] - EA[e])**2 / EA[e] for e in StrAlph]
561
Rank.append((sum(RMab), (a, b)))
562
# Sort in non-decreasing order of chi-square statistic. It's
563
# possible that two different keys share the same chi-square
564
# statistic.
565
Rank = sorted(Rank)
566
RankedList = []
567
# NOTE: each secret key is a tuple (a,b). So key[0] indexes a,
568
# and key[1] indexes b. The value of val is not used at all, making
569
# it redundant to access val in the first place. The following line
570
# of code is written with readability in mind.
571
[RankedList.append((key, pdict[(key[0], key[1])]))
572
for val, key in Rank]
573
return RankedList
574
575
def rank_by_squared_differences(self, C, pdict):
576
r"""
577
Use the squared-differences measure to rank all possible keys.
578
Currently, this method only applies to the capital letters of
579
the English alphabet.
580
581
ALGORITHM:
582
583
Consider a non-empty alphabet `A` consisting of `n`
584
elements, and let `C` be a ciphertext encoded using elements of
585
`A`. The plaintext `P` corresponding to `C` is also encoded using
586
elements of `A`. Let `M` be a candidate decipherment of `C`,
587
i.e. `M` is the result of attempting to decrypt `C` using a key
588
`(a,b)` which is not necessarily the same key used to encrypt `P`.
589
Suppose `F_A(e)` is the characteristic frequency probability of
590
`e \in A` and let `F_M(e)` be the message frequency probability with
591
respect to `M`. The characteristic frequency probability
592
distribution of an alphabet is the expected frequency probability
593
distribution for that alphabet. The message frequency probability
594
distribution of `M` provides a distribution of the ratio of character
595
occurrences over message length. One can interpret the
596
characteristic frequency probability `F_A(e)` as the expected
597
probability, while the message frequency probability `F_M(e)` is
598
the observed probability. If `M` is of length `L`, then the observed
599
frequency of `e \in A` is
600
601
.. MATH::
602
603
O_M(e)
604
=
605
F_M(e) \cdot L
606
607
and the expected frequency of `e \in A` is
608
609
.. MATH::
610
611
E_A(e)
612
=
613
F_A(e) \cdot L
614
615
The squared-differences, or residual sum of squares, rank
616
`R_{RSS}(M)` of `M` corresponding to a key
617
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` is given by
618
619
.. MATH::
620
621
R_{RSS}(M)
622
=
623
\sum_{e \in A} \big( O_M(e) - E_A(e) \big)^2
624
625
Cryptanalysis by exhaustive key search produces a candidate
626
decipherment `M_{a,b}` for each possible key `(a,b)`. For a set
627
`D = \big\{M_{a_1,b_1}, M_{a_2,b_2}, \dots, M_{a_k,b_k} \big\}`
628
of all candidate decipherments corresponding to a ciphertext `C`,
629
the smaller is the rank `R_{RSS}(M_{a_i,b_i})` the more likely
630
that `(a_i,b_i)` is the secret key. This key ranking method is
631
based on the residual sum of squares measure [RSS09]_.
632
633
INPUT:
634
635
- ``C`` -- The ciphertext, a non-empty string. The ciphertext
636
must be encoded using the upper-case letters of the English
637
alphabet.
638
639
- ``pdict`` -- A dictionary of key, possible plaintext
640
pairs. This should be the output of :func:`brute_force` with
641
``ranking="none"``.
642
643
OUTPUT:
644
645
- A list ranking the most likely keys first. Each element of the
646
list is a tuple of key, possible plaintext pairs.
647
648
EXAMPLES:
649
650
Use the method of squared differences to rank all possible keys
651
and their corresponding decipherment::
652
653
sage: A = AffineCryptosystem(AlphabeticStrings())
654
sage: a, b = (3, 7)
655
sage: P = A.encoding("Line.")
656
sage: C = A.enciphering(a, b, P)
657
sage: Plist = A.brute_force(C)
658
sage: Rank = A.rank_by_squared_differences(C, Plist)
659
sage: Rank[:10] # display only the top 10 candidate keys
660
<BLANKLINE>
661
[((1, 1), NETS),
662
((15, 6), ETUN),
663
((7, 17), HCTE),
664
((3, 7), LINE),
665
((17, 15), DELO),
666
((9, 4), EDWT),
667
((9, 9), POHE),
668
((21, 8), ELID),
669
((17, 20), STAD),
670
((7, 18), SNEP)]
671
672
As more ciphertext is available, the reliability of the
673
squared-differences ranking function increases::
674
675
sage: A = AffineCryptosystem(AlphabeticStrings())
676
sage: a, b = (11, 24)
677
sage: P = A.encoding("Longer message is more information for cryptanalysis.")
678
sage: C = A.enciphering(a, b, P)
679
sage: Plist = A.brute_force(C)
680
sage: Rank = A.rank_by_squared_differences(C, Plist)
681
sage: Rank[:10] # display only the top 10 candidate keys
682
<BLANKLINE>
683
[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
684
((9, 14), DYRUGTKGAAEUGIAKYTGIRNYTKEHIYRNYTSTQFHEREDQAIA),
685
((23, 24), DSNEUHIUMMAEUOMISHUONZSHIAROSNZSHKHQXRANADQMOM),
686
((23, 1), ETOFVIJVNNBFVPNJTIVPOATIJBSPTOATILIRYSBOBERNPN),
687
((21, 16), VEBGANYAQQOGAMQYENAMBDENYOTMEBDENUNIHTOBOVIQMQ),
688
((7, 12), TULAIVCIEEYAISECUVISLRUVCYNSULRUVQVGDNYLYTGESE),
689
((5, 20), ZQTOUHWUEEGOUIEWQHUITRQHWGBIQTRQHAHMNBGTGZMEIE),
690
((21, 8), JSPUOBMOEECUOAEMSBOAPRSBMCHASPRSBIBWVHCPCJWEAE),
691
((25, 7), SLWVREHRTTJVRZTHLERZWGLEHJIZLWGLENEFAIJWJSFTZT),
692
((25, 15), ATEDZMPZBBRDZHBPTMZHEOTMPRQHTEOTMVMNIQRERANBHB)]
693
694
TESTS:
695
696
The ciphertext cannot be an empty string::
697
698
sage: A.rank_by_squared_differences("", Plist)
699
Traceback (most recent call last):
700
...
701
AttributeError: 'str' object has no attribute 'parent'
702
sage: A.rank_by_squared_differences(A.encoding(""), Plist)
703
Traceback (most recent call last):
704
...
705
ValueError: The ciphertext must be a non-empty string.
706
sage: A.rank_by_squared_differences(A.encoding(" "), Plist)
707
Traceback (most recent call last):
708
...
709
ValueError: The ciphertext must be a non-empty string.
710
711
The ciphertext must be encoded using the capital letters of the
712
English alphabet as implemented in
713
:func:`AlphabeticStrings()
714
<sage.monoids.string_monoid.AlphabeticStrings>`::
715
716
sage: H = HexadecimalStrings()
717
sage: A.rank_by_squared_differences(H.encoding("line"), Plist)
718
Traceback (most recent call last):
719
...
720
TypeError: The ciphertext must be capital letters of the English alphabet.
721
sage: B = BinaryStrings()
722
sage: A.rank_by_squared_differences(B.encoding("line"), Plist)
723
Traceback (most recent call last):
724
...
725
TypeError: The ciphertext must be capital letters of the English alphabet.
726
727
The dictionary ``pdict`` cannot be empty::
728
729
sage: A.rank_by_squared_differences(C, {})
730
Traceback (most recent call last):
731
...
732
KeyError: (1, 0)
733
"""
734
# NOTE: the code here is very similar to that in the method
735
# rank_by_squared_differences() of the class ShiftCryptosystem.
736
# The most significant change in the code below is in how the
737
# secret key (a,b) is processed.
738
739
# sanity check
740
from sage.monoids.string_monoid import AlphabeticStrings
741
if not isinstance(C.parent(), AlphabeticStringMonoid):
742
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
743
if str(C) == "":
744
raise ValueError("The ciphertext must be a non-empty string.")
745
746
# compute the rank of each key
747
AS = AlphabeticStrings()
748
# the alphabet in question
749
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
750
StrAlph = str(Alph)
751
# message length
752
L = len(C)
753
# expected frequency tally
754
EA = AS.characteristic_frequency()
755
for e in EA:
756
EA[e] *= L
757
# Compute the rank R_{RSS}(M) of M with secret key (a,b).
758
Rank = []
759
for a in self._invertible_A:
760
for b in xrange(self.alphabet_size()):
761
# observed frequency tally
762
OM = pdict[(a, b)].frequency_distribution().function()
763
for e in Alph:
764
if e in OM:
765
OM[e] *= L
766
else:
767
OM.setdefault(e, 0.0)
768
# the rank R_{RSS}(M) of M with secret key (a,b)
769
RMab = [(OM[AS(e)] - EA[e])**2 for e in StrAlph]
770
Rank.append((sum(RMab), (a, b)))
771
# Sort in non-decreasing order of squared-differences statistic. It's
772
# possible that two different keys share the same squared-differences
773
# statistic.
774
Rank = sorted(Rank)
775
RankedList = []
776
# NOTE: each secret key is a tuple (a,b). So key[0] indexes a,
777
# and key[1] indexes b. The value of val is not used at all, making
778
# it redundant to access val in the first place. The following line
779
# of code is written with readability in mind.
780
[RankedList.append((key, pdict[(key[0], key[1])]))
781
for val, key in Rank]
782
return RankedList
783
784
def brute_force(self, C, ranking="none"):
785
r"""
786
Attempt a brute force cryptanalysis of the ciphertext ``C``.
787
788
INPUT:
789
790
- ``C`` -- A ciphertext over one of the supported alphabets of this
791
affine cryptosystem. See the class :class:`AffineCryptosystem` for
792
documentation on the supported alphabets.
793
794
- ``ranking`` -- (default ``"none"``) the method to use for
795
ranking all possible keys. If ``ranking="none"``, then do not
796
use any ranking function. The following ranking functions are
797
supported:
798
799
- ``"chi_square"`` -- the chi-square ranking function
800
as implemented in the method :func:`rank_by_chi_square`.
801
802
- ``"squared_differences"`` -- the squared differences ranking
803
function as implemented in the method
804
:func:`rank_by_squared_differences`.
805
806
OUTPUT:
807
808
- All the possible plaintext sequences corresponding to the
809
ciphertext ``C``. This method effectively uses all the possible
810
keys in this affine cryptosystem to decrypt ``C``. The method is
811
also referred to as exhaustive key search. The output is a
812
dictionary of key, candidate decipherment pairs.
813
814
EXAMPLES:
815
816
Cryptanalyze using all possible keys with the option
817
``ranking="none"``::
818
819
sage: A = AffineCryptosystem(AlphabeticStrings())
820
sage: a, b = (3, 7)
821
sage: P = A.encoding("Linear"); P
822
LINEAR
823
sage: C = A.enciphering(a, b, P)
824
sage: L = A.brute_force(C)
825
sage: sorted(L.items())[:26] # display 26 candidate decipherments
826
<BLANKLINE>
827
[((1, 0), OFUTHG),
828
((1, 1), NETSGF),
829
((1, 2), MDSRFE),
830
((1, 3), LCRQED),
831
((1, 4), KBQPDC),
832
((1, 5), JAPOCB),
833
((1, 6), IZONBA),
834
((1, 7), HYNMAZ),
835
((1, 8), GXMLZY),
836
((1, 9), FWLKYX),
837
((1, 10), EVKJXW),
838
((1, 11), DUJIWV),
839
((1, 12), CTIHVU),
840
((1, 13), BSHGUT),
841
((1, 14), ARGFTS),
842
((1, 15), ZQFESR),
843
((1, 16), YPEDRQ),
844
((1, 17), XODCQP),
845
((1, 18), WNCBPO),
846
((1, 19), VMBAON),
847
((1, 20), ULAZNM),
848
((1, 21), TKZYML),
849
((1, 22), SJYXLK),
850
((1, 23), RIXWKJ),
851
((1, 24), QHWVJI),
852
((1, 25), PGVUIH)]
853
854
Use the chi-square ranking function, i.e. ``ranking="chisquare"``::
855
856
sage: A = AffineCryptosystem(AlphabeticStrings())
857
sage: a, b = (3, 7)
858
sage: P = A.encoding("Linear functions for encrypting and decrypting."); P
859
LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING
860
sage: C = A.enciphering(a, b, P)
861
sage: Rank = A.brute_force(C, ranking="chisquare")
862
sage: Rank[:10] # display only the top 10 candidate keys
863
<BLANKLINE>
864
[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
865
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
866
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
867
((11, 15), HSRYELDAROVSWRQDWLYROLUBVSRIERTTYOLUBVSRI),
868
((25, 1), NWHIUVFMHOPWEHSFEVIHOVABPWHCUHLLIOVABPWHC),
869
((25, 7), TCNOABLSNUVCKNYLKBONUBGHVCNIANRROUBGHVCNI),
870
((15, 4), SHIBVOWZILEHDIJWDOBILOFYEHIRVIGGBLOFYEHIR),
871
((15, 23), PEFYSLTWFIBEAFGTALYFILCVBEFOSFDDYILCVBEFO),
872
((7, 10), IDUFHSYXUTEDNULYNSFUTSVGEDURHUMMFTSVGEDUR),
873
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH)]
874
875
Use the squared differences ranking function, i.e.
876
``ranking="squared_differences"``::
877
878
sage: Rank = A.brute_force(C, ranking="squared_differences")
879
sage: Rank[:10] # display only the top 10 candidate keys
880
<BLANKLINE>
881
[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
882
((23, 6), GJENRAMXEPYJDEZMDANEPATCYJELREOONPATCYJEL),
883
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
884
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH),
885
((19, 9), DIRGETNORSHIYRANYTGRSTQFHIRUERZZGSTQFHIRU),
886
((23, 18), KNIRVEQBITCNHIDQHERITEXGCNIPVISSRTEXGCNIP),
887
((17, 16), GHORBEIDOJMHFOVIFEROJETWMHOZBOAARJETWMHOZ),
888
((21, 14), AHEZRMOFEVQHTEBOTMZEVMNIQHEDREKKZVMNIQHED),
889
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
890
((7, 18), SNEPRCIHEDONXEVIXCPEDCFQONEBREWWPDCFQONEB)]
891
892
TESTS:
893
894
Currently, the binary number system is not supported as an
895
alphabet of this affine cryptosystem::
896
897
sage: A = AffineCryptosystem(AlphabeticStrings())
898
sage: BinStr = BinaryStrings()
899
sage: C = BinStr.encoding("abc")
900
sage: A.brute_force(C)
901
Traceback (most recent call last):
902
...
903
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
904
905
Nor are the octal, hexadecimal, and radix-64 number systems
906
supported::
907
908
sage: OctStr = OctalStrings()
909
sage: C = OctStr([1, 2, 3])
910
sage: A.brute_force(C)
911
Traceback (most recent call last):
912
...
913
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
914
sage: HexStr = HexadecimalStrings()
915
sage: C = HexStr.encoding("abc")
916
sage: A.brute_force(C)
917
Traceback (most recent call last):
918
...
919
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
920
sage: RadStr = Radix64Strings()
921
sage: C = RadStr([1, 2, 3])
922
sage: A.brute_force(C)
923
Traceback (most recent call last):
924
...
925
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
926
927
Only the chi-square and squared-differences ranking functions are
928
currently supported. The keyword ``ranking`` must take on either
929
of the values ``"none"``, ``"chisquare"`` or
930
``"squared_differences"``::
931
932
sage: A = AffineCryptosystem(AlphabeticStrings())
933
sage: a, b = (3, 7)
934
sage: P = A.encoding("Linear")
935
sage: C = A.enciphering(a, b, P)
936
sage: A.brute_force(C, ranking="chi")
937
Traceback (most recent call last):
938
...
939
ValueError: Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.
940
sage: A.brute_force(C, ranking="")
941
Traceback (most recent call last):
942
...
943
ValueError: Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.
944
"""
945
# Sanity check: ensure that C is encoded using one of the
946
# supported alphabets of this affine cryptosystem.
947
if not isinstance(C.parent(), AlphabeticStringMonoid):
948
raise TypeError("Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.")
949
ranking_functions = ["none", "chisquare", "squared_differences"]
950
if ranking not in ranking_functions:
951
raise ValueError("Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.")
952
953
# Now do the actual task of cryptanalysis by means of exhaustive
954
# key search, also known as the brute force method. Let D be a
955
# dictionary of key/plaintext pairs.
956
D = {}
957
958
# NOTE: This is a good candidate for loop unrolling and
959
# further optimization. Unless we can justify that this block of
960
# code is a bottleneck on the runtime of the method, we should
961
# leave it as is.
962
[D.setdefault((a, b), self.deciphering(a, b, C))
963
for a in self._invertible_A
964
for b in xrange(self.alphabet_size())]
965
966
if ranking == "none":
967
return D
968
if ranking == "chisquare":
969
return self.rank_by_chi_square(C, D)
970
if ranking == "squared_differences":
971
return self.rank_by_squared_differences(C, D)
972
973
def deciphering(self, a, b, C):
974
r"""
975
Decrypt the ciphertext ``C`` with the key ``(a, b)`` using affine
976
cipher decryption.
977
978
INPUT:
979
980
- ``a, b`` -- a secret key belonging to the key space of this affine
981
cipher. This key must be an element of
982
`\ZZ/n\ZZ \times \ZZ/n\ZZ` such that `\gcd(a,n) = 1` with `n`
983
being the size of the ciphertext and plaintext spaces.
984
985
- ``C`` -- a string of ciphertext; possibly an empty string.
986
Characters in this string must be encoded using one of the
987
supported alphabets. See the method :func:`encoding()` for more
988
information.
989
990
OUTPUT:
991
992
- The plaintext corresponding to the ciphertext ``C``.
993
994
EXAMPLES:
995
996
Decryption over the capital letters of the English alphabet::
997
998
sage: A = AffineCryptosystem(AlphabeticStrings())
999
sage: a, b = (5, 2)
1000
sage: P = A.encoding("Affine functions are linear functions.")
1001
sage: C = A.enciphering(a, b, P); C
1002
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
1003
sage: P == A.deciphering(a, b, C)
1004
True
1005
1006
The previous example can also be worked through using functional
1007
notation::
1008
1009
sage: A = AffineCryptosystem(AlphabeticStrings())
1010
sage: a, b = (5, 2)
1011
sage: P = A.encoding("Affine functions are linear functions.")
1012
sage: E = A(a, b); E
1013
Affine cipher on Free alphabetic string monoid on A-Z
1014
sage: C = E(P); C
1015
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
1016
sage: aInv, bInv = A.inverse_key(a, b)
1017
sage: D = A(aInv, bInv); D
1018
Affine cipher on Free alphabetic string monoid on A-Z
1019
sage: D(C) == P
1020
True
1021
1022
If the ciphertext is an empty string, then the plaintext is also
1023
an empty string regardless of the value of the secret key::
1024
1025
sage: a, b = A.random_key()
1026
sage: A.deciphering(a, b, A.encoding(""))
1027
<BLANKLINE>
1028
sage: A.deciphering(a, b, A.encoding(" "))
1029
<BLANKLINE>
1030
1031
TESTS:
1032
1033
The key must be an ordered pair
1034
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of the
1035
plaintext and ciphertext spaces. Furthermore, `a` must be
1036
relatively prime to `n`, i.e. `\gcd(a,n) = 1`::
1037
1038
sage: A.deciphering(2, 6, P)
1039
Traceback (most recent call last):
1040
...
1041
ValueError: (a, b) = (2, 6) is outside the range of acceptable values for a key of this affine cipher.
1042
"""
1043
aInv, bInv = self.inverse_key(a, b)
1044
D = self(aInv, bInv)
1045
return D(C)
1046
1047
def enciphering(self, a, b, P):
1048
r"""
1049
Encrypt the plaintext ``P`` with the key ``(a, b)`` using affine cipher
1050
encryption.
1051
1052
INPUT:
1053
1054
- ``a, b`` -- a secret key belonging to the key space of this affine
1055
cipher. This key must be an element of
1056
`\ZZ/n\ZZ \times \ZZ/n\ZZ` such that `\gcd(a,n) = 1` with `n`
1057
being the size of the ciphertext and plaintext spaces.
1058
1059
- ``P`` -- a string of plaintext; possibly an empty string.
1060
Characters in this string must be encoded using one of the
1061
supported alphabets. See the method :func:`encoding()` for more
1062
information.
1063
1064
OUTPUT:
1065
1066
- The ciphertext corresponding to the plaintext ``P``.
1067
1068
EXAMPLES:
1069
1070
Encryption over the capital letters of the English alphabet::
1071
1072
sage: A = AffineCryptosystem(AlphabeticStrings())
1073
sage: a, b = (3, 6)
1074
sage: P = A.encoding("Affine ciphers work with linear functions.")
1075
sage: A.enciphering(a, b, P)
1076
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI
1077
1078
Now work through the previous example using functional notation::
1079
1080
sage: A = AffineCryptosystem(AlphabeticStrings())
1081
sage: a, b = (3, 6)
1082
sage: P = A.encoding("Affine ciphers work with linear functions.")
1083
sage: E = A(a, b); E
1084
Affine cipher on Free alphabetic string monoid on A-Z
1085
sage: E(P)
1086
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI
1087
1088
If the plaintext is an empty string, then the ciphertext is also
1089
an empty string regardless of the value of the secret key::
1090
1091
sage: a, b = A.random_key()
1092
sage: A.enciphering(a, b, A.encoding(""))
1093
<BLANKLINE>
1094
sage: A.enciphering(a, b, A.encoding(" "))
1095
<BLANKLINE>
1096
1097
TESTS:
1098
1099
The key must be an ordered pair
1100
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of the
1101
plaintext and ciphertext spaces. Furthermore, `a` must be
1102
relatively prime to `n`, i.e. `\gcd(a,n) = 1`::
1103
1104
sage: A.enciphering(2, 6, P)
1105
Traceback (most recent call last):
1106
...
1107
ValueError: (a, b) = (2, 6) is outside the range of acceptable values for a key of this affine cryptosystem.
1108
"""
1109
E = self(a, b)
1110
return E(P)
1111
1112
def encoding(self, S):
1113
r"""
1114
The encoding of the string ``S`` over the string monoid of this
1115
affine cipher. For example, if the string monoid of this cryptosystem
1116
is
1117
:class:`AlphabeticStringMonoid <sage.monoids.string_monoid.AlphabeticStringMonoid>`,
1118
then the encoding of ``S`` would be its upper-case equivalent
1119
stripped of all non-alphabetic characters. Only the following alphabet
1120
is supported for the affine cipher:
1121
1122
- capital letters of the English alphabet as implemented in
1123
:func:`AlphabeticStrings() <sage.monoids.string_monoid.AlphabeticStrings>`
1124
1125
INPUT:
1126
1127
- ``S`` -- a string, possibly empty.
1128
1129
OUTPUT:
1130
1131
- The encoding of ``S`` over the string monoid of this cryptosystem.
1132
If ``S`` is an empty string, return an empty string.
1133
1134
EXAMPLES:
1135
1136
Encoding over the upper-case letters of the English alphabet::
1137
1138
sage: A = AffineCryptosystem(AlphabeticStrings())
1139
sage: A.encoding("Affine cipher over capital letters of the English alphabet.")
1140
AFFINECIPHEROVERCAPITALLETTERSOFTHEENGLISHALPHABET
1141
1142
The argument ``S`` can be an empty string, in which case an empty
1143
string is returned::
1144
1145
sage: AffineCryptosystem(AlphabeticStrings()).encoding("")
1146
<BLANKLINE>
1147
"""
1148
D = self.cipher_domain()
1149
if isinstance(D, AlphabeticStringMonoid):
1150
return D(strip_encoding(S))
1151
try:
1152
return D.encoding(S)
1153
except:
1154
raise TypeError("Argument S = %s does not encode in the cipher domain" % S)
1155
1156
def inverse_key(self, a, b):
1157
r"""
1158
The inverse key corresponding to the secret key `(a,b)`. If `p` is
1159
a plaintext character so that `p \in \ZZ/n\ZZ` and `n` is the
1160
alphabet size, then the ciphertext `c` corresponding to `p` is
1161
1162
.. MATH::
1163
1164
c \equiv ap + b \pmod{n}
1165
1166
As `(a,b)` is a key, then the multiplicative inverse `a^{-1}`
1167
exists and the original plaintext can be recovered as follows
1168
1169
.. MATH::
1170
1171
p \equiv a^{-1} (c - b) \pmod{n}
1172
\equiv a^{-1}c + a^{-1}(-b) \pmod{n}
1173
1174
Therefore the ordered pair `(a^{-1}, -ba^{-1})` is the inverse key
1175
corresponding to `(a,b)`.
1176
1177
INPUT:
1178
1179
- ``a, b`` -- a secret key for this affine cipher. The ordered pair
1180
`(a,b)` must be an element of `\ZZ/n\ZZ \times \ZZ/n\ZZ` such that
1181
`\gcd(a,n) = 1`.
1182
1183
OUTPUT:
1184
1185
- The inverse key `(a^{-1}, -ba^{-1})` corresponding to `(a,b)`.
1186
1187
EXAMPLES::
1188
1189
sage: A = AffineCryptosystem(AlphabeticStrings())
1190
sage: a, b = (1, 2)
1191
sage: A.inverse_key(a, b)
1192
(1, 24)
1193
sage: A.inverse_key(3, 2)
1194
(9, 8)
1195
1196
Suppose that the plaintext and ciphertext spaces are the capital
1197
letters of the English alphabet so that `n = 26`. If `\varphi(n)`
1198
is the Euler phi function of `n`, then there are `\varphi(n)`
1199
integers `0 \leq a < n` that are relatively prime to `n`. For the
1200
capital letters of the English alphabet, there are 12 such integers
1201
relatively prime to `n`::
1202
1203
sage: euler_phi(A.alphabet_size())
1204
12
1205
1206
And here is a list of those integers::
1207
1208
sage: n = A.alphabet_size()
1209
sage: L = [i for i in xrange(n) if gcd(i, n) == 1]; L
1210
[1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
1211
1212
Then a secret key `(a,b)` of this shift cryptosystem is
1213
such that `a` is an element of the list ``L`` in the last example.
1214
Any inverse key `(A, B)` corresponding to `(a,b)` is such that
1215
`A` is also in the list ``L`` above::
1216
1217
sage: a, b = (3, 9)
1218
sage: a in L
1219
True
1220
sage: aInv, bInv = A.inverse_key(a, b)
1221
sage: aInv, bInv
1222
(9, 23)
1223
sage: aInv in L
1224
True
1225
1226
TESTS:
1227
1228
Any ordered pair of the form `(0, b)` for any integer `b` cannot be
1229
a secret key of this affine cipher. Hence `(0, b)` does not have
1230
a corresponding inverse key::
1231
1232
sage: A = AffineCryptosystem(AlphabeticStrings())
1233
sage: A.inverse_key(0, 1)
1234
Traceback (most recent call last):
1235
...
1236
ValueError: (a, b) = (0, 1) is outside the range of acceptable values for a key of this affine cipher.
1237
"""
1238
try:
1239
from sage.rings.arith import inverse_mod
1240
from sage.rings.finite_rings.integer_mod import Mod
1241
n = self.alphabet_size()
1242
aInv = inverse_mod(a, n)
1243
bInv = Mod(-b * aInv, n).lift()
1244
return (aInv, bInv)
1245
except:
1246
raise ValueError("(a, b) = (%s, %s) is outside the range of acceptable values for a key of this affine cipher." % (a, b))
1247
1248
def random_key(self):
1249
r"""
1250
Generate a random key within the key space of this affine cipher.
1251
The generated secret key is an ordered pair
1252
`(a, b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of
1253
the cipher domain and `\gcd(a, n) = 1`. Let `\varphi(n)` denote
1254
the Euler phi function of `n`. Then the affine cipher has
1255
`n \cdot \varphi(n)` possible keys (see page 10 of [Sti06]_).
1256
1257
OUTPUT:
1258
1259
- A random key within the key space of this affine cryptosystem.
1260
The output key is an ordered pair `(a,b)`.
1261
1262
EXAMPLES::
1263
1264
sage: A = AffineCryptosystem(AlphabeticStrings())
1265
sage: A.random_key() # random
1266
(17, 25)
1267
1268
If `(a,b)` is a secret key and `n` is the size of the plaintext and
1269
ciphertext alphabets, then `\gcd(a, n) = 1`::
1270
1271
sage: a, b = A.random_key()
1272
sage: n = A.alphabet_size()
1273
sage: gcd(a, n)
1274
1
1275
"""
1276
# Return a random element in ZZ/nZZ x ZZ/nZZ where n is the number
1277
# of elements in the plaintext/ciphertext alphabet.
1278
from sage.misc.prandom import randint
1279
n = self.alphabet_size()
1280
L = len(self._invertible_A)
1281
a = Integer(self._invertible_A[randint(0, L - 1)])
1282
b = Integer(randint(0, n - 1))
1283
return (a, b)
1284
1285
class HillCryptosystem(SymmetricKeyCryptosystem):
1286
"""
1287
Create a Hill cryptosystem defined by the `m` x `m` matrix space
1288
over `\ZZ / N \ZZ`, where `N` is the alphabet size of
1289
the string monoid ``S``.
1290
1291
INPUT:
1292
1293
- ``S`` - a string monoid over some alphabet
1294
1295
- ``m`` - integer `> 0`; the block length of matrices that specify
1296
block permutations
1297
1298
OUTPUT:
1299
1300
- A Hill cryptosystem of block length ``m`` over the alphabet ``S``.
1301
1302
EXAMPLES::
1303
1304
sage: S = AlphabeticStrings()
1305
sage: E = HillCryptosystem(S,3)
1306
sage: E
1307
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
1308
sage: R = IntegerModRing(26)
1309
sage: M = MatrixSpace(R,3,3)
1310
sage: A = M([[1,0,1],[0,1,1],[2,2,3]])
1311
sage: A
1312
[1 0 1]
1313
[0 1 1]
1314
[2 2 3]
1315
sage: e = E(A)
1316
sage: e
1317
Hill cipher on Free alphabetic string monoid on A-Z of block length 3
1318
sage: e(S("LAMAISONBLANCHE"))
1319
JYVKSKQPELAYKPV
1320
1321
TESTS::
1322
1323
sage: S = AlphabeticStrings()
1324
sage: E = HillCryptosystem(S,3)
1325
sage: E == loads(dumps(E))
1326
True
1327
"""
1328
1329
def __init__(self, S, m):
1330
"""
1331
See ``HillCryptosystem`` for full documentation.
1332
1333
Create a Hill cryptosystem defined by the `m` x `m` matrix space
1334
over `\ZZ / N \ZZ`, where `N` is the alphabet size of
1335
the string monoid ``S``.
1336
1337
INPUT:
1338
1339
- ``S`` - a string monoid over some alphabet
1340
1341
- ``m`` - integer `> 0`; the block length of matrices that specify
1342
block permutations
1343
1344
OUTPUT:
1345
1346
- A Hill cryptosystem of block length ``m`` over the alphabet ``S``.
1347
1348
EXAMPLES::
1349
1350
sage: S = AlphabeticStrings()
1351
sage: E = HillCryptosystem(S,3)
1352
sage: E
1353
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
1354
"""
1355
if not isinstance(S, StringMonoid_class):
1356
raise TypeError("S (= %s) must be a string monoid." % S)
1357
R = IntegerModRing(S.ngens())
1358
M = MatrixSpace(R, m, m)
1359
SymmetricKeyCryptosystem.__init__(self, S, S, M, block_length=m)
1360
1361
def __call__(self, A):
1362
"""
1363
Create a Hill cipher.
1364
1365
INPUT:
1366
1367
- ``A`` - a matrix which specifies a block permutation
1368
1369
EXAMPLES::
1370
1371
sage: S = AlphabeticStrings()
1372
sage: E = HillCryptosystem(S,3)
1373
sage: E
1374
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
1375
sage: M = E.key_space()
1376
sage: A = M([[1,0,1],[0,1,1],[2,2,3]])
1377
sage: A
1378
[1 0 1]
1379
[0 1 1]
1380
[2 2 3]
1381
sage: e = E(A)
1382
sage: e
1383
Hill cipher on Free alphabetic string monoid on A-Z of block length 3
1384
sage: m = S("LAMAISONBLANCHE")
1385
sage: e(m)
1386
JYVKSKQPELAYKPV
1387
sage: c = e.inverse()
1388
sage: c(e(m))
1389
LAMAISONBLANCHE
1390
"""
1391
M = self.key_space()
1392
m = self.block_length()
1393
if isinstance(A, list):
1394
try:
1395
A = M(A)
1396
except:
1397
raise TypeError("A (= %s) must specify a square matrix of degree %s." % (A, m))
1398
return HillCipher(self, A)
1399
1400
def _repr_(self):
1401
"""
1402
Return a string representation of self.
1403
1404
EXAMPLES::
1405
1406
sage: A = AlphabeticStrings()
1407
sage: H = HillCryptosystem(A, 3)
1408
sage: H
1409
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
1410
sage: H._repr_()
1411
'Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3'
1412
"""
1413
return "Hill cryptosystem on %s of block length %s" % (
1414
self.cipher_domain(), self.block_length())
1415
1416
def block_length(self):
1417
"""
1418
The row or column dimension of a matrix specifying a block
1419
permutation. Encryption and decryption keys of a Hill cipher are
1420
square matrices, i.e. the row and column dimensions of an encryption
1421
or decryption key are the same. This row/column dimension is referred
1422
to as the *block length*.
1423
1424
OUTPUT:
1425
1426
- The block length of an encryption/decryption key.
1427
1428
EXAMPLES::
1429
1430
sage: A = AlphabeticStrings()
1431
sage: n = randint(1, A.ngens() - 1)
1432
sage: H = HillCryptosystem(A, n)
1433
sage: H.block_length() == n
1434
True
1435
"""
1436
return self.key_space().nrows()
1437
1438
def random_key(self):
1439
"""
1440
A random key within the key space of this Hill cipher. That is,
1441
generate a random `m` x `m` matrix to be used as a block
1442
permutation, where `m` is the block length of this Hill cipher. If
1443
`n` is the size of the cryptosystem alphabet, then there are
1444
`n^{m^2}` possible keys. However the number of valid keys,
1445
i.e. invertible `m` x `m` square matrices, is smaller than
1446
`n^{m^2}`.
1447
1448
OUTPUT:
1449
1450
- A random key within the key space of this Hill cipher.
1451
1452
EXAMPLES::
1453
1454
sage: A = AlphabeticStrings()
1455
sage: n = 3
1456
sage: H = HillCryptosystem(A, n)
1457
sage: K = H.random_key()
1458
sage: Ki = H.inverse_key(K)
1459
sage: M = "LAMAISONBLANCHE"
1460
sage: e = H(K)
1461
sage: d = H(Ki)
1462
sage: d(e(A(M))) == A(M)
1463
True
1464
"""
1465
M = self.key_space()
1466
R = M.base_ring()
1467
m = M.nrows()
1468
N = Integer(self.cipher_domain().ngens())
1469
while True:
1470
A = M([ randint(0, N-1) for i in range(m**2) ])
1471
if N.gcd(A.det().lift()) == 1:
1472
break
1473
return A
1474
1475
def inverse_key(self, A):
1476
"""
1477
The inverse key corresponding to the key ``A``.
1478
1479
INPUT:
1480
1481
- ``A`` - an invertible matrix of the key space of this Hill cipher
1482
1483
OUTPUT:
1484
1485
- The inverse matrix of ``A``.
1486
1487
EXAMPLES::
1488
1489
sage: S = AlphabeticStrings()
1490
sage: E = HillCryptosystem(S,3)
1491
sage: A = E.random_key()
1492
sage: B = E.inverse_key(A)
1493
sage: M = S("LAMAISONBLANCHE")
1494
sage: e = E(A)
1495
sage: c = E(B)
1496
sage: c(e(M))
1497
LAMAISONBLANCHE
1498
"""
1499
S = self.plaintext_space()
1500
M = self.key_space()
1501
if not A in M:
1502
raise TypeError("A (= %s) must be a matrix in the key space of %s." % (A, self))
1503
m = self.block_length()
1504
MatZZ = MatrixSpace(ZZ, m)
1505
AZ = MatZZ([ [ A[i, j].lift() for j in range(m) ] for i in range(m) ])
1506
AZ_adj = AZ.adjoint()
1507
u, r, s = xgcd(A.det().lift(), S.ngens())
1508
if u != 1:
1509
raise ValueError("Argument:\n\n%s\n\nis not invertible." % (A))
1510
return r * A.parent()(AZ_adj)
1511
1512
def encoding(self, M):
1513
"""
1514
The encoding of the string ``M`` over the string monoid of this
1515
Hill cipher. For example, if the string monoid of this Hill cipher
1516
is :class:`AlphabeticStringMonoid`, then the encoding of ``M`` would
1517
be its upper-case equivalent stripped of all non-alphabetic
1518
characters.
1519
1520
INPUT:
1521
1522
- ``M`` - a string, possibly empty
1523
1524
OUTPUT:
1525
1526
- The encoding of ``M`` over the string monoid of this Hill cipher.
1527
1528
EXAMPLES::
1529
1530
sage: M = "The matrix cipher by Lester S. Hill."
1531
sage: A = AlphabeticStrings()
1532
sage: H = HillCryptosystem(A, 7)
1533
sage: H.encoding(M) == A.encoding(M)
1534
True
1535
"""
1536
S = self.cipher_domain()
1537
if isinstance(S, AlphabeticStringMonoid):
1538
return S(strip_encoding(M))
1539
try:
1540
return S.encoding(M)
1541
except:
1542
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
1543
1544
def deciphering(self, A, C):
1545
"""
1546
Decrypt the ciphertext ``C`` using the key ``A``.
1547
1548
INPUT:
1549
1550
- ``A`` - a key within the key space of this Hill cipher
1551
1552
- ``C`` - a string (possibly empty) over the string monoid of this
1553
Hill cipher
1554
1555
OUTPUT:
1556
1557
- The plaintext corresponding to the ciphertext ``C``.
1558
1559
EXAMPLES::
1560
1561
sage: H = HillCryptosystem(AlphabeticStrings(), 3)
1562
sage: K = H.random_key()
1563
sage: M = H.encoding("Good day, mate! How ya going?")
1564
sage: H.deciphering(K, H.enciphering(K, M)) == M
1565
True
1566
"""
1567
# TODO: some type checking that A is invertible hence a valid key
1568
i = self(self.inverse_key(A))
1569
return i(C)
1570
1571
def enciphering(self, A, M):
1572
"""
1573
Encrypt the plaintext ``M`` using the key ``A``.
1574
1575
INPUT:
1576
1577
- ``A`` - a key within the key space of this Hill cipher
1578
1579
- ``M`` - a string (possibly empty) over the string monoid of this
1580
Hill cipher.
1581
1582
OUTPUT:
1583
1584
- The ciphertext corresponding to the plaintext ``M``.
1585
1586
EXAMPLES::
1587
1588
sage: H = HillCryptosystem(AlphabeticStrings(), 3)
1589
sage: K = H.random_key()
1590
sage: M = H.encoding("Good day, mate! How ya going?")
1591
sage: H.deciphering(K, H.enciphering(K, M)) == M
1592
True
1593
"""
1594
# TODO: some type checking that A is invertible hence a valid key
1595
e = self(A)
1596
return e(M)
1597
1598
class ShiftCryptosystem(SymmetricKeyCryptosystem):
1599
r"""
1600
Create a shift cryptosystem.
1601
1602
Let `A = \{ a_0, a_1, a_2, \dots, a_{n-1} \}` be a non-empty alphabet
1603
consisting of `n` unique elements. Define a mapping
1604
`f : A \longrightarrow \ZZ/ n\ZZ` from the alphabet `A` to
1605
the set `\ZZ / n\ZZ` of integers modulo `n`, given by
1606
`f(a_i) = i`. Thus we can identify each element of the alphabet `A`
1607
with a unique integer `0 \leq i < n`. A key of the shift cipher is an
1608
integer `0 \leq k < n`. Therefore the key space is `\ZZ / n\ZZ`. Since
1609
we assume that `A` does not have repeated elements, the mapping
1610
`f : A \longrightarrow \ZZ/ n\ZZ` is bijective.
1611
Encryption works by moving along the alphabet by `k` positions, with
1612
wrap around. Decryption reverses the process by moving backwards by
1613
`k` positions, with wrap around. More generally, let `k` be a secret key,
1614
i.e. an element of the key space, and let `p` be a plaintext
1615
character and consequently `p \in \ZZ / n\ZZ`. Then the ciphertext
1616
character `c` corresponding to `p` is given by
1617
1618
.. MATH::
1619
1620
c \equiv p + k \pmod{n}
1621
1622
Similarly, given a ciphertext character `c \in \ZZ / n\ZZ` and a secret
1623
key `k`, we can recover the corresponding plaintext character as follows:
1624
1625
.. MATH::
1626
1627
p \equiv c - k \pmod{n}
1628
1629
Use the bijection `f : A \longrightarrow \ZZ/ n\ZZ` to convert `c`
1630
and `p` back to elements of the alphabet `A`. Currently, the following
1631
alphabets are supported for the shift cipher:
1632
1633
- capital letters of the English alphabet as implemented in
1634
:func:`AlphabeticStrings()
1635
<sage.monoids.string_monoid.AlphabeticStrings>`
1636
1637
- the alphabet consisting of the hexadecimal number system as
1638
implemented in
1639
:func:`HexadecimalStrings()
1640
<sage.monoids.string_monoid.HexadecimalStrings>`
1641
1642
- the alphabet consisting of the binary number system as implemented in
1643
:func:`BinaryStrings() <sage.monoids.string_monoid.BinaryStrings>`
1644
1645
EXAMPLES:
1646
1647
Some examples illustrating encryption and decryption over various
1648
alphabets. Here is an example over the upper-case letters of the English
1649
alphabet::
1650
1651
sage: S = ShiftCryptosystem(AlphabeticStrings()); S
1652
Shift cryptosystem on Free alphabetic string monoid on A-Z
1653
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
1654
sage: P
1655
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
1656
sage: K = 7
1657
sage: C = S.enciphering(K, P); C
1658
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
1659
sage: S.deciphering(K, C)
1660
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
1661
sage: S.deciphering(K, C) == P
1662
True
1663
1664
The previous example can also be done as follows::
1665
1666
sage: S = ShiftCryptosystem(AlphabeticStrings())
1667
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
1668
sage: K = 7
1669
sage: E = S(K); E
1670
Shift cipher on Free alphabetic string monoid on A-Z
1671
sage: C = E(P); C
1672
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
1673
sage: D = S(S.inverse_key(K)); D
1674
Shift cipher on Free alphabetic string monoid on A-Z
1675
sage: D(C) == P
1676
True
1677
sage: D(C) == P == D(E(P))
1678
True
1679
1680
Over the hexadecimal number system::
1681
1682
sage: S = ShiftCryptosystem(HexadecimalStrings()); S
1683
Shift cryptosystem on Free hexadecimal string monoid
1684
sage: P = S.encoding("Encryption & decryption shifts along the alphabet."); P
1685
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
1686
sage: K = 5
1687
sage: C = S.enciphering(K, P); C
1688
9ab3b8c7cec5c9beb4b3757b75b9bab8c7cec5c9beb4b375c8bdbebbc9c875b6b1b4b3bc75c9bdba75b6b1c5bdb6b7bac973
1689
sage: S.deciphering(K, C)
1690
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
1691
sage: S.deciphering(K, C) == P
1692
True
1693
1694
And over the binary number system::
1695
1696
sage: S = ShiftCryptosystem(BinaryStrings()); S
1697
Shift cryptosystem on Free binary string monoid
1698
sage: P = S.encoding("The binary alphabet is very insecure."); P
1699
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
1700
sage: K = 1
1701
sage: C = S.enciphering(K, P); C
1702
10101011100101111001101011011111100111011001011010010001100111101000110110000110110111111001111010010011100011111001011110011110100111011001101010001011110111111001011010001100110111111000100110011010100011011000011011011111100101101001000110001100100110101001110010001010100011011001101011010001
1703
sage: S.deciphering(K, C)
1704
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
1705
sage: S.deciphering(K, C) == P
1706
True
1707
1708
A shift cryptosystem with key `k = 3` is commonly referred to as the
1709
Caesar cipher. Create a Caesar cipher over the upper-case letters of the
1710
English alphabet::
1711
1712
sage: caesar = ShiftCryptosystem(AlphabeticStrings())
1713
sage: K = 3
1714
sage: P = caesar.encoding("abcdef"); P
1715
ABCDEF
1716
sage: C = caesar.enciphering(K, P); C
1717
DEFGHI
1718
sage: caesar.deciphering(K, C) == P
1719
True
1720
1721
Generate a random key for encryption and decryption::
1722
1723
sage: S = ShiftCryptosystem(AlphabeticStrings())
1724
sage: P = S.encoding("Shift cipher with a random key.")
1725
sage: K = S.random_key()
1726
sage: C = S.enciphering(K, P)
1727
sage: S.deciphering(K, C) == P
1728
True
1729
1730
Decrypting with the key ``K`` is equivalent to encrypting with its
1731
corresponding inverse key::
1732
1733
sage: S.enciphering(S.inverse_key(K), C) == P
1734
True
1735
1736
TESTS:
1737
1738
Currently, the octal number system is not supported as an alphabet for
1739
this shift cryptosystem::
1740
1741
sage: ShiftCryptosystem(OctalStrings())
1742
Traceback (most recent call last):
1743
...
1744
TypeError: A (= Free octal string monoid) is not supported as a cipher domain of this shift cryptosystem.
1745
1746
Nor is the radix-64 number system supported::
1747
1748
sage: ShiftCryptosystem(Radix64Strings())
1749
Traceback (most recent call last):
1750
...
1751
TypeError: A (= Free radix 64 string monoid) is not supported as a cipher domain of this shift cryptosystem.
1752
1753
Testing of dumping and loading objects::
1754
1755
sage: SA = ShiftCryptosystem(AlphabeticStrings())
1756
sage: SA == loads(dumps(SA))
1757
True
1758
sage: SH = ShiftCryptosystem(HexadecimalStrings())
1759
sage: SH == loads(dumps(SH))
1760
True
1761
sage: SB = ShiftCryptosystem(BinaryStrings())
1762
sage: SB == loads(dumps(SB))
1763
True
1764
1765
The key ``K`` must satisfy the inequality `0 \leq K < n` with `n`
1766
being the size of the plaintext, ciphertext, and key spaces. For the
1767
shift cryptosystem, all these spaces are the same alphabet. This
1768
inequality must be satisfied for each of the supported alphabets.
1769
The capital letters of the English alphabet::
1770
1771
sage: S = ShiftCryptosystem(AlphabeticStrings())
1772
sage: S(2 + S.alphabet_size())
1773
Traceback (most recent call last):
1774
...
1775
ValueError: K (=28) is outside the range of acceptable values for a key of this shift cryptosystem.
1776
sage: S(-2)
1777
Traceback (most recent call last):
1778
...
1779
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
1780
1781
The hexadecimal number system::
1782
1783
sage: S = ShiftCryptosystem(HexadecimalStrings())
1784
sage: S(1 + S.alphabet_size())
1785
Traceback (most recent call last):
1786
...
1787
ValueError: K (=17) is outside the range of acceptable values for a key of this shift cryptosystem.
1788
sage: S(-1)
1789
Traceback (most recent call last):
1790
...
1791
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
1792
1793
The binary number system::
1794
1795
sage: S = ShiftCryptosystem(BinaryStrings())
1796
sage: S(1 + S.alphabet_size())
1797
Traceback (most recent call last):
1798
...
1799
ValueError: K (=3) is outside the range of acceptable values for a key of this shift cryptosystem.
1800
sage: S(-2)
1801
Traceback (most recent call last):
1802
...
1803
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
1804
"""
1805
1806
def __init__(self, A):
1807
r"""
1808
See ``ShiftCryptosystem`` for full documentation.
1809
1810
Create a shift cryptosystem defined over the alphabet ``A``.
1811
1812
INPUT:
1813
1814
- ``A`` -- a string monoid over some alphabet; this is the non-empty
1815
alphabet over which the plaintext and ciphertext spaces
1816
are defined.
1817
1818
OUTPUT:
1819
1820
- A shift cryptosystem over the alphabet ``A``.
1821
1822
EXAMPLES::
1823
1824
sage: S = ShiftCryptosystem(AlphabeticStrings()); S
1825
Shift cryptosystem on Free alphabetic string monoid on A-Z
1826
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
1827
sage: P
1828
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
1829
sage: K = 7
1830
sage: C = S.enciphering(K, P); C
1831
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
1832
sage: S.deciphering(K, C)
1833
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
1834
sage: S.deciphering(K, C) == P
1835
True
1836
"""
1837
# NOTE: the code here is very similar to that in the method
1838
# rank_by_chi_square() of the class AffineCryptosystem. The most
1839
# significant change in the code below is in how the secret key k
1840
# is processed.
1841
1842
# sanity check
1843
from sage.monoids.string_monoid import (
1844
AlphabeticStringMonoid,
1845
BinaryStringMonoid,
1846
HexadecimalStringMonoid)
1847
if not isinstance(A, ( AlphabeticStringMonoid,
1848
BinaryStringMonoid,
1849
HexadecimalStringMonoid )):
1850
raise TypeError("A (= %s) is not supported as a cipher domain of this shift cryptosystem." % A)
1851
# Initialize the shift cryptosystem with the plaintext, ciphertext,
1852
# and key spaces.
1853
SymmetricKeyCryptosystem.__init__(self, A, A, IntegerModRing(A.ngens()))
1854
1855
def __call__(self, K):
1856
r"""
1857
Create a shift cipher with key ``K``.
1858
1859
INPUT:
1860
1861
- ``K`` -- a secret key; this key is used for both encryption and
1862
decryption. For the shift cryptosystem whose plaintext and
1863
ciphertext spaces are `A`, a key is any integer `k` such that
1864
`0 \leq k < n` where `n` is the size or cardinality of the set
1865
`A`.
1866
1867
OUTPUT:
1868
1869
- A shift cipher with secret key ``K``.
1870
1871
EXAMPLES::
1872
1873
sage: S = ShiftCryptosystem(AlphabeticStrings())
1874
sage: P = S.encoding("Shifting sand."); P
1875
SHIFTINGSAND
1876
sage: K = 3
1877
sage: E = S(K); E
1878
Shift cipher on Free alphabetic string monoid on A-Z
1879
sage: E(P)
1880
VKLIWLQJVDQG
1881
sage: D = S(S.inverse_key(K)); D
1882
Shift cipher on Free alphabetic string monoid on A-Z
1883
sage: D(E(P))
1884
SHIFTINGSAND
1885
1886
TESTS:
1887
1888
The key ``K`` must satisfy the inequality `0 \leq K < n` with `n`
1889
being the size of the plaintext, ciphertext, and key spaces. For the
1890
shift cryptosystem, all these spaces are the same alphabet. This
1891
inequality must be satisfied for each of the supported alphabets.
1892
The capital letters of the English alphabet::
1893
1894
sage: S = ShiftCryptosystem(AlphabeticStrings())
1895
sage: S(2 + S.alphabet_size())
1896
Traceback (most recent call last):
1897
...
1898
ValueError: K (=28) is outside the range of acceptable values for a key of this shift cryptosystem.
1899
sage: S(-2)
1900
Traceback (most recent call last):
1901
...
1902
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
1903
1904
The hexadecimal number system::
1905
1906
sage: S = ShiftCryptosystem(HexadecimalStrings())
1907
sage: S(1 + S.alphabet_size())
1908
Traceback (most recent call last):
1909
...
1910
ValueError: K (=17) is outside the range of acceptable values for a key of this shift cryptosystem.
1911
sage: S(-1)
1912
Traceback (most recent call last):
1913
...
1914
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
1915
1916
The binary number system::
1917
1918
sage: S = ShiftCryptosystem(BinaryStrings())
1919
sage: S(1 + S.alphabet_size())
1920
Traceback (most recent call last):
1921
...
1922
ValueError: K (=3) is outside the range of acceptable values for a key of this shift cryptosystem.
1923
sage: S(-2)
1924
Traceback (most recent call last):
1925
...
1926
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
1927
"""
1928
# Sanity check: the key K must satisfy the inequality
1929
# 0 <= K < n with n being the size of the plaintext, ciphertext, and
1930
# key spaces. For the shift cryptosystem, all these spaces are the
1931
# same alphabet.
1932
if 0 <= K < self.alphabet_size():
1933
return ShiftCipher(self, K)
1934
# from sage.rings.finite_rings.integer_mod import Mod
1935
# return ShiftCipher(self, Mod(K, self.alphabet_size()).lift())
1936
else:
1937
raise ValueError("K (=%s) is outside the range of acceptable values for a key of this shift cryptosystem." % K)
1938
1939
def _repr_(self):
1940
r"""
1941
Return the string representation of ``self``.
1942
1943
EXAMPLES::
1944
1945
sage: ShiftCryptosystem(AlphabeticStrings())
1946
Shift cryptosystem on Free alphabetic string monoid on A-Z
1947
sage: ShiftCryptosystem(HexadecimalStrings())
1948
Shift cryptosystem on Free hexadecimal string monoid
1949
sage: ShiftCryptosystem(BinaryStrings())
1950
Shift cryptosystem on Free binary string monoid
1951
"""
1952
# The shift cipher has the plaintext and ciphertext spaces defined
1953
# over the same non-empty alphabet. The cipher domain is the same
1954
# as the alphabet used for the plaintext and ciphertext spaces.
1955
return "Shift cryptosystem on %s" % self.cipher_domain()
1956
1957
def rank_by_chi_square(self, C, pdict):
1958
r"""
1959
Use the chi-square statistic to rank all possible keys. Currently,
1960
this method only applies to the capital letters of the English
1961
alphabet.
1962
1963
ALGORITHM:
1964
1965
Consider a non-empty alphabet `A` consisting of `n`
1966
elements, and let `C` be a ciphertext encoded using elements of
1967
`A`. The plaintext `P` corresponding to `C` is also encoded using
1968
elements of `A`. Let `M` be a candidate decipherment of `C`,
1969
i.e. `M` is the result of attempting to decrypt `C` using a key
1970
`k \in \ZZ/n\ZZ` which is not necessarily the same key used to
1971
encrypt `P`. Suppose `F_A(e)` is the characteristic frequency
1972
probability of `e \in A` and let `F_M(e)` be the message frequency
1973
probability with respect to `M`. The characteristic frequency
1974
probability distribution of an alphabet is the expected frequency
1975
probability distribution for that alphabet. The message frequency
1976
probability distribution of `M` provides a distribution of the ratio
1977
of character occurrences over message length. One can interpret the
1978
characteristic frequency probability `F_A(e)` as the expected
1979
probability, while the message frequency probability `F_M(e)` is
1980
the observed probability. If `M` is of length `L`, then the observed
1981
frequency of `e \in A` is
1982
1983
.. MATH::
1984
1985
O_M(e)
1986
=
1987
F_M(e) \cdot L
1988
1989
and the expected frequency of `e \in A` is
1990
1991
.. MATH::
1992
1993
E_A(e)
1994
=
1995
F_A(e) \cdot L
1996
1997
The chi-square rank `R_{\chi^2}(M)` of `M` corresponding to a key
1998
`k \in \ZZ/n\ZZ` is given by
1999
2000
.. MATH::
2001
2002
R_{\chi^2}(M)
2003
=
2004
\sum_{e \in A} \frac {\big( O_M(e) - E_A(e) \big)^2}
2005
{E_A(e)}
2006
2007
Cryptanalysis by exhaustive key search produces a candidate
2008
decipherment `M_{k}` for each possible key `k \in \ZZ/n\ZZ`. For
2009
a set
2010
`D = \big\{M_{k_1}, M_{k_2}, \dots, M_{k_r} \big\}`
2011
of all candidate decipherments corresponding to a ciphertext `C`,
2012
the smaller is the rank `R_{\chi^2}(M_{k_i})` the more likely
2013
that `k_i` is the secret key. This key ranking method is based on
2014
the Pearson chi-square test [PearsonTest09]_.
2015
2016
INPUT:
2017
2018
- ``C`` -- The ciphertext, a non-empty string. The ciphertext
2019
must be encoded using the upper-case letters of the English
2020
alphabet.
2021
2022
- ``pdict`` -- A dictionary of key, possible plaintext pairs.
2023
This should be the output of :func:`brute_force` with
2024
``ranking="none"``.
2025
2026
OUTPUT:
2027
2028
- A list ranking the most likely keys first. Each element of the
2029
list is a tuple of key, possible plaintext pairs.
2030
2031
EXAMPLES:
2032
2033
Use the chi-square statistic to rank all possible keys and their
2034
corresponding decipherment::
2035
2036
sage: S = ShiftCryptosystem(AlphabeticStrings())
2037
sage: P = S.encoding("Shi."); P
2038
SHI
2039
sage: K = 5
2040
sage: C = S.enciphering(K, P)
2041
sage: Pdict = S.brute_force(C)
2042
sage: S.rank_by_chi_square(C, Pdict)
2043
<BLANKLINE>
2044
[(9, ODE),
2045
(5, SHI),
2046
(20, DST),
2047
(19, ETU),
2048
(21, CRS),
2049
(10, NCD),
2050
(25, YNO),
2051
(6, RGH),
2052
(12, LAB),
2053
(8, PEF),
2054
(1, WLM),
2055
(11, MBC),
2056
(18, FUV),
2057
(17, GVW),
2058
(2, VKL),
2059
(4, TIJ),
2060
(3, UJK),
2061
(0, XMN),
2062
(16, HWX),
2063
(15, IXY),
2064
(23, APQ),
2065
(24, ZOP),
2066
(22, BQR),
2067
(7, QFG),
2068
(13, KZA),
2069
(14, JYZ)]
2070
2071
As more ciphertext is available, the reliability of the chi-square
2072
ranking function increases::
2073
2074
sage: P = S.encoding("Shift cipher."); P
2075
SHIFTCIPHER
2076
sage: C = S.enciphering(K, P)
2077
sage: Pdict = S.brute_force(C)
2078
sage: S.rank_by_chi_square(C, Pdict)
2079
<BLANKLINE>
2080
[(5, SHIFTCIPHER),
2081
(9, ODEBPYELDAN),
2082
(18, FUVSGPVCURE),
2083
(2, VKLIWFLSKHU),
2084
(20, DSTQENTASPC),
2085
(19, ETURFOUBTQD),
2086
(21, CRSPDMSZROB),
2087
(6, RGHESBHOGDQ),
2088
(7, QFGDRAGNFCP),
2089
(12, LABYMVBIAXK),
2090
(17, GVWTHQWDVSF),
2091
(24, ZOPMAJPWOLY),
2092
(1, WLMJXGMTLIV),
2093
(0, XMNKYHNUMJW),
2094
(11, MBCZNWCJBYL),
2095
(8, PEFCQZFMEBO),
2096
(25, YNOLZIOVNKX),
2097
(10, NCDAOXDKCZM),
2098
(3, UJKHVEKRJGT),
2099
(4, TIJGUDJQIFS),
2100
(22, BQROCLRYQNA),
2101
(16, HWXUIRXEWTG),
2102
(15, IXYVJSYFXUH),
2103
(14, JYZWKTZGYVI),
2104
(13, KZAXLUAHZWJ),
2105
(23, APQNBKQXPMZ)]
2106
2107
TESTS:
2108
2109
The ciphertext cannot be an empty string::
2110
2111
sage: S.rank_by_chi_square("", Pdict)
2112
Traceback (most recent call last):
2113
...
2114
AttributeError: 'str' object has no attribute 'parent'
2115
sage: S.rank_by_chi_square(S.encoding(""), Pdict)
2116
Traceback (most recent call last):
2117
...
2118
ValueError: The ciphertext must be a non-empty string.
2119
sage: S.rank_by_chi_square(S.encoding(" "), Pdict)
2120
Traceback (most recent call last):
2121
...
2122
ValueError: The ciphertext must be a non-empty string.
2123
2124
The ciphertext must be encoded using the capital letters of the
2125
English alphabet as implemented in
2126
:func:`AlphabeticStrings()
2127
<sage.monoids.string_monoid.AlphabeticStrings>`::
2128
2129
sage: H = HexadecimalStrings()
2130
sage: S.rank_by_chi_square(H.encoding("shift"), Pdict)
2131
Traceback (most recent call last):
2132
...
2133
TypeError: The ciphertext must be capital letters of the English alphabet.
2134
sage: B = BinaryStrings()
2135
sage: S.rank_by_chi_square(B.encoding("shift"), Pdict)
2136
Traceback (most recent call last):
2137
...
2138
TypeError: The ciphertext must be capital letters of the English alphabet.
2139
2140
The dictionary ``pdict`` cannot be empty::
2141
2142
sage: S.rank_by_chi_square(C, {})
2143
Traceback (most recent call last):
2144
...
2145
KeyError: 0
2146
2147
REFERENCES:
2148
2149
.. [PearsonTest09] `Pearson chi-square test
2150
<http://en.wikipedia.org/wiki/Goodness_of_fit>`_. Wikipedia,
2151
accessed 13th October 2009.
2152
"""
2153
# NOTE: the code here is very similar to that in the method
2154
# rank_by_chi_square() of the class AffineCryptosystem. The most
2155
# significant change in the code below is in how the secret key k
2156
# is processed.
2157
2158
# sanity check
2159
from sage.monoids.string_monoid import AlphabeticStrings
2160
if not isinstance(C.parent(), AlphabeticStringMonoid):
2161
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
2162
if str(C) == "":
2163
raise ValueError("The ciphertext must be a non-empty string.")
2164
2165
# compute the rank of each key
2166
AS = AlphabeticStrings()
2167
# the alphabet in question
2168
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
2169
StrAlph = str(Alph)
2170
# message length
2171
L = len(C)
2172
# expected frequency tally
2173
EA = AS.characteristic_frequency()
2174
for e in EA:
2175
EA[e] *= L
2176
# the rank R(M, k) of M for each key
2177
Rank = []
2178
for key in xrange(self.alphabet_size()):
2179
# observed frequency tally
2180
OM = pdict[key].frequency_distribution().function()
2181
for e in Alph:
2182
if e in OM:
2183
OM[e] *= L
2184
else:
2185
OM.setdefault(e, 0.0)
2186
# the rank R(M, K) of M with shift key k
2187
RMk = [(OM[AS(e)] - EA[e])**2 / EA[e] for e in StrAlph]
2188
Rank.append((sum(RMk), key))
2189
# Sort in non-decreasing order of squared-differences statistic. It's
2190
# possible that two different keys share the same squared-differences
2191
# statistic.
2192
Rank = sorted(Rank)
2193
RankedList = []
2194
# In the following line, the value of val is not used at all, making
2195
# it redundant to access val in the first place. This line
2196
# of code is written with readability in mind.
2197
[RankedList.append((key, pdict[key])) for val, key in Rank]
2198
return RankedList
2199
2200
def rank_by_squared_differences(self, C, pdict):
2201
r"""
2202
Use the squared-differences measure to rank all possible keys.
2203
Currently, this method only applies to the capital letters of
2204
the English alphabet.
2205
2206
ALGORITHM:
2207
2208
Consider a non-empty alphabet `A` consisting of `n`
2209
elements, and let `C` be a ciphertext encoded using elements of
2210
`A`. The plaintext `P` corresponding to `C` is also encoded using
2211
elements of `A`. Let `M` be a candidate decipherment of `C`,
2212
i.e. `M` is the result of attempting to decrypt `C` using a key
2213
`k \in \ZZ/n\ZZ` which is not necessarily the same key used to
2214
encrypt `P`. Suppose `F_A(e)` is the characteristic frequency
2215
probability of `e \in A` and let `F_M(e)` be the message
2216
frequency probability with respect to `M`. The characteristic
2217
frequency probability distribution of an alphabet is the expected
2218
frequency probability distribution for that alphabet. The message
2219
frequency probability distribution of `M` provides a distribution
2220
of the ratio of character occurrences over message length. One can
2221
interpret the characteristic frequency probability `F_A(e)` as the
2222
expected probability, while the message frequency probability
2223
`F_M(e)` is the observed probability. If `M` is of length `L`, then
2224
the observed frequency of `e \in A` is
2225
2226
.. MATH::
2227
2228
O_M(e)
2229
=
2230
F_M(e) \cdot L
2231
2232
and the expected frequency of `e \in A` is
2233
2234
.. MATH::
2235
2236
E_A(e)
2237
=
2238
F_A(e) \cdot L
2239
2240
The squared-differences, or residual sum of squares, rank
2241
`R_{RSS}(M)` of `M` corresponding to a key
2242
`k \in \ZZ/n\ZZ` is given by
2243
2244
.. MATH::
2245
2246
R_{RSS}(M)
2247
=
2248
\sum_{e \in A} \big( O_M(e) - E_A(e) \big)^2
2249
2250
Cryptanalysis by exhaustive key search produces a candidate
2251
decipherment `M_{k}` for each possible key `k \in \ZZ/n\ZZ`. For
2252
a set
2253
`D = \big\{M_{k_1}, M_{k_2}, \dots, M_{k_r} \big\}`
2254
of all candidate decipherments corresponding to a ciphertext `C`,
2255
the smaller is the rank `R_{RSS}(M_{k_i})` the more likely
2256
that `k_i` is the secret key. This key ranking method is based
2257
on the residual sum of squares measure [RSS09]_.
2258
2259
INPUT:
2260
2261
- ``C`` -- The ciphertext, a non-empty string. The ciphertext
2262
must be encoded using the upper-case letters of the English
2263
alphabet.
2264
2265
- ``pdict`` -- A dictionary of key, possible plaintext pairs.
2266
This should be the output of :func:`brute_force` with
2267
``ranking="none"``.
2268
2269
OUTPUT:
2270
2271
- A list ranking the most likely keys first. Each element of the
2272
list is a tuple of key, possible plaintext pairs.
2273
2274
EXAMPLES:
2275
2276
Use the method of squared differences to rank all possible keys
2277
and their corresponding decipherment::
2278
2279
sage: S = ShiftCryptosystem(AlphabeticStrings())
2280
sage: P = S.encoding("Shi."); P
2281
SHI
2282
sage: K = 5
2283
sage: C = S.enciphering(K, P)
2284
sage: Pdict = S.brute_force(C)
2285
sage: S.rank_by_squared_differences(C, Pdict)
2286
<BLANKLINE>
2287
[(19, ETU),
2288
(9, ODE),
2289
(20, DST),
2290
(5, SHI),
2291
(8, PEF),
2292
(4, TIJ),
2293
(25, YNO),
2294
(21, CRS),
2295
(6, RGH),
2296
(10, NCD),
2297
(12, LAB),
2298
(23, APQ),
2299
(24, ZOP),
2300
(0, XMN),
2301
(13, KZA),
2302
(15, IXY),
2303
(1, WLM),
2304
(16, HWX),
2305
(22, BQR),
2306
(11, MBC),
2307
(18, FUV),
2308
(2, VKL),
2309
(17, GVW),
2310
(7, QFG),
2311
(3, UJK),
2312
(14, JYZ)]
2313
2314
As more ciphertext is available, the reliability of the squared
2315
differences ranking function increases::
2316
2317
sage: P = S.encoding("Shift cipher."); P
2318
SHIFTCIPHER
2319
sage: C = S.enciphering(K, P)
2320
sage: Pdict = S.brute_force(C)
2321
sage: S.rank_by_squared_differences(C, Pdict)
2322
<BLANKLINE>
2323
[(20, DSTQENTASPC),
2324
(5, SHIFTCIPHER),
2325
(9, ODEBPYELDAN),
2326
(19, ETURFOUBTQD),
2327
(6, RGHESBHOGDQ),
2328
(16, HWXUIRXEWTG),
2329
(8, PEFCQZFMEBO),
2330
(21, CRSPDMSZROB),
2331
(22, BQROCLRYQNA),
2332
(25, YNOLZIOVNKX),
2333
(3, UJKHVEKRJGT),
2334
(18, FUVSGPVCURE),
2335
(4, TIJGUDJQIFS),
2336
(10, NCDAOXDKCZM),
2337
(7, QFGDRAGNFCP),
2338
(24, ZOPMAJPWOLY),
2339
(2, VKLIWFLSKHU),
2340
(12, LABYMVBIAXK),
2341
(17, GVWTHQWDVSF),
2342
(1, WLMJXGMTLIV),
2343
(13, KZAXLUAHZWJ),
2344
(0, XMNKYHNUMJW),
2345
(15, IXYVJSYFXUH),
2346
(14, JYZWKTZGYVI),
2347
(11, MBCZNWCJBYL),
2348
(23, APQNBKQXPMZ)]
2349
2350
TESTS:
2351
2352
The ciphertext cannot be an empty string::
2353
2354
sage: S.rank_by_squared_differences("", Pdict)
2355
Traceback (most recent call last):
2356
...
2357
AttributeError: 'str' object has no attribute 'parent'
2358
sage: S.rank_by_squared_differences(S.encoding(""), Pdict)
2359
Traceback (most recent call last):
2360
...
2361
ValueError: The ciphertext must be a non-empty string.
2362
sage: S.rank_by_squared_differences(S.encoding(" "), Pdict)
2363
Traceback (most recent call last):
2364
...
2365
ValueError: The ciphertext must be a non-empty string.
2366
2367
The ciphertext must be encoded using the capital letters of the
2368
English alphabet as implemented in
2369
:func:`AlphabeticStrings()
2370
<sage.monoids.string_monoid.AlphabeticStrings>`::
2371
2372
sage: H = HexadecimalStrings()
2373
sage: S.rank_by_squared_differences(H.encoding("shift"), Pdict)
2374
Traceback (most recent call last):
2375
...
2376
TypeError: The ciphertext must be capital letters of the English alphabet.
2377
sage: B = BinaryStrings()
2378
sage: S.rank_by_squared_differences(B.encoding("shift"), Pdict)
2379
Traceback (most recent call last):
2380
...
2381
TypeError: The ciphertext must be capital letters of the English alphabet.
2382
2383
The dictionary ``pdict`` cannot be empty::
2384
2385
sage: S.rank_by_squared_differences(C, {})
2386
Traceback (most recent call last):
2387
...
2388
KeyError: 0
2389
2390
REFERENCES:
2391
2392
.. [RSS09] `Residual sum of squares
2393
<http://en.wikipedia.org/wiki/Residual_sum_of_squares>`_.
2394
Wikipedia, accessed 13th October 2009.
2395
"""
2396
# NOTE: the code in this method is very similar to that in the
2397
# method rank_by_chi_square(). The only difference here is the
2398
# line that computes the list RMk.
2399
2400
# sanity check
2401
from sage.monoids.string_monoid import (
2402
AlphabeticStringMonoid,
2403
AlphabeticStrings)
2404
if not isinstance(C.parent(), AlphabeticStringMonoid):
2405
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
2406
if str(C) == "":
2407
raise ValueError("The ciphertext must be a non-empty string.")
2408
2409
# compute the rank of each key
2410
AS = AlphabeticStrings()
2411
# the alphabet in question
2412
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
2413
StrAlph = str(Alph)
2414
# message length
2415
L = len(C)
2416
# expected frequency tally
2417
EA = AS.characteristic_frequency()
2418
for e in EA:
2419
EA[e] *= L
2420
# the rank R(M, k) of M for each key
2421
Rank = []
2422
for key in xrange(self.alphabet_size()):
2423
# observed frequency tally
2424
OM = pdict[key].frequency_distribution().function()
2425
for e in Alph:
2426
if e in OM:
2427
OM[e] *= L
2428
else:
2429
OM.setdefault(e, 0.0)
2430
# the rank R(M, K) of M with shift key k
2431
RMk = [(OM[AS(e)] - EA[e])**2 for e in StrAlph]
2432
Rank.append((sum(RMk), key))
2433
# Sort in non-decreasing order of squared-differences statistic. It's
2434
# possible that two different keys share the same squared-differences
2435
# statistic.
2436
Rank = sorted(Rank)
2437
RankedList = []
2438
# In the following line, the value of val is not used at all, making
2439
# it redundant to access val in the first place. This line
2440
# of code is written with readability in mind.
2441
[RankedList.append((key, pdict[key])) for val, key in Rank]
2442
return RankedList
2443
2444
def brute_force(self, C, ranking="none"):
2445
r"""
2446
Attempt a brute force cryptanalysis of the ciphertext ``C``.
2447
2448
INPUT:
2449
2450
- ``C`` -- A ciphertext over one of the supported alphabets of this
2451
shift cryptosystem. See the class :class:`ShiftCryptosystem` for
2452
documentation on the supported alphabets.
2453
2454
- ``ranking`` -- (default ``"none"``) the method to use for
2455
ranking all possible keys. If ``ranking="none"``, then do not
2456
use any ranking function. The following ranking functions are
2457
supported:
2458
2459
- ``"chisquare"`` -- the chi-square ranking function as
2460
implemented in the method :func:`rank_by_chi_square`.
2461
2462
- ``"squared_differences"`` -- the squared differences ranking
2463
function as implemented in the method
2464
:func:`rank_by_squared_differences`.
2465
2466
OUTPUT:
2467
2468
- All the possible plaintext sequences corresponding to the
2469
ciphertext ``C``. This method effectively uses all the possible
2470
keys in this shift cryptosystem to decrypt ``C``. The method is
2471
also referred to as exhaustive key search. The output is
2472
a dictionary of key, plaintext pairs.
2473
2474
EXAMPLES:
2475
2476
Cryptanalyze using all possible keys for various alphabets. Over
2477
the upper-case letters of the English alphabet::
2478
2479
sage: S = ShiftCryptosystem(AlphabeticStrings())
2480
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
2481
sage: K = 7
2482
sage: C = S.enciphering(K, P)
2483
sage: Dict = S.brute_force(C)
2484
sage: for k in xrange(len(Dict)):
2485
... if Dict[k] == P:
2486
... print "key =", k
2487
...
2488
key = 7
2489
2490
Over the hexadecimal number system::
2491
2492
sage: S = ShiftCryptosystem(HexadecimalStrings())
2493
sage: P = S.encoding("Encryption & decryption shifts along the alphabet.")
2494
sage: K = 5
2495
sage: C = S.enciphering(K, P)
2496
sage: Dict = S.brute_force(C)
2497
sage: for k in xrange(len(Dict)):
2498
... if Dict[k] == P:
2499
... print "key =", k
2500
...
2501
key = 5
2502
2503
And over the binary number system::
2504
2505
sage: S = ShiftCryptosystem(BinaryStrings())
2506
sage: P = S.encoding("The binary alphabet is very insecure.")
2507
sage: K = 1
2508
sage: C = S.enciphering(K, P)
2509
sage: Dict = S.brute_force(C)
2510
sage: for k in xrange(len(Dict)):
2511
... if Dict[k] == P:
2512
... print "key =", k
2513
...
2514
key = 1
2515
2516
Don't use any ranking functions, i.e. ``ranking="none"``::
2517
2518
sage: S = ShiftCryptosystem(AlphabeticStrings())
2519
sage: P = S.encoding("Shifting using modular arithmetic.")
2520
sage: K = 8
2521
sage: C = S.enciphering(K, P)
2522
sage: pdict = S.brute_force(C)
2523
sage: sorted(pdict.items())
2524
<BLANKLINE>
2525
[(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
2526
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
2527
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
2528
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
2529
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
2530
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
2531
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
2532
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
2533
(8, SHIFTINGUSINGMODULARARITHMETIC),
2534
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
2535
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
2536
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
2537
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
2538
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
2539
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
2540
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
2541
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
2542
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
2543
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
2544
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
2545
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
2546
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
2547
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
2548
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
2549
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
2550
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL)]
2551
2552
Use the chi-square ranking function, i.e. ``ranking="chisquare"``::
2553
2554
sage: S.brute_force(C, ranking="chisquare")
2555
<BLANKLINE>
2556
[(8, SHIFTINGUSINGMODULARARITHMETIC),
2557
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
2558
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
2559
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
2560
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
2561
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
2562
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
2563
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
2564
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
2565
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
2566
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
2567
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
2568
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
2569
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
2570
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
2571
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
2572
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
2573
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
2574
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
2575
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
2576
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
2577
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
2578
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
2579
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
2580
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
2581
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT)]
2582
2583
Use the squared differences ranking function, i.e.
2584
``ranking="squared_differences"``::
2585
2586
sage: S.brute_force(C, ranking="squared_differences")
2587
<BLANKLINE>
2588
[(8, SHIFTINGUSINGMODULARARITHMETIC),
2589
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
2590
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
2591
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
2592
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
2593
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
2594
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
2595
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
2596
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
2597
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
2598
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
2599
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
2600
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
2601
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
2602
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
2603
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
2604
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
2605
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
2606
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
2607
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
2608
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
2609
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
2610
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
2611
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
2612
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
2613
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF)]
2614
2615
TESTS:
2616
2617
Currently, the octal number system is not supported as an alphabet for
2618
this shift cryptosystem::
2619
2620
sage: SA = ShiftCryptosystem(AlphabeticStrings())
2621
sage: OctStr = OctalStrings()
2622
sage: C = OctStr([1, 2, 3])
2623
sage: SA.brute_force(C)
2624
Traceback (most recent call last):
2625
...
2626
TypeError: ciphertext must be encoded using one of the supported cipher domains of this shift cryptosystem.
2627
2628
Nor is the radix-64 alphabet supported::
2629
2630
sage: Rad64 = Radix64Strings()
2631
sage: C = Rad64([1, 2, 3])
2632
sage: SA.brute_force(C)
2633
Traceback (most recent call last):
2634
...
2635
TypeError: ciphertext must be encoded using one of the supported cipher domains of this shift cryptosystem.
2636
"""
2637
# Sanity check: ensure that C is encoded using one of the
2638
# supported alphabets of this shift cryptosystem.
2639
from sage.monoids.string_monoid import (
2640
AlphabeticStringMonoid,
2641
BinaryStringMonoid,
2642
HexadecimalStringMonoid)
2643
if not isinstance(C.parent(), (
2644
AlphabeticStringMonoid,
2645
BinaryStringMonoid,
2646
HexadecimalStringMonoid)):
2647
raise TypeError("ciphertext must be encoded using one of the supported cipher domains of this shift cryptosystem.")
2648
ranking_functions = ["none", "chisquare", "squared_differences"]
2649
if ranking not in ranking_functions:
2650
raise ValueError("Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.")
2651
2652
# Now do the actual task of cryptanalysis by means of exhaustive key
2653
# search, also known as the brute force method.
2654
# let D be a dictionary of key/plaintext pairs
2655
D = {}
2656
2657
# NOTE: This loop is a good candidate for loop unrolling. Unless we
2658
# can justify that this block of code is a bottleneck on the runtime
2659
# of the method, we should leave it as is. For the alphabets that
2660
# are supported by this shift cryptosystem, it can be a waste of
2661
# time optimizing the code when the largest alphabet size is less
2662
# than 100.
2663
for k in xrange(self.alphabet_size()):
2664
D.setdefault(k, self.deciphering(k, C))
2665
2666
if ranking == "none":
2667
return D
2668
if ranking == "chisquare":
2669
return self.rank_by_chi_square(C, D)
2670
if ranking == "squared_differences":
2671
return self.rank_by_squared_differences(C, D)
2672
2673
def deciphering(self, K, C):
2674
r"""
2675
Decrypt the ciphertext ``C`` with the key ``K`` using shift cipher
2676
decryption.
2677
2678
INPUT:
2679
2680
- ``K`` -- a secret key; a key belonging to the key space of this
2681
shift cipher. This key is an integer `k` satisfying the inequality
2682
`0 \leq k < n`, where `n` is the size of the cipher domain.
2683
2684
- ``C`` -- a string of ciphertext; possibly an empty string.
2685
Characters in this string must be encoded using one of the
2686
supported alphabets. See the method :func:`encoding()`
2687
for more information.
2688
2689
OUTPUT:
2690
2691
- The plaintext corresponding to the ciphertext ``C``.
2692
2693
EXAMPLES:
2694
2695
Let's perform decryption over the supported alphabets. Here is
2696
decryption over the capital letters of the English alphabet::
2697
2698
sage: S = ShiftCryptosystem(AlphabeticStrings())
2699
sage: P = S.encoding("Stop shifting me."); P
2700
STOPSHIFTINGME
2701
sage: K = 13
2702
sage: C = S.enciphering(K, P); C
2703
FGBCFUVSGVATZR
2704
sage: S.deciphering(K, C) == P
2705
True
2706
2707
Decryption over the hexadecimal number system::
2708
2709
sage: S = ShiftCryptosystem(HexadecimalStrings())
2710
sage: P = S.encoding("Shift me now."); P
2711
5368696674206d65206e6f772e
2712
sage: K = 7
2713
sage: C = S.enciphering(K, P); C
2714
cadfd0ddeb97d4dc97d5d6ee95
2715
sage: S.deciphering(K, C) == P
2716
True
2717
2718
Decryption over the binary number system::
2719
2720
sage: S = ShiftCryptosystem(BinaryStrings())
2721
sage: P = S.encoding("OK, enough shifting."); P
2722
0100111101001011001011000010000001100101011011100110111101110101011001110110100000100000011100110110100001101001011001100111010001101001011011100110011100101110
2723
sage: K = 1
2724
sage: C = S.enciphering(K, P); C
2725
1011000010110100110100111101111110011010100100011001000010001010100110001001011111011111100011001001011110010110100110011000101110010110100100011001100011010001
2726
sage: S.deciphering(K, C) == P
2727
True
2728
"""
2729
E = self(self.inverse_key(K))
2730
return E(C)
2731
2732
def enciphering(self, K, P):
2733
r"""
2734
Encrypt the plaintext ``P`` with the key ``K`` using shift cipher
2735
encryption.
2736
2737
INPUT:
2738
2739
- ``K`` -- a key belonging to the key space of this shift cipher.
2740
This key is an integer `k` satisfying the inequality
2741
`0 \leq k < n`, where `n` is the size of the cipher domain.
2742
2743
- ``P`` -- a string of plaintext; possibly an empty string.
2744
Characters in this string must be encoded using one of the
2745
supported alphabets. See the method :func:`encoding()` for more
2746
information.
2747
2748
OUTPUT:
2749
2750
- The ciphertext corresponding to the plaintext ``P``.
2751
2752
EXAMPLES:
2753
2754
Let's perform encryption over the supported alphabets. Here is
2755
encryption over the capital letters of the English alphabet::
2756
2757
sage: S = ShiftCryptosystem(AlphabeticStrings())
2758
sage: P = S.encoding("Shift your gear."); P
2759
SHIFTYOURGEAR
2760
sage: K = 3
2761
sage: S.enciphering(K, P)
2762
VKLIWBRXUJHDU
2763
2764
Encryption over the hexadecimal number system::
2765
2766
sage: S = ShiftCryptosystem(HexadecimalStrings())
2767
sage: P = S.encoding("Capitalize with the shift key."); P
2768
4361706974616c697a65207769746820746865207368696674206b65792e
2769
sage: K = 5
2770
sage: S.enciphering(K, P)
2771
98b6c5bec9b6b1becfba75ccbec9bd75c9bdba75c8bdbebbc975b0bace73
2772
2773
Encryption over the binary number system::
2774
2775
sage: S = ShiftCryptosystem(BinaryStrings())
2776
sage: P = S.encoding("Don't shift."); P
2777
010001000110111101101110001001110111010000100000011100110110100001101001011001100111010000101110
2778
sage: K = 1
2779
sage: S.enciphering(K, P)
2780
101110111001000010010001110110001000101111011111100011001001011110010110100110011000101111010001
2781
"""
2782
E = self(K)
2783
return E(P)
2784
2785
def encoding(self, S):
2786
r"""
2787
The encoding of the string ``S`` over the string monoid of this
2788
shift cipher. For example, if the string monoid of this cryptosystem
2789
is
2790
:class:`AlphabeticStringMonoid <sage.monoids.string_monoid.AlphabeticStringMonoid>`,
2791
then the encoding of ``S`` would be its upper-case equivalent
2792
stripped of all non-alphabetic characters. The following alphabets
2793
are supported for the shift cipher:
2794
2795
- capital letters of the English alphabet as implemented in
2796
:func:`AlphabeticStrings() <sage.monoids.string_monoid.AlphabeticStrings>`
2797
2798
- the alphabet consisting of the hexadecimal number system as
2799
implemented in
2800
:func:`HexadecimalStrings() <sage.monoids.string_monoid.HexadecimalStrings>`
2801
2802
- the alphabet consisting of the binary number system as implemented in
2803
:func:`BinaryStrings() <sage.monoids.string_monoid.BinaryStrings>`
2804
2805
INPUT:
2806
2807
- ``S`` -- a string, possibly empty.
2808
2809
OUTPUT:
2810
2811
- The encoding of ``S`` over the string monoid of this cryptosystem.
2812
If ``S`` is an empty string, return an empty string.
2813
2814
EXAMPLES:
2815
2816
Encoding over the upper-case letters of the English alphabet::
2817
2818
sage: S = ShiftCryptosystem(AlphabeticStrings())
2819
sage: S.encoding("Shift cipher on capital letters of the English alphabet.")
2820
SHIFTCIPHERONCAPITALLETTERSOFTHEENGLISHALPHABET
2821
2822
Encoding over the binary system::
2823
2824
sage: S = ShiftCryptosystem(BinaryStrings())
2825
sage: S.encoding("Binary")
2826
010000100110100101101110011000010111001001111001
2827
2828
Encoding over the hexadecimal system::
2829
2830
sage: S = ShiftCryptosystem(HexadecimalStrings())
2831
sage: S.encoding("Over hexadecimal system.")
2832
4f7665722068657861646563696d616c2073797374656d2e
2833
2834
The argument ``S`` can be an empty string, in which case an empty
2835
string is returned::
2836
2837
sage: ShiftCryptosystem(AlphabeticStrings()).encoding("")
2838
<BLANKLINE>
2839
sage: ShiftCryptosystem(HexadecimalStrings()).encoding("")
2840
<BLANKLINE>
2841
sage: ShiftCryptosystem(BinaryStrings()).encoding("")
2842
<BLANKLINE>
2843
"""
2844
D = self.cipher_domain()
2845
if isinstance(D, AlphabeticStringMonoid):
2846
return D(strip_encoding(S))
2847
try:
2848
return D.encoding(S)
2849
except:
2850
raise TypeError("Argument S = %s does not encode in the cipher domain" % S)
2851
2852
def inverse_key(self, K):
2853
r"""
2854
The inverse key corresponding to the key ``K``. For the shift cipher,
2855
the inverse key corresponding to ``K`` is `-K \bmod n`, where
2856
`n > 0` is the size of the cipher domain, i.e. the
2857
plaintext/ciphertext space. A key `k` of the shift cipher is an
2858
integer `0 \leq k < n`. The key `k = 0` has no effect on either the
2859
plaintext or the ciphertext.
2860
2861
INPUT:
2862
2863
- ``K`` -- a key for this shift cipher. This must be an integer `k`
2864
such that `0 \leq k < n`, where `n` is the size of the cipher domain.
2865
2866
OUTPUT:
2867
2868
- The inverse key corresponding to ``K``.
2869
2870
EXAMPLES:
2871
2872
Some random keys and their respective inverse keys::
2873
2874
sage: S = ShiftCryptosystem(AlphabeticStrings())
2875
sage: key = S.random_key(); key # random
2876
2
2877
sage: S.inverse_key(key) # random
2878
24
2879
sage: S = ShiftCryptosystem(HexadecimalStrings())
2880
sage: key = S.random_key(); key # random
2881
12
2882
sage: S.inverse_key(key) # random
2883
4
2884
sage: S = ShiftCryptosystem(BinaryStrings())
2885
sage: key = S.random_key(); key # random
2886
1
2887
sage: S.inverse_key(key) # random
2888
1
2889
sage: key = S.random_key(); key # random
2890
0
2891
sage: S.inverse_key(key) # random
2892
0
2893
2894
Regardless of the value of a key, the addition of the key and its
2895
inverse must be equal to the alphabet size. This relationship holds
2896
exactly when the value of the key is non-zero::
2897
2898
sage: S = ShiftCryptosystem(AlphabeticStrings())
2899
sage: K = S.random_key()
2900
sage: while K == 0:
2901
... K = S.random_key()
2902
...
2903
sage: invK = S.inverse_key(K)
2904
sage: K + invK == S.alphabet_size()
2905
True
2906
sage: invK + K == S.alphabet_size()
2907
True
2908
sage: K = S.random_key()
2909
sage: while K != 0:
2910
... K = S.random_key()
2911
...
2912
sage: invK = S.inverse_key(K)
2913
sage: K + invK != S.alphabet_size()
2914
True
2915
sage: K; invK
2916
0
2917
0
2918
2919
TESTS:
2920
2921
The key ``K`` must satisfy the inequality `0 \leq K < n` with `n`
2922
being the size of the plaintext, ciphertext, and key spaces. For the
2923
shift cryptosystem, all these spaces are the same alphabet. This
2924
inequality must be satisfied for each of the supported alphabets.
2925
The capital letters of the English alphabet::
2926
2927
sage: S = ShiftCryptosystem(AlphabeticStrings())
2928
sage: S.inverse_key(S.alphabet_size())
2929
Traceback (most recent call last):
2930
...
2931
ValueError: K (=26) is outside the range of acceptable values for a key of this shift cryptosystem.
2932
sage: S.inverse_key(-1)
2933
Traceback (most recent call last):
2934
...
2935
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
2936
2937
The hexadecimal number system::
2938
2939
sage: S = ShiftCryptosystem(HexadecimalStrings())
2940
sage: S.inverse_key(S.alphabet_size())
2941
Traceback (most recent call last):
2942
...
2943
ValueError: K (=16) is outside the range of acceptable values for a key of this shift cryptosystem.
2944
sage: S.inverse_key(-1)
2945
Traceback (most recent call last):
2946
...
2947
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
2948
2949
The binary number system::
2950
2951
sage: S = ShiftCryptosystem(BinaryStrings())
2952
sage: S.inverse_key(S.alphabet_size())
2953
Traceback (most recent call last):
2954
...
2955
ValueError: K (=2) is outside the range of acceptable values for a key of this shift cryptosystem.
2956
sage: S.inverse_key(-1)
2957
Traceback (most recent call last):
2958
...
2959
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
2960
"""
2961
# Sanity check: the key K must satisfy the inequality
2962
# 0 <= K < n with n being the size of the plaintext, ciphertext, and
2963
# key spaces. For the shift cryptosystem, all these spaces are the
2964
# same alphabet.
2965
if 0 <= K < self.alphabet_size():
2966
# Let A be the alphabet of this cryptosystem and let n be the
2967
# number of elements in A. If k is a key, then the corresponding
2968
# inverse key is -k mod n.
2969
return self.key_space()(-Integer(K)).lift()
2970
else:
2971
raise ValueError("K (=%s) is outside the range of acceptable values for a key of this shift cryptosystem." % K)
2972
2973
def random_key(self):
2974
r"""
2975
Generate a random key within the key space of this shift cipher.
2976
The generated key is an integer `0 \leq k < n` with `n` being the
2977
size of the cipher domain. Thus there are `n` possible keys in the
2978
key space, which is the set `\ZZ / n\ZZ`. The key `k = 0` has no
2979
effect on either the plaintext or the ciphertext.
2980
2981
OUTPUT:
2982
2983
- A random key within the key space of this shift cryptosystem.
2984
2985
EXAMPLES::
2986
2987
sage: S = ShiftCryptosystem(AlphabeticStrings())
2988
sage: S.random_key() # random
2989
18
2990
sage: S = ShiftCryptosystem(BinaryStrings())
2991
sage: S.random_key() # random
2992
0
2993
sage: S = ShiftCryptosystem(HexadecimalStrings())
2994
sage: S.random_key() # random
2995
5
2996
2997
Regardless of the value of a key, the addition of the key and its
2998
inverse must be equal to the alphabet size. This relationship holds
2999
exactly when the value of the key is non-zero::
3000
3001
sage: S = ShiftCryptosystem(AlphabeticStrings())
3002
sage: K = S.random_key()
3003
sage: while K == 0:
3004
... K = S.random_key()
3005
...
3006
sage: invK = S.inverse_key(K)
3007
sage: K + invK == S.alphabet_size()
3008
True
3009
sage: invK + K == S.alphabet_size()
3010
True
3011
sage: K = S.random_key()
3012
sage: while K != 0:
3013
... K = S.random_key()
3014
...
3015
sage: invK = S.inverse_key(K)
3016
sage: K + invK != S.alphabet_size()
3017
True
3018
sage: K; invK
3019
0
3020
0
3021
"""
3022
# Return a random element in ZZ/nZZ where n is the number of elements
3023
# in the plaintext/ciphertext alphabet and key space.
3024
from sage.misc.prandom import randint
3025
return Integer(randint(0, self.alphabet_size() - 1))
3026
3027
class SubstitutionCryptosystem(SymmetricKeyCryptosystem):
3028
"""
3029
Create a substitution cryptosystem.
3030
3031
INPUT:
3032
3033
- ``S`` - a string monoid over some alphabet
3034
3035
OUTPUT:
3036
3037
- A substitution cryptosystem over the alphabet ``S``.
3038
3039
EXAMPLES::
3040
3041
sage: M = AlphabeticStrings()
3042
sage: E = SubstitutionCryptosystem(M)
3043
sage: E
3044
Substitution cryptosystem on Free alphabetic string monoid on A-Z
3045
sage: K = M([ 25-i for i in range(26) ])
3046
sage: K
3047
ZYXWVUTSRQPONMLKJIHGFEDCBA
3048
sage: e = E(K)
3049
sage: m = M("THECATINTHEHAT")
3050
sage: e(m)
3051
GSVXZGRMGSVSZG
3052
3053
TESTS::
3054
3055
sage: M = AlphabeticStrings()
3056
sage: E = SubstitutionCryptosystem(M)
3057
sage: E == loads(dumps(E))
3058
True
3059
"""
3060
3061
def __init__(self, S):
3062
"""
3063
See ``SubstitutionCryptosystem`` for full documentation.
3064
3065
EXAMPLES::
3066
3067
sage: M = AlphabeticStrings()
3068
sage: E = SubstitutionCryptosystem(M)
3069
sage: E
3070
Substitution cryptosystem on Free alphabetic string monoid on A-Z
3071
"""
3072
if not isinstance(S, StringMonoid_class):
3073
raise TypeError("S (= %s) must be a string monoid." % S)
3074
SymmetricKeyCryptosystem.__init__(self, S, S, S)
3075
3076
def __call__(self, K):
3077
"""
3078
Create a substitution cipher.
3079
3080
INPUT:
3081
3082
- ``K`` - a key which is a permutation of the cryptosystem alphabet
3083
3084
EXAMPLES::
3085
3086
sage: M = AlphabeticStrings()
3087
sage: E = SubstitutionCryptosystem(M)
3088
sage: E
3089
Substitution cryptosystem on Free alphabetic string monoid on A-Z
3090
sage: K = M([ 25-i for i in range(26) ])
3091
sage: K
3092
ZYXWVUTSRQPONMLKJIHGFEDCBA
3093
sage: e = E(K)
3094
sage: m = M("THECATINTHEHAT")
3095
sage: e(m)
3096
GSVXZGRMGSVSZG
3097
"""
3098
if not isinstance(K, StringMonoidElement):
3099
raise TypeError("K (= %s) must be a string." % K)
3100
if K.parent() != self.key_space():
3101
raise TypeError("K (= %s) must be a string in the key space." % K)
3102
return SubstitutionCipher(self, K)
3103
3104
def _repr_(self):
3105
"""
3106
Return a string representation of self.
3107
3108
EXAMPLES::
3109
3110
sage: A = AlphabeticStrings()
3111
sage: S = SubstitutionCryptosystem(A)
3112
sage: S
3113
Substitution cryptosystem on Free alphabetic string monoid on A-Z
3114
sage: S._repr_()
3115
'Substitution cryptosystem on Free alphabetic string monoid on A-Z'
3116
"""
3117
return "Substitution cryptosystem on %s" % self.cipher_domain()
3118
3119
def random_key(self):
3120
"""
3121
Generate a random key within the key space of this substitution
3122
cipher. The generated key is a permutation of the cryptosystem
3123
alphabet. Let `n` be the length of the alphabet. Then there are
3124
`n!` possible keys in the key space.
3125
3126
OUTPUT:
3127
3128
- A random key within the key space of this cryptosystem.
3129
3130
EXAMPLES::
3131
3132
sage: A = AlphabeticStrings()
3133
sage: S = SubstitutionCryptosystem(A)
3134
sage: K = S.random_key()
3135
sage: Ki = S.inverse_key(K)
3136
sage: M = "THECATINTHEHAT"
3137
sage: e = S(K)
3138
sage: d = S(Ki)
3139
sage: d(e(A(M))) == A(M)
3140
True
3141
"""
3142
S = self.cipher_domain()
3143
n = S.ngens()
3144
I = SymmetricGroup(n).random_element().list()
3145
return S([ i-1 for i in I ])
3146
3147
def inverse_key(self, K):
3148
"""
3149
The inverse key corresponding to the key ``K``. The specified key is a
3150
permutation of the cryptosystem alphabet.
3151
3152
INPUT:
3153
3154
- ``K`` - a key belonging to the key space of this cryptosystem
3155
3156
OUTPUT:
3157
3158
- The inverse key of ``K``.
3159
3160
EXAMPLES::
3161
3162
sage: S = AlphabeticStrings()
3163
sage: E = SubstitutionCryptosystem(S)
3164
sage: K = E.random_key()
3165
sage: L = E.inverse_key(K)
3166
sage: M = S("THECATINTHEHAT")
3167
sage: e = E(K)
3168
sage: c = E(L)
3169
sage: c(e(M))
3170
THECATINTHEHAT
3171
"""
3172
I = K._element_list
3173
S = self.cipher_domain()
3174
n = S.ngens()
3175
return S([ I.index(i) for i in range(n) ])
3176
3177
def encoding(self, M):
3178
"""
3179
The encoding of the string ``M`` over the string monoid of this
3180
substitution cipher. For example, if the string monoid of this
3181
cryptosystem is :class:`AlphabeticStringMonoid`, then the encoding
3182
of ``M`` would be its upper-case equivalent stripped of all
3183
non-alphabetic characters.
3184
3185
INPUT:
3186
3187
- ``M`` - a string, possibly empty
3188
3189
OUTPUT:
3190
3191
- The encoding of ``M`` over the string monoid of this cryptosystem.
3192
3193
EXAMPLES::
3194
3195
sage: M = "Peter Pan(ning) for gold."
3196
sage: A = AlphabeticStrings()
3197
sage: S = SubstitutionCryptosystem(A)
3198
sage: S.encoding(M) == A.encoding(M)
3199
True
3200
"""
3201
S = self.cipher_domain()
3202
if isinstance(S, AlphabeticStringMonoid):
3203
return S(strip_encoding(M))
3204
try:
3205
return S.encoding(M)
3206
except:
3207
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
3208
3209
def deciphering(self, K, C):
3210
"""
3211
Decrypt the ciphertext ``C`` using the key ``K``.
3212
3213
INPUT:
3214
3215
- ``K`` - a key belonging to the key space of this substitution cipher
3216
3217
- ``C`` - a string (possibly empty) over the string monoid of this
3218
cryptosystem.
3219
3220
OUTPUT:
3221
3222
- The plaintext corresponding to the ciphertext ``C``.
3223
3224
EXAMPLES::
3225
3226
sage: S = SubstitutionCryptosystem(AlphabeticStrings())
3227
sage: K = S.random_key()
3228
sage: M = S.encoding("Don't substitute me!")
3229
sage: S.deciphering(K, S.enciphering(K, M)) == M
3230
True
3231
"""
3232
i = self(self.inverse_key(K))
3233
return i(C)
3234
3235
def enciphering(self, K, M):
3236
"""
3237
Encrypt the plaintext ``M`` using the key ``K``.
3238
3239
INPUT:
3240
3241
- ``K`` - a key belonging to the key space of this substitution cipher
3242
3243
- ``M`` - a string (possibly empty) over the string monoid of this
3244
cryptosystem.
3245
3246
OUTPUT:
3247
3248
- The ciphertext corresponding to the plaintext ``M``.
3249
3250
EXAMPLES::
3251
3252
sage: S = SubstitutionCryptosystem(AlphabeticStrings())
3253
sage: K = S.random_key()
3254
sage: M = S.encoding("Don't substitute me.")
3255
sage: S.deciphering(K, S.enciphering(K, M)) == M
3256
True
3257
"""
3258
e = self(K)
3259
return e(M)
3260
3261
class TranspositionCryptosystem(SymmetricKeyCryptosystem):
3262
"""
3263
Create a transposition cryptosystem of block length ``n``.
3264
3265
INPUT:
3266
3267
- ``S`` - a string monoid over some alphabet
3268
3269
- ``n`` - integer `> 0`; a block length of a block permutation
3270
3271
OUTPUT:
3272
3273
- A transposition cryptosystem of block length ``n`` over the
3274
alphabet ``S``.
3275
3276
EXAMPLES::
3277
3278
sage: S = AlphabeticStrings()
3279
sage: E = TranspositionCryptosystem(S,14)
3280
sage: E
3281
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
3282
sage: K = [ 14-i for i in range(14) ]
3283
sage: K
3284
[14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
3285
sage: e = E(K)
3286
sage: e(S("THECATINTHEHAT"))
3287
TAHEHTNITACEHT
3288
3289
TESTS::
3290
3291
sage: S = AlphabeticStrings()
3292
sage: E = TranspositionCryptosystem(S,14)
3293
sage: E == loads(dumps(E))
3294
True
3295
"""
3296
3297
def __init__(self, S, n):
3298
"""
3299
See ``TranspositionCryptosystem`` for full documentation.
3300
3301
EXAMPLES::
3302
3303
sage: S = AlphabeticStrings()
3304
sage: E = TranspositionCryptosystem(S,14)
3305
sage: E
3306
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
3307
"""
3308
if not isinstance(S, StringMonoid_class):
3309
raise TypeError("S (= %s) must be a string monoid." % S)
3310
key_space = SymmetricGroup(n)
3311
SymmetricKeyCryptosystem.__init__(self, S, S, key_space, block_length=n)
3312
3313
def __call__(self, K):
3314
"""
3315
Create a transposition cipher.
3316
3317
INPUT:
3318
3319
- ``K`` - a key which specifies a block permutation
3320
3321
EXAMPLES::
3322
3323
sage: M = AlphabeticStrings()
3324
sage: E = TranspositionCryptosystem(M,14)
3325
sage: E
3326
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
3327
sage: K = [ 14-i for i in range(14) ]
3328
sage: K
3329
[14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
3330
sage: e = E(K)
3331
sage: m = M("THECATINTHEHAT")
3332
sage: e(m)
3333
TAHEHTNITACEHT
3334
"""
3335
G = self.key_space()
3336
if isinstance(K, list):
3337
try:
3338
K = G(K)
3339
except:
3340
raise TypeError("K (= %s) must specify a permutation." % K)
3341
if not isinstance(K, PermutationGroupElement) and K.parent() == G:
3342
raise TypeError("K (= %s) must be a permutation or list specifying a permutation." % K)
3343
return TranspositionCipher(self, K)
3344
3345
def _repr_(self):
3346
"""
3347
Return a string representation of self.
3348
3349
EXAMPLES::
3350
3351
sage: A = AlphabeticStrings()
3352
sage: T = TranspositionCryptosystem(A, 14)
3353
sage: T
3354
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
3355
sage: T._repr_()
3356
'Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14'
3357
"""
3358
return "Transposition cryptosystem on %s of block length %s" % (
3359
self.cipher_domain(), self.block_length())
3360
3361
def random_key(self):
3362
"""
3363
Generate a random key within the key space of this transposition
3364
cryptosystem. Let `n > 0` be the block length of this cryptosystem.
3365
Then there are `n!` possible keys.
3366
3367
OUTPUT:
3368
3369
- A random key within the key space of this cryptosystem.
3370
3371
EXAMPLES::
3372
3373
sage: S = AlphabeticStrings()
3374
sage: E = TranspositionCryptosystem(S, 14)
3375
sage: K = E.random_key()
3376
sage: Ki = E.inverse_key(K)
3377
sage: e = E(K)
3378
sage: d = E(Ki)
3379
sage: M = "THECATINTHEHAT"
3380
sage: C = e(S(M))
3381
sage: d(S(C)) == S(M)
3382
True
3383
"""
3384
n = self.block_length()
3385
return SymmetricGroup(n).random_element()
3386
3387
def inverse_key(self, K, check=True):
3388
"""
3389
The inverse key corresponding to the key ``K``.
3390
3391
INPUT:
3392
3393
- ``K`` - a key belonging to the key space of this transposition
3394
cipher
3395
3396
- ``check`` - bool (default: ``True``); check that ``K`` belongs to
3397
the key space of this cryptosystem.
3398
3399
OUTPUT:
3400
3401
- The inverse key corresponding to ``K``.
3402
3403
EXAMPLES::
3404
3405
sage: S = AlphabeticStrings()
3406
sage: E = TranspositionCryptosystem(S, 14)
3407
sage: K = E.random_key()
3408
sage: Ki = E.inverse_key(K)
3409
sage: e = E(K)
3410
sage: d = E(Ki)
3411
sage: M = "THECATINTHEHAT"
3412
sage: C = e(S(M))
3413
sage: d(S(C)) == S(M)
3414
True
3415
"""
3416
if check:
3417
if not K in self.key_space():
3418
raise TypeError("Argument K (= %s) is not in the key space." % K)
3419
return K**-1
3420
3421
def encoding(self, M):
3422
"""
3423
The encoding of the string ``M`` over the string monoid of this
3424
transposition cipher. For example, if the string monoid of this
3425
cryptosystem is :class:`AlphabeticStringMonoid`, then the encoding
3426
of ``M`` would be its upper-case equivalent stripped of all
3427
non-alphabetic characters.
3428
3429
INPUT:
3430
3431
- ``M`` - a string, possibly empty
3432
3433
OUTPUT:
3434
3435
- The encoding of ``M`` over the string monoid of this cryptosystem.
3436
3437
EXAMPLES::
3438
3439
sage: M = "Transposition cipher is not about matrix transpose."
3440
sage: A = AlphabeticStrings()
3441
sage: T = TranspositionCryptosystem(A, 11)
3442
sage: T.encoding(M) == A.encoding(M)
3443
True
3444
"""
3445
S = self.cipher_domain()
3446
if isinstance(S, AlphabeticStringMonoid):
3447
return S(strip_encoding(M))
3448
try:
3449
return S.encoding(M)
3450
except:
3451
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
3452
3453
def deciphering(self, K, C):
3454
"""
3455
Decrypt the ciphertext ``C`` using the key ``K``.
3456
3457
INPUT:
3458
3459
- ``K`` - a key belonging to the key space of this transposition
3460
cipher
3461
3462
- ``C`` - a string (possibly empty) over the string monoid of this
3463
cryptosystem.
3464
3465
OUTPUT:
3466
3467
- The plaintext corresponding to the ciphertext ``C``.
3468
3469
EXAMPLES::
3470
3471
sage: T = TranspositionCryptosystem(AlphabeticStrings(), 14)
3472
sage: K = T.random_key()
3473
sage: M = T.encoding("The cat in the hat.")
3474
sage: T.deciphering(K, T.enciphering(K, M)) == M
3475
True
3476
"""
3477
i = self(self.inverse_key(K))
3478
return i(C)
3479
3480
def enciphering(self, K, M):
3481
"""
3482
Encrypt the plaintext ``M`` using the key ``K``.
3483
3484
INPUT:
3485
3486
- ``K`` - a key belonging to the key space of this transposition
3487
cipher
3488
3489
- ``M`` - a string (possibly empty) over the string monoid of this
3490
cryptosystem
3491
3492
OUTPUT:
3493
3494
- The ciphertext corresponding to the plaintext ``M``.
3495
3496
EXAMPLES::
3497
3498
sage: T = TranspositionCryptosystem(AlphabeticStrings(), 14)
3499
sage: K = T.random_key()
3500
sage: M = T.encoding("The cat in the hat.")
3501
sage: T.deciphering(K, T.enciphering(K, M)) == M
3502
True
3503
"""
3504
e = self(K)
3505
return e(M)
3506
3507
class VigenereCryptosystem(SymmetricKeyCryptosystem):
3508
"""
3509
Create a Vigenere cryptosystem of block length ``n``.
3510
3511
INPUT:
3512
3513
- ``S``-- a string monoid over some alphabet
3514
3515
- ``n`` - integer `> 0`; block length of an encryption/decryption key
3516
3517
OUTPUT:
3518
3519
- A Vigenere cryptosystem of block length ``n`` over the alphabet
3520
``S``.
3521
3522
EXAMPLES::
3523
3524
sage: S = AlphabeticStrings()
3525
sage: E = VigenereCryptosystem(S,14)
3526
sage: E
3527
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
3528
sage: K = S('ABCDEFGHIJKLMN')
3529
sage: K
3530
ABCDEFGHIJKLMN
3531
sage: e = E(K)
3532
sage: e
3533
Cipher on Free alphabetic string monoid on A-Z
3534
sage: e(S("THECATINTHEHAT"))
3535
TIGFEYOUBQOSMG
3536
3537
TESTS::
3538
3539
sage: S = AlphabeticStrings()
3540
sage: E = VigenereCryptosystem(S,14)
3541
sage: E == loads(dumps(E))
3542
True
3543
"""
3544
3545
def __init__(self, S, n):
3546
"""
3547
See ``VigenereCryptosystem`` for full documentation.
3548
3549
EXAMPLES::
3550
3551
sage: S = AlphabeticStrings()
3552
sage: E = VigenereCryptosystem(S,14)
3553
sage: E
3554
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
3555
"""
3556
if not isinstance(S, StringMonoid_class):
3557
raise TypeError("S (= %s) must be a string monoid." % S)
3558
SymmetricKeyCryptosystem.__init__(self, S, S, S, block_length=1, period=n)
3559
3560
def __call__(self, K):
3561
"""
3562
Create a Vigenere cipher.
3563
3564
INPUT: A key which specifies a block permutation.
3565
3566
EXAMPLES::
3567
3568
sage: S = AlphabeticStrings()
3569
sage: E = VigenereCryptosystem(S,14)
3570
sage: E
3571
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
3572
sage: K = S('ABCDEFGHIJKLMN')
3573
sage: K
3574
ABCDEFGHIJKLMN
3575
sage: e = E(K)
3576
sage: e
3577
Cipher on Free alphabetic string monoid on A-Z
3578
sage: e(S("THECATINTHEHAT"))
3579
TIGFEYOUBQOSMG
3580
"""
3581
S = self.key_space()
3582
m = self.period()
3583
if isinstance(K, list):
3584
try:
3585
K = S(K)
3586
except:
3587
raise TypeError("K (= %s) must specify a string of length %s." % (K, m))
3588
if not len(K) == m:
3589
raise TypeError("K (= %s) must specify a string of length %s." % (K, m))
3590
return VigenereCipher(self, K)
3591
3592
def _repr_(self):
3593
"""
3594
Return a string representation of self.
3595
3596
EXAMPLES::
3597
3598
sage: A = AlphabeticStrings()
3599
sage: V = VigenereCryptosystem(A, 14)
3600
sage: V
3601
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
3602
sage: V._repr_()
3603
'Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14'
3604
"""
3605
return "Vigenere cryptosystem on %s of period %s" % (
3606
self.cipher_domain(), self.period())
3607
3608
def random_key(self):
3609
"""
3610
Generate a random key within the key space of this Vigenere
3611
cryptosystem. Let `n > 0` be the length of the cryptosystem alphabet
3612
and let `m > 0` be the block length of this cryptosystem. Then there
3613
are `n^m` possible keys.
3614
3615
OUTPUT:
3616
3617
- A random key within the key space of this cryptosystem.
3618
3619
EXAMPLES::
3620
3621
sage: A = AlphabeticStrings()
3622
sage: V = VigenereCryptosystem(A, 14)
3623
sage: M = "THECATINTHEHAT"
3624
sage: K = V.random_key()
3625
sage: Ki = V.inverse_key(K)
3626
sage: e = V(K)
3627
sage: d = V(Ki)
3628
sage: d(e(A(M))) == A(M)
3629
True
3630
"""
3631
S = self.key_space()
3632
n = S.ngens()
3633
m = self.period()
3634
return S([ randint(0, n-1) for i in range(m) ])
3635
3636
def inverse_key(self, K):
3637
"""
3638
The inverse key corresponding to the key ``K``.
3639
3640
INPUT:
3641
3642
- ``K`` - a key within the key space of this Vigenere cryptosystem
3643
3644
OUTPUT:
3645
3646
- The inverse key corresponding to ``K``.
3647
3648
EXAMPLES::
3649
3650
sage: S = AlphabeticStrings()
3651
sage: E = VigenereCryptosystem(S,14)
3652
sage: K = E.random_key()
3653
sage: L = E.inverse_key(K)
3654
sage: M = S("THECATINTHEHAT")
3655
sage: e = E(K)
3656
sage: c = E(L)
3657
sage: c(e(M))
3658
THECATINTHEHAT
3659
"""
3660
S = self.key_space()
3661
n = S.ngens()
3662
return S([ (-i)%(n) for i in K._element_list ])
3663
3664
def encoding(self, M):
3665
"""
3666
The encoding of the string ``M`` over the string monoid of this
3667
Vigenere cipher. For example, if the string monoid of this
3668
cryptosystem is :class:`AlphabeticStringMonoid`, then the encoding
3669
of ``M`` would be its upper-case equivalent stripped of all
3670
non-alphabetic characters.
3671
3672
INPUT:
3673
3674
- ``M`` - a string, possibly empty
3675
3676
OUTPUT:
3677
3678
- The encoding of ``M`` over the string monoid of this cryptosystem.
3679
3680
EXAMPLES::
3681
3682
sage: A = AlphabeticStrings()
3683
sage: V = VigenereCryptosystem(A, 24)
3684
sage: M = "Jack and Jill went up the hill."
3685
sage: V.encoding(M) == A.encoding(M)
3686
True
3687
"""
3688
S = self.cipher_domain()
3689
if isinstance(S, AlphabeticStringMonoid):
3690
return S(strip_encoding(M))
3691
try:
3692
return S.encoding(M)
3693
except:
3694
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
3695
3696
def deciphering(self, K, C):
3697
"""
3698
Decrypt the ciphertext ``C`` using the key ``K``.
3699
3700
INPUT:
3701
3702
- ``K`` - a key belonging to the key space of this Vigenere cipher
3703
3704
- ``C`` - a string (possibly empty) over the string monoid of this
3705
cryptosystem
3706
3707
OUTPUT:
3708
3709
- The plaintext corresponding to the ciphertext ``C``.
3710
3711
EXAMPLES::
3712
3713
sage: V = VigenereCryptosystem(AlphabeticStrings(), 24)
3714
sage: K = V.random_key()
3715
sage: M = V.encoding("Jack and Jill went up the hill.")
3716
sage: V.deciphering(K, V.enciphering(K, M)) == M
3717
True
3718
"""
3719
i = self(self.inverse_key(K))
3720
return i(C)
3721
3722
def enciphering(self, K, M):
3723
"""
3724
Encrypt the plaintext ``M`` using the key ``K``.
3725
3726
INPUT:
3727
3728
- ``K`` - a key belonging to the key space of this Vigenere cipher
3729
3730
- ``M`` - a string (possibly empty) over the string monoid of this
3731
cryptosystem
3732
3733
OUTPUT:
3734
3735
- The ciphertext corresponding to the plaintext ``M``.
3736
3737
EXAMPLES::
3738
3739
sage: V = VigenereCryptosystem(AlphabeticStrings(), 24)
3740
sage: K = V.random_key()
3741
sage: M = V.encoding("Jack and Jill went up the hill.")
3742
sage: V.deciphering(K, V.enciphering(K, M)) == M
3743
True
3744
"""
3745
e = self(K)
3746
return e(M)
3747
3748