Classical Cryptosystems#

A convenient user interface to various classical ciphers. These include:

These classical cryptosystems support alphabets such as:

  • the capital letters of the English alphabet; see AlphabeticStrings()

  • the hexadecimal number system; see HexadecimalStrings()

  • the binary number system; see BinaryStrings()

  • the octal number system; see OctalStrings()

  • the radix-64 number system; see Radix64Strings()

AUTHORS:

  • David Kohel (2007): initial version with the Hill, substitution, transposition, and Vigenere cryptosystems.

  • Minh Van Nguyen (2009-08): shift cipher, affine cipher

class sage.crypto.classical.AffineCryptosystem(A)[source]#

Bases: SymmetricKeyCryptosystem

Create an affine cryptosystem.

Let \(A = \{ a_0, a_1, a_2, \dots, a_{n-1} \}\) be a non-empty alphabet consisting of \(n\) unique elements. Define a mapping \(f : A \longrightarrow \ZZ / n\ZZ\) from the alphabet \(A\) to the set \(\ZZ / n\ZZ\) of integers modulo \(n\), given by \(f(a_i) = i\). Thus we can identify each element of the alphabet \(A\) with a unique integer \(0 \leq i < n\). A key of the affine cipher is an ordered pair of integers \((a, b) \in \ZZ / n\ZZ \times \ZZ / n\ZZ\) such that \(\gcd(a, n) = 1\). Therefore the key space is \(\ZZ / n\ZZ \times \ZZ / n\ZZ\). Since we assume that \(A\) does not have repeated elements, the mapping \(f : A \longrightarrow \ZZ/ n\ZZ\) is bijective. Encryption and decryption functions are both affine functions. Let \((a,b)\) be a secret key, i.e. an element of the key space, and let \(p\) be a plaintext character and consequently \(p \in \ZZ / n\ZZ\). Then the ciphertext character \(c\) corresponding to \(p\) is given by

\[c \equiv ap + b \pmod{n}\]

Similarly, given a ciphertext character \(c \in \ZZ / n\ZZ\) and a secret key \((a,b)\), we can recover the corresponding plaintext character as follows:

\[p \equiv a^{-1} (c - b) \pmod{n}\]

where \(a^{-1}\) is the inverse of \(a\) modulo \(n\). Use the bijection \(f : A \longrightarrow \ZZ / n\ZZ\) to convert \(c\) and \(p\) back to elements of the alphabet \(A\). Currently, only the following alphabet is supported for the affine cipher:

  • capital letters of the English alphabet as implemented in AlphabeticStrings()

EXAMPLES:

Encryption and decryption over the capital letters of the English alphabet:

sage: A = AffineCryptosystem(AlphabeticStrings()); A
Affine cryptosystem on Free alphabetic string monoid on A-Z
sage: P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
sage: P
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
sage: a, b = (9, 13)
sage: C = A.enciphering(a, b, P); C
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
sage: A.deciphering(a, b, C)
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
sage: A.deciphering(a, b, C) == P
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings()); A
Affine cryptosystem on Free alphabetic string monoid on A-Z
>>> P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
>>> P
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
>>> a, b = (Integer(9), Integer(13))
>>> C = A.enciphering(a, b, P); C
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
>>> A.deciphering(a, b, C)
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
>>> A.deciphering(a, b, C) == P
True

We can also use functional notation to work through the previous example:

sage: A = AffineCryptosystem(AlphabeticStrings()); A
Affine cryptosystem on Free alphabetic string monoid on A-Z
sage: P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
sage: P
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
sage: a, b = (9, 13)
sage: E = A(a, b); E
Affine cipher on Free alphabetic string monoid on A-Z
sage: C = E(P); C
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
sage: aInv, bInv = A.inverse_key(a, b)
sage: D = A(aInv, bInv); D
Affine cipher on Free alphabetic string monoid on A-Z
sage: D(C)
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
sage: D(C) == P
True
sage: D(C) == P == D(E(P))
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings()); A
Affine cryptosystem on Free alphabetic string monoid on A-Z
>>> P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
>>> P
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
>>> a, b = (Integer(9), Integer(13))
>>> E = A(a, b); E
Affine cipher on Free alphabetic string monoid on A-Z
>>> C = E(P); C
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
>>> aInv, bInv = A.inverse_key(a, b)
>>> D = A(aInv, bInv); D
Affine cipher on Free alphabetic string monoid on A-Z
>>> D(C)
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
>>> D(C) == P
True
>>> D(C) == P == D(E(P))
True

Encrypting the ciphertext with the inverse key also produces the plaintext:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: P = A.encoding("Encrypt with inverse key.")
sage: a, b = (11, 8)
sage: C = A.enciphering(a, b, P)
sage: P; C
ENCRYPTWITHINVERSEKEY
AVENMRJQSJHSVFANYAOAM
sage: aInv, bInv = A.inverse_key(a, b)
sage: A.enciphering(aInv, bInv, C)
ENCRYPTWITHINVERSEKEY
sage: A.enciphering(aInv, bInv, C) == P
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> P = A.encoding("Encrypt with inverse key.")
>>> a, b = (Integer(11), Integer(8))
>>> C = A.enciphering(a, b, P)
>>> P; C
ENCRYPTWITHINVERSEKEY
AVENMRJQSJHSVFANYAOAM
>>> aInv, bInv = A.inverse_key(a, b)
>>> A.enciphering(aInv, bInv, C)
ENCRYPTWITHINVERSEKEY
>>> A.enciphering(aInv, bInv, C) == P
True

For a secret key \((a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ\), if \(a = 1\) then any affine cryptosystem with key \((1, b)\) for any \(b \in \ZZ/n\ZZ\) is a shift cryptosystem. Here is how we can create a Caesar cipher using an affine cipher:

sage: caesar = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (1, 3)
sage: P = caesar.encoding("abcdef"); P
ABCDEF
sage: C = caesar.enciphering(a, b, P); C
DEFGHI
sage: caesar.deciphering(a, b, C) == P
True
>>> from sage.all import *
>>> caesar = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(1), Integer(3))
>>> P = caesar.encoding("abcdef"); P
ABCDEF
>>> C = caesar.enciphering(a, b, P); C
DEFGHI
>>> caesar.deciphering(a, b, C) == P
True

Any affine cipher with keys of the form \((a,0) \in \ZZ/n\ZZ \times \ZZ/n\ZZ\) is called a decimation cipher on the Roman alphabet, or decimation cipher for short:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: P = A.encoding("A decimation cipher is a specialized affine cipher.")
sage: a, b = (17, 0)
sage: C = A.enciphering(a, b, P)
sage: P; C
ADECIMATIONCIPHERISASPECIALIZEDAFFINECIPHER
AZQIGWALGENIGVPQDGUAUVQIGAFGJQZAHHGNQIGVPQD
sage: A.deciphering(a, b, C) == P
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> P = A.encoding("A decimation cipher is a specialized affine cipher.")
>>> a, b = (Integer(17), Integer(0))
>>> C = A.enciphering(a, b, P)
>>> P; C
ADECIMATIONCIPHERISASPECIALIZEDAFFINECIPHER
AZQIGWALGENIGVPQDGUAUVQIGAFGJQZAHHGNQIGVPQD
>>> A.deciphering(a, b, C) == P
True

Generate a random key for encryption and decryption:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: P = A.encoding("An affine cipher with a random key.")
sage: a, b = A.random_key()
sage: C = A.enciphering(a, b, P)
sage: A.deciphering(a, b, C) == P
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> P = A.encoding("An affine cipher with a random key.")
>>> a, b = A.random_key()
>>> C = A.enciphering(a, b, P)
>>> A.deciphering(a, b, C) == P
True

REFERENCES:

brute_force(C, ranking='none')[source]#

Attempt a brute force cryptanalysis of the ciphertext C.

INPUT:

  • C – A ciphertext over one of the supported alphabets of this affine cryptosystem. See the class AffineCryptosystem for documentation on the supported alphabets.

  • ranking – (default "none") the method to use for ranking all possible keys. If ranking="none", then do not use any ranking function. The following ranking functions are supported:

OUTPUT:

  • All the possible plaintext sequences corresponding to the ciphertext C. This method effectively uses all the possible keys in this affine cryptosystem to decrypt C. The method is also referred to as exhaustive key search. The output is a dictionary of key, candidate decipherment pairs.

EXAMPLES:

Cryptanalyze using all possible keys with the option ranking="none":

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (3, 7)
sage: P = A.encoding("Linear"); P
LINEAR
sage: C = A.enciphering(a, b, P)
sage: L = A.brute_force(C)
sage: sorted(L.items())[:26]  # display 26 candidate decipherments

[((1, 0), OFUTHG),
((1, 1), NETSGF),
((1, 2), MDSRFE),
((1, 3), LCRQED),
((1, 4), KBQPDC),
((1, 5), JAPOCB),
((1, 6), IZONBA),
((1, 7), HYNMAZ),
((1, 8), GXMLZY),
((1, 9), FWLKYX),
((1, 10), EVKJXW),
((1, 11), DUJIWV),
((1, 12), CTIHVU),
((1, 13), BSHGUT),
((1, 14), ARGFTS),
((1, 15), ZQFESR),
((1, 16), YPEDRQ),
((1, 17), XODCQP),
((1, 18), WNCBPO),
((1, 19), VMBAON),
((1, 20), ULAZNM),
((1, 21), TKZYML),
((1, 22), SJYXLK),
((1, 23), RIXWKJ),
((1, 24), QHWVJI),
((1, 25), PGVUIH)]
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(3), Integer(7))
>>> P = A.encoding("Linear"); P
LINEAR
>>> C = A.enciphering(a, b, P)
>>> L = A.brute_force(C)
>>> sorted(L.items())[:Integer(26)]  # display 26 candidate decipherments
<BLANKLINE>
[((1, 0), OFUTHG),
((1, 1), NETSGF),
((1, 2), MDSRFE),
((1, 3), LCRQED),
((1, 4), KBQPDC),
((1, 5), JAPOCB),
((1, 6), IZONBA),
((1, 7), HYNMAZ),
((1, 8), GXMLZY),
((1, 9), FWLKYX),
((1, 10), EVKJXW),
((1, 11), DUJIWV),
((1, 12), CTIHVU),
((1, 13), BSHGUT),
((1, 14), ARGFTS),
((1, 15), ZQFESR),
((1, 16), YPEDRQ),
((1, 17), XODCQP),
((1, 18), WNCBPO),
((1, 19), VMBAON),
((1, 20), ULAZNM),
((1, 21), TKZYML),
((1, 22), SJYXLK),
((1, 23), RIXWKJ),
((1, 24), QHWVJI),
((1, 25), PGVUIH)]

Use the chi-square ranking function, i.e. ranking="chisquare":

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (3, 7)
sage: P = A.encoding("Linear functions for encrypting and decrypting."); P
LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING
sage: C = A.enciphering(a, b, P)
sage: Rank = A.brute_force(C, ranking="chisquare")
sage: Rank[:10]  # display only the top 10 candidate keys

[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
((11, 15), HSRYELDAROVSWRQDWLYROLUBVSRIERTTYOLUBVSRI),
((25, 1), NWHIUVFMHOPWEHSFEVIHOVABPWHCUHLLIOVABPWHC),
((25, 7), TCNOABLSNUVCKNYLKBONUBGHVCNIANRROUBGHVCNI),
((15, 4), SHIBVOWZILEHDIJWDOBILOFYEHIRVIGGBLOFYEHIR),
((15, 23), PEFYSLTWFIBEAFGTALYFILCVBEFOSFDDYILCVBEFO),
((7, 10), IDUFHSYXUTEDNULYNSFUTSVGEDURHUMMFTSVGEDUR),
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH)]
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(3), Integer(7))
>>> P = A.encoding("Linear functions for encrypting and decrypting."); P
LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING
>>> C = A.enciphering(a, b, P)
>>> Rank = A.brute_force(C, ranking="chisquare")
>>> Rank[:Integer(10)]  # display only the top 10 candidate keys
<BLANKLINE>
[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
((11, 15), HSRYELDAROVSWRQDWLYROLUBVSRIERTTYOLUBVSRI),
((25, 1), NWHIUVFMHOPWEHSFEVIHOVABPWHCUHLLIOVABPWHC),
((25, 7), TCNOABLSNUVCKNYLKBONUBGHVCNIANRROUBGHVCNI),
((15, 4), SHIBVOWZILEHDIJWDOBILOFYEHIRVIGGBLOFYEHIR),
((15, 23), PEFYSLTWFIBEAFGTALYFILCVBEFOSFDDYILCVBEFO),
((7, 10), IDUFHSYXUTEDNULYNSFUTSVGEDURHUMMFTSVGEDUR),
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH)]

Use the squared differences ranking function, i.e. ranking="squared_differences":

sage: Rank = A.brute_force(C, ranking="squared_differences")
sage: Rank[:10]  # display only the top 10 candidate keys

[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
((23, 6), GJENRAMXEPYJDEZMDANEPATCYJELREOONPATCYJEL),
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH),
((19, 9), DIRGETNORSHIYRANYTGRSTQFHIRUERZZGSTQFHIRU),
((23, 18), KNIRVEQBITCNHIDQHERITEXGCNIPVISSRTEXGCNIP),
((17, 16), GHORBEIDOJMHFOVIFEROJETWMHOZBOAARJETWMHOZ),
((21, 14), AHEZRMOFEVQHTEBOTMZEVMNIQHEDREKKZVMNIQHED),
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
((7, 18), SNEPRCIHEDONXEVIXCPEDCFQONEBREWWPDCFQONEB)]
>>> from sage.all import *
>>> Rank = A.brute_force(C, ranking="squared_differences")
>>> Rank[:Integer(10)]  # display only the top 10 candidate keys
<BLANKLINE>
[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
((23, 6), GJENRAMXEPYJDEZMDANEPATCYJELREOONPATCYJEL),
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH),
((19, 9), DIRGETNORSHIYRANYTGRSTQFHIRUERZZGSTQFHIRU),
((23, 18), KNIRVEQBITCNHIDQHERITEXGCNIPVISSRTEXGCNIP),
((17, 16), GHORBEIDOJMHFOVIFEROJETWMHOZBOAARJETWMHOZ),
((21, 14), AHEZRMOFEVQHTEBOTMZEVMNIQHEDREKKZVMNIQHED),
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
((7, 18), SNEPRCIHEDONXEVIXCPEDCFQONEBREWWPDCFQONEB)]
deciphering(a, b, C)[source]#

Decrypt the ciphertext C with the key (a, b) using affine cipher decryption.

INPUT:

  • a, b – a secret key belonging to the key space of this affine cipher. This key must be an element of \(\ZZ/n\ZZ \times \ZZ/n\ZZ\) such that \(\gcd(a,n) = 1\) with \(n\) being the size of the ciphertext and plaintext spaces.

  • C – a string of ciphertext; possibly an empty string. Characters in this string must be encoded using one of the supported alphabets. See the method encoding() for more information.

OUTPUT:

  • The plaintext corresponding to the ciphertext C.

EXAMPLES:

Decryption over the capital letters of the English alphabet:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (5, 2)
sage: P = A.encoding("Affine functions are linear functions.")
sage: C = A.enciphering(a, b, P); C
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
sage: P == A.deciphering(a, b, C)
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(5), Integer(2))
>>> P = A.encoding("Affine functions are linear functions.")
>>> C = A.enciphering(a, b, P); C
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
>>> P == A.deciphering(a, b, C)
True

The previous example can also be worked through using functional notation:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (5, 2)
sage: P = A.encoding("Affine functions are linear functions.")
sage: E = A(a, b); E
Affine cipher on Free alphabetic string monoid on A-Z
sage: C = E(P); C
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
sage: aInv, bInv = A.inverse_key(a, b)
sage: D = A(aInv, bInv); D
Affine cipher on Free alphabetic string monoid on A-Z
sage: D(C) == P
True
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(5), Integer(2))
>>> P = A.encoding("Affine functions are linear functions.")
>>> E = A(a, b); E
Affine cipher on Free alphabetic string monoid on A-Z
>>> C = E(P); C
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
>>> aInv, bInv = A.inverse_key(a, b)
>>> D = A(aInv, bInv); D
Affine cipher on Free alphabetic string monoid on A-Z
>>> D(C) == P
True

If the ciphertext is an empty string, then the plaintext is also an empty string regardless of the value of the secret key:

sage: a, b = A.random_key()
sage: A.deciphering(a, b, A.encoding(""))

sage: A.deciphering(a, b, A.encoding(" "))
>>> from sage.all import *
>>> a, b = A.random_key()
>>> A.deciphering(a, b, A.encoding(""))
<BLANKLINE>
>>> A.deciphering(a, b, A.encoding(" "))
<BLANKLINE>
enciphering(a, b, P)[source]#

Encrypt the plaintext P with the key (a, b) using affine cipher encryption.

INPUT:

  • a, b – a secret key belonging to the key space of this affine cipher. This key must be an element of \(\ZZ/n\ZZ \times \ZZ/n\ZZ\) such that \(\gcd(a,n) = 1\) with \(n\) being the size of the ciphertext and plaintext spaces.

  • P – a string of plaintext; possibly an empty string. Characters in this string must be encoded using one of the supported alphabets. See the method encoding() for more information.

OUTPUT:

  • The ciphertext corresponding to the plaintext P.

EXAMPLES:

Encryption over the capital letters of the English alphabet:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (3, 6)
sage: P = A.encoding("Affine ciphers work with linear functions.")
sage: A.enciphering(a, b, P)
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(3), Integer(6))
>>> P = A.encoding("Affine ciphers work with linear functions.")
>>> A.enciphering(a, b, P)
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI

Now work through the previous example using functional notation:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (3, 6)
sage: P = A.encoding("Affine ciphers work with linear functions.")
sage: E = A(a, b); E
Affine cipher on Free alphabetic string monoid on A-Z
sage: E(P)
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(3), Integer(6))
>>> P = A.encoding("Affine ciphers work with linear functions.")
>>> E = A(a, b); E
Affine cipher on Free alphabetic string monoid on A-Z
>>> E(P)
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI

If the plaintext is an empty string, then the ciphertext is also an empty string regardless of the value of the secret key:

sage: a, b = A.random_key()
sage: A.enciphering(a, b, A.encoding(""))

sage: A.enciphering(a, b, A.encoding(" "))
>>> from sage.all import *
>>> a, b = A.random_key()
>>> A.enciphering(a, b, A.encoding(""))
<BLANKLINE>
>>> A.enciphering(a, b, A.encoding(" "))
<BLANKLINE>
encoding(S)[source]#

The encoding of the string S over the string monoid of this affine cipher. For example, if the string monoid of this cryptosystem is AlphabeticStringMonoid, then the encoding of S would be its upper-case equivalent stripped of all non-alphabetic characters. Only the following alphabet is supported for the affine cipher:

  • capital letters of the English alphabet as implemented in AlphabeticStrings()

INPUT:

  • S – a string, possibly empty.

OUTPUT:

  • The encoding of S over the string monoid of this cryptosystem. If S is an empty string, return an empty string.

EXAMPLES:

Encoding over the upper-case letters of the English alphabet:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: A.encoding("Affine cipher over capital letters of the English alphabet.")
AFFINECIPHEROVERCAPITALLETTERSOFTHEENGLISHALPHABET
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> A.encoding("Affine cipher over capital letters of the English alphabet.")
AFFINECIPHEROVERCAPITALLETTERSOFTHEENGLISHALPHABET

The argument S can be an empty string, in which case an empty string is returned:

sage: AffineCryptosystem(AlphabeticStrings()).encoding("")
>>> from sage.all import *
>>> AffineCryptosystem(AlphabeticStrings()).encoding("")
<BLANKLINE>
inverse_key(a, b)[source]#

The inverse key corresponding to the secret key \((a,b)\). If \(p\) is a plaintext character so that \(p \in \ZZ/n\ZZ\) and \(n\) is the alphabet size, then the ciphertext \(c\) corresponding to \(p\) is

\[c \equiv ap + b \pmod{n}\]

As \((a,b)\) is a key, then the multiplicative inverse \(a^{-1}\) exists and the original plaintext can be recovered as follows

\[p \equiv a^{-1} (c - b) \pmod{n} \equiv a^{-1}c + a^{-1}(-b) \pmod{n}\]

Therefore the ordered pair \((a^{-1}, -ba^{-1})\) is the inverse key corresponding to \((a,b)\).

INPUT:

  • a, b – a secret key for this affine cipher. The ordered pair \((a,b)\) must be an element of \(\ZZ/n\ZZ \times \ZZ/n\ZZ\) such that \(\gcd(a,n) = 1\).

OUTPUT:

  • The inverse key \((a^{-1}, -ba^{-1})\) corresponding to \((a,b)\).

EXAMPLES:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (1, 2)
sage: A.inverse_key(a, b)
(1, 24)
sage: A.inverse_key(3, 2)
(9, 8)
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(1), Integer(2))
>>> A.inverse_key(a, b)
(1, 24)
>>> A.inverse_key(Integer(3), Integer(2))
(9, 8)

Suppose that the plaintext and ciphertext spaces are the capital letters of the English alphabet so that \(n = 26\). If \(\varphi(n)\) is the Euler phi function of \(n\), then there are \(\varphi(n)\) integers \(0 \leq a < n\) that are relatively prime to \(n\). For the capital letters of the English alphabet, there are 12 such integers relatively prime to \(n\):

sage: euler_phi(A.alphabet_size())                                          # needs sage.libs.pari
12
>>> from sage.all import *
>>> euler_phi(A.alphabet_size())                                          # needs sage.libs.pari
12

And here is a list of those integers:

sage: n = A.alphabet_size()
sage: L = [i for i in range(n) if gcd(i, n) == 1]; L
[1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
>>> from sage.all import *
>>> n = A.alphabet_size()
>>> L = [i for i in range(n) if gcd(i, n) == Integer(1)]; L
[1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]

Then a secret key \((a,b)\) of this shift cryptosystem is such that \(a\) is an element of the list L in the last example. Any inverse key \((A, B)\) corresponding to \((a,b)\) is such that \(A\) is also in the list L above:

sage: a, b = (3, 9)
sage: a in L
True
sage: aInv, bInv = A.inverse_key(a, b)
sage: aInv, bInv
(9, 23)
sage: aInv in L
True
>>> from sage.all import *
>>> a, b = (Integer(3), Integer(9))
>>> a in L
True
>>> aInv, bInv = A.inverse_key(a, b)
>>> aInv, bInv
(9, 23)
>>> aInv in L
True
random_key()[source]#

Generate a random key within the key space of this affine cipher. The generated secret key is an ordered pair \((a, b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ\) with \(n\) being the size of the cipher domain and \(\gcd(a, n) = 1\). Let \(\varphi(n)\) denote the Euler phi function of \(n\). Then the affine cipher has \(n \cdot \varphi(n)\) possible keys (see page 10 of [Sti2006]).

OUTPUT:

  • A random key within the key space of this affine cryptosystem. The output key is an ordered pair \((a,b)\).

EXAMPLES:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: A.random_key()  # random
(17, 25)
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> A.random_key()  # random
(17, 25)

If \((a,b)\) is a secret key and \(n\) is the size of the plaintext and ciphertext alphabets, then \(\gcd(a, n) = 1\):

sage: a, b = A.random_key()
sage: n = A.alphabet_size()
sage: gcd(a, n)
1
>>> from sage.all import *
>>> a, b = A.random_key()
>>> n = A.alphabet_size()
>>> gcd(a, n)
1
rank_by_chi_square(C, pdict)[source]#

Use the chi-square statistic to rank all possible keys. Currently, this method only applies to the capital letters of the English alphabet.

ALGORITHM:

Consider a non-empty alphabet \(A\) consisting of \(n\) elements, and let \(C\) be a ciphertext encoded using elements of \(A\). The plaintext \(P\) corresponding to \(C\) is also encoded using elements of \(A\). Let \(M\) be a candidate decipherment of \(C\), i.e. \(M\) is the result of attempting to decrypt \(C\) using a key \((a,b)\) which is not necessarily the same key used to encrypt \(P\). Suppose \(F_A(e)\) is the characteristic frequency probability of \(e \in A\) and let \(F_M(e)\) be the message frequency probability with respect to \(M\). The characteristic frequency probability distribution of an alphabet is the expected frequency probability distribution for that alphabet. The message frequency probability distribution of \(M\) provides a distribution of the ratio of character occurrences over message length. One can interpret the characteristic frequency probability \(F_A(e)\) as the expected probability, while the message frequency probability \(F_M(e)\) is the observed probability. If \(M\) is of length \(L\), then the observed frequency of \(e \in A\) is

\[O_M(e) = F_M(e) \cdot L\]

and the expected frequency of \(e \in A\) is

\[E_A(e) = F_A(e) \cdot L\]

The chi-square rank \(R_{\chi^2}(M)\) of \(M\) corresponding to a key \((a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ\) is given by

\[R_{\chi^2}(M) = \sum_{e \in A} \frac {\big( O_M(e) - E_A(e) \big)^2} {E_A(e)}\]

Cryptanalysis by exhaustive key search produces a candidate decipherment \(M_{a,b}\) for each possible key \((a,b)\). For a set \(D = \big\{M_{a_1,b_1}, M_{a_2,b_2}, \dots, M_{a_k,b_k} \big\}\) of all candidate decipherments corresponding to a ciphertext \(C\), the smaller is the rank \(R_{\chi^2}(M_{a_i,b_i})\) the more likely that \((a_i,b_i)\) is the secret key. This key ranking method is based on the Pearson chi-square test [PearsonTest].

INPUT:

  • C – The ciphertext, a non-empty string. The ciphertext must be encoded using the upper-case letters of the English alphabet.

  • pdict – A dictionary of key, possible plaintext pairs. This should be the output of brute_force() with ranking="none".

OUTPUT:

  • A list ranking the most likely keys first. Each element of the list is a tuple of key, possible plaintext pairs.

EXAMPLES:

Use the chi-square statistic to rank all possible keys and their corresponding decipherment:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (3, 7)
sage: P = A.encoding("Line.")
sage: C = A.enciphering(a, b, P)
sage: Plist = A.brute_force(C)
sage: Rank = A.rank_by_chi_square(C, Plist)
sage: Rank[:10]  # display only the top 10 candidate keys

[((1, 1), NETS),
((3, 7), LINE),
((17, 20), STAD),
((5, 2), SLOT),
((5, 5), HADI),
((9, 25), TSLI),
((17, 15), DELO),
((15, 6), ETUN),
((21, 8), ELID),
((7, 17), HCTE)]
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(3), Integer(7))
>>> P = A.encoding("Line.")
>>> C = A.enciphering(a, b, P)
>>> Plist = A.brute_force(C)
>>> Rank = A.rank_by_chi_square(C, Plist)
>>> Rank[:Integer(10)]  # display only the top 10 candidate keys
<BLANKLINE>
[((1, 1), NETS),
((3, 7), LINE),
((17, 20), STAD),
((5, 2), SLOT),
((5, 5), HADI),
((9, 25), TSLI),
((17, 15), DELO),
((15, 6), ETUN),
((21, 8), ELID),
((7, 17), HCTE)]

As more ciphertext is available, the reliability of the chi-square ranking function increases:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (11, 24)
sage: P = A.encoding("Longer message is more information for cryptanalysis.")
sage: C = A.enciphering(a, b, P)
sage: Plist = A.brute_force(C)
sage: Rank = A.rank_by_chi_square(C, Plist)
sage: Rank[:10]  # display only the top 10 candidate keys

[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
((17, 9), INURFSBFLLHRFDLBNSFDUYNSBHEDNUYNSTSVGEHUHIVLDL),
((9, 18), RMFIUHYUOOSIUWOYMHUWFBMHYSVWMFBMHGHETVSFSREOWO),
((15, 12), VSTACPUCOOGACYOUSPCYTBSPUGNYSTBSPEPIRNGTGVIOYO),
((3, 22), PAFOYLKYGGSOYEGKALYEFTALKSBEAFTALILCVBSFSPCGEG),
((25, 3), OHSRNADNPPFRNVPDHANVSCHADFEVHSCHAJABWEFSFOBPVP),
((7, 25), GHYNVIPVRRLNVFRPHIVFYEHIPLAFHYEHIDITQALYLGTRFR),
((5, 2), NEHCIVKISSUCIWSKEVIWHFEVKUPWEHFEVOVABPUHUNASWS),
((15, 25), IFGNPCHPBBTNPLBHFCPLGOFCHTALFGOFCRCVEATGTIVBLB),
((9, 6), BWPSERIEYYCSEGYIWREGPLWRICFGWPLWRQRODFCPCBOYGY)]
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(11), Integer(24))
>>> P = A.encoding("Longer message is more information for cryptanalysis.")
>>> C = A.enciphering(a, b, P)
>>> Plist = A.brute_force(C)
>>> Rank = A.rank_by_chi_square(C, Plist)
>>> Rank[:Integer(10)]  # display only the top 10 candidate keys
<BLANKLINE>
[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
((17, 9), INURFSBFLLHRFDLBNSFDUYNSBHEDNUYNSTSVGEHUHIVLDL),
((9, 18), RMFIUHYUOOSIUWOYMHUWFBMHYSVWMFBMHGHETVSFSREOWO),
((15, 12), VSTACPUCOOGACYOUSPCYTBSPUGNYSTBSPEPIRNGTGVIOYO),
((3, 22), PAFOYLKYGGSOYEGKALYEFTALKSBEAFTALILCVBSFSPCGEG),
((25, 3), OHSRNADNPPFRNVPDHANVSCHADFEVHSCHAJABWEFSFOBPVP),
((7, 25), GHYNVIPVRRLNVFRPHIVFYEHIPLAFHYEHIDITQALYLGTRFR),
((5, 2), NEHCIVKISSUCIWSKEVIWHFEVKUPWEHFEVOVABPUHUNASWS),
((15, 25), IFGNPCHPBBTNPLBHFCPLGOFCHTALFGOFCRCVEATGTIVBLB),
((9, 6), BWPSERIEYYCSEGYIWREGPLWRICFGWPLWRQRODFCPCBOYGY)]
rank_by_squared_differences(C, pdict)[source]#

Use the squared-differences measure to rank all possible keys. Currently, this method only applies to the capital letters of the English alphabet.

ALGORITHM:

Consider a non-empty alphabet \(A\) consisting of \(n\) elements, and let \(C\) be a ciphertext encoded using elements of \(A\). The plaintext \(P\) corresponding to \(C\) is also encoded using elements of \(A\). Let \(M\) be a candidate decipherment of \(C\), i.e. \(M\) is the result of attempting to decrypt \(C\) using a key \((a,b)\) which is not necessarily the same key used to encrypt \(P\). Suppose \(F_A(e)\) is the characteristic frequency probability of \(e \in A\) and let \(F_M(e)\) be the message frequency probability with respect to \(M\). The characteristic frequency probability distribution of an alphabet is the expected frequency probability distribution for that alphabet. The message frequency probability distribution of \(M\) provides a distribution of the ratio of character occurrences over message length. One can interpret the characteristic frequency probability \(F_A(e)\) as the expected probability, while the message frequency probability \(F_M(e)\) is the observed probability. If \(M\) is of length \(L\), then the observed frequency of \(e \in A\) is

\[O_M(e) = F_M(e) \cdot L\]

and the expected frequency of \(e \in A\) is

\[E_A(e) = F_A(e) \cdot L\]

The squared-differences, or residual sum of squares, rank \(R_{RSS}(M)\) of \(M\) corresponding to a key \((a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ\) is given by

\[R_{RSS}(M) = \sum_{e \in A} \big( O_M(e) - E_A(e) \big)^2\]

Cryptanalysis by exhaustive key search produces a candidate decipherment \(M_{a,b}\) for each possible key \((a,b)\). For a set \(D = \big\{M_{a_1,b_1}, M_{a_2,b_2}, \dots, M_{a_k,b_k} \big\}\) of all candidate decipherments corresponding to a ciphertext \(C\), the smaller is the rank \(R_{RSS}(M_{a_i,b_i})\) the more likely that \((a_i,b_i)\) is the secret key. This key ranking method is based on the residual sum of squares measure [RSS].

INPUT:

  • C – The ciphertext, a non-empty string. The ciphertext must be encoded using the upper-case letters of the English alphabet.

  • pdict – A dictionary of key, possible plaintext pairs. This should be the output of brute_force() with ranking="none".

OUTPUT:

  • A list ranking the most likely keys first. Each element of the list is a tuple of key, possible plaintext pairs.

EXAMPLES:

Use the method of squared differences to rank all possible keys and their corresponding decipherment:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (3, 7)
sage: P = A.encoding("Line.")
sage: C = A.enciphering(a, b, P)
sage: Plist = A.brute_force(C)
sage: Rank = A.rank_by_squared_differences(C, Plist)
sage: Rank[:10]  # display only the top 10 candidate keys

[((1, 1), NETS),
((15, 6), ETUN),
((7, 17), HCTE),
((3, 7), LINE),
((17, 15), DELO),
((9, 4), EDWT),
((9, 9), POHE),
((21, 8), ELID),
((17, 20), STAD),
((7, 18), SNEP)]
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(3), Integer(7))
>>> P = A.encoding("Line.")
>>> C = A.enciphering(a, b, P)
>>> Plist = A.brute_force(C)
>>> Rank = A.rank_by_squared_differences(C, Plist)
>>> Rank[:Integer(10)]  # display only the top 10 candidate keys
<BLANKLINE>
[((1, 1), NETS),
((15, 6), ETUN),
((7, 17), HCTE),
((3, 7), LINE),
((17, 15), DELO),
((9, 4), EDWT),
((9, 9), POHE),
((21, 8), ELID),
((17, 20), STAD),
((7, 18), SNEP)]

As more ciphertext is available, the reliability of the squared-differences ranking function increases:

sage: A = AffineCryptosystem(AlphabeticStrings())
sage: a, b = (11, 24)
sage: P = A.encoding("Longer message is more information for cryptanalysis.")
sage: C = A.enciphering(a, b, P)
sage: Plist = A.brute_force(C)
sage: Rank = A.rank_by_squared_differences(C, Plist)
sage: Rank[:10]  # display only the top 10 candidate keys

[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
((9, 14), DYRUGTKGAAEUGIAKYTGIRNYTKEHIYRNYTSTQFHEREDQAIA),
((23, 24), DSNEUHIUMMAEUOMISHUONZSHIAROSNZSHKHQXRANADQMOM),
((23, 1), ETOFVIJVNNBFVPNJTIVPOATIJBSPTOATILIRYSBOBERNPN),
((21, 16), VEBGANYAQQOGAMQYENAMBDENYOTMEBDENUNIHTOBOVIQMQ),
((7, 12), TULAIVCIEEYAISECUVISLRUVCYNSULRUVQVGDNYLYTGESE),
((5, 20), ZQTOUHWUEEGOUIEWQHUITRQHWGBIQTRQHAHMNBGTGZMEIE),
((21, 8), JSPUOBMOEECUOAEMSBOAPRSBMCHASPRSBIBWVHCPCJWEAE),
((25, 7), SLWVREHRTTJVRZTHLERZWGLEHJIZLWGLENEFAIJWJSFTZT),
((25, 15), ATEDZMPZBBRDZHBPTMZHEOTMPRQHTEOTMVMNIQRERANBHB)]
>>> from sage.all import *
>>> A = AffineCryptosystem(AlphabeticStrings())
>>> a, b = (Integer(11), Integer(24))
>>> P = A.encoding("Longer message is more information for cryptanalysis.")
>>> C = A.enciphering(a, b, P)
>>> Plist = A.brute_force(C)
>>> Rank = A.rank_by_squared_differences(C, Plist)
>>> Rank[:Integer(10)]  # display only the top 10 candidate keys
<BLANKLINE>
[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
((9, 14), DYRUGTKGAAEUGIAKYTGIRNYTKEHIYRNYTSTQFHEREDQAIA),
((23, 24), DSNEUHIUMMAEUOMISHUONZSHIAROSNZSHKHQXRANADQMOM),
((23, 1), ETOFVIJVNNBFVPNJTIVPOATIJBSPTOATILIRYSBOBERNPN),
((21, 16), VEBGANYAQQOGAMQYENAMBDENYOTMEBDENUNIHTOBOVIQMQ),
((7, 12), TULAIVCIEEYAISECUVISLRUVCYNSULRUVQVGDNYLYTGESE),
((5, 20), ZQTOUHWUEEGOUIEWQHUITRQHWGBIQTRQHAHMNBGTGZMEIE),
((21, 8), JSPUOBMOEECUOAEMSBOAPRSBMCHASPRSBIBWVHCPCJWEAE),
((25, 7), SLWVREHRTTJVRZTHLERZWGLEHJIZLWGLENEFAIJWJSFTZT),
((25, 15), ATEDZMPZBBRDZHBPTMZHEOTMPRQHTEOTMVMNIQRERANBHB)]
class sage.crypto.classical.HillCryptosystem(S, m)[source]#

Bases: SymmetricKeyCryptosystem

Create a Hill cryptosystem defined by the \(m \times m\) matrix space over \(\ZZ / N \ZZ\), where \(N\) is the alphabet size of the string monoid S.

INPUT:

  • S – a string monoid over some alphabet

  • m – integer \(> 0\); the block length of matrices that specify block permutations

OUTPUT:

  • A Hill cryptosystem of block length m over the alphabet S.

EXAMPLES:

sage: # needs sage.modules
sage: S = AlphabeticStrings()
sage: E = HillCryptosystem(S, 3); E
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
sage: R = IntegerModRing(26)
sage: M = MatrixSpace(R,3,3)
sage: A = M([[1,0,1],[0,1,1],[2,2,3]]); A
[1 0 1]
[0 1 1]
[2 2 3]
sage: e = E(A); e
Hill cipher on Free alphabetic string monoid on A-Z of block length 3
sage: e(S("LAMAISONBLANCHE"))
JYVKSKQPELAYKPV
>>> from sage.all import *
>>> # needs sage.modules
>>> S = AlphabeticStrings()
>>> E = HillCryptosystem(S, Integer(3)); E
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
>>> R = IntegerModRing(Integer(26))
>>> M = MatrixSpace(R,Integer(3),Integer(3))
>>> A = M([[Integer(1),Integer(0),Integer(1)],[Integer(0),Integer(1),Integer(1)],[Integer(2),Integer(2),Integer(3)]]); A
[1 0 1]
[0 1 1]
[2 2 3]
>>> e = E(A); e
Hill cipher on Free alphabetic string monoid on A-Z of block length 3
>>> e(S("LAMAISONBLANCHE"))
JYVKSKQPELAYKPV
block_length()[source]#

The row or column dimension of a matrix specifying a block permutation. Encryption and decryption keys of a Hill cipher are square matrices, i.e. the row and column dimensions of an encryption or decryption key are the same. This row/column dimension is referred to as the block length.

OUTPUT:

  • The block length of an encryption/decryption key.

EXAMPLES:

sage: A = AlphabeticStrings()
sage: n = randint(1, A.ngens() - 1)
sage: H = HillCryptosystem(A, n)                                            # needs sage.modules
sage: H.block_length() == n                                                 # needs sage.modules
True
>>> from sage.all import *
>>> A = AlphabeticStrings()
>>> n = randint(Integer(1), A.ngens() - Integer(1))
>>> H = HillCryptosystem(A, n)                                            # needs sage.modules
>>> H.block_length() == n                                                 # needs sage.modules
True
deciphering(A, C)[source]#

Decrypt the ciphertext C using the key A.

INPUT:

  • A – a key within the key space of this Hill cipher

  • C – a string (possibly empty) over the string monoid of this Hill cipher

OUTPUT:

  • The plaintext corresponding to the ciphertext C.

EXAMPLES:

sage: # needs sage.modules
sage: H = HillCryptosystem(AlphabeticStrings(), 3)
sage: K = H.random_key()
sage: M = H.encoding("Good day, mate! How ya going?")
sage: H.deciphering(K, H.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> # needs sage.modules
>>> H = HillCryptosystem(AlphabeticStrings(), Integer(3))
>>> K = H.random_key()
>>> M = H.encoding("Good day, mate! How ya going?")
>>> H.deciphering(K, H.enciphering(K, M)) == M
True
enciphering(A, M)[source]#

Encrypt the plaintext M using the key A.

INPUT:

  • A – a key within the key space of this Hill cipher

  • M – a string (possibly empty) over the string monoid of this Hill cipher.

OUTPUT:

  • The ciphertext corresponding to the plaintext M.

EXAMPLES:

sage: # needs sage.modules
sage: H = HillCryptosystem(AlphabeticStrings(), 3)
sage: K = H.random_key()
sage: M = H.encoding("Good day, mate! How ya going?")
sage: H.deciphering(K, H.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> # needs sage.modules
>>> H = HillCryptosystem(AlphabeticStrings(), Integer(3))
>>> K = H.random_key()
>>> M = H.encoding("Good day, mate! How ya going?")
>>> H.deciphering(K, H.enciphering(K, M)) == M
True
encoding(M)[source]#

The encoding of the string M over the string monoid of this Hill cipher. For example, if the string monoid of this Hill cipher is AlphabeticStringMonoid, then the encoding of M would be its upper-case equivalent stripped of all non-alphabetic characters.

INPUT:

  • M – a string, possibly empty

OUTPUT:

  • The encoding of M over the string monoid of this Hill cipher.

EXAMPLES:

sage: M = "The matrix cipher by Lester S. Hill."
sage: A = AlphabeticStrings()
sage: H = HillCryptosystem(A, 7)                                            # needs sage.modules
sage: H.encoding(M) == A.encoding(M)                                        # needs sage.modules
True
>>> from sage.all import *
>>> M = "The matrix cipher by Lester S. Hill."
>>> A = AlphabeticStrings()
>>> H = HillCryptosystem(A, Integer(7))                                            # needs sage.modules
>>> H.encoding(M) == A.encoding(M)                                        # needs sage.modules
True
inverse_key(A)[source]#

The inverse key corresponding to the key A.

INPUT:

  • A – an invertible matrix of the key space of this Hill cipher

OUTPUT:

  • The inverse matrix of A.

EXAMPLES:

sage: # needs sage.modules
sage: S = AlphabeticStrings()
sage: E = HillCryptosystem(S, 3)
sage: A = E.random_key()
sage: B = E.inverse_key(A)
sage: M = S("LAMAISONBLANCHE")
sage: e = E(A)
sage: c = E(B)
sage: c(e(M))
LAMAISONBLANCHE
>>> from sage.all import *
>>> # needs sage.modules
>>> S = AlphabeticStrings()
>>> E = HillCryptosystem(S, Integer(3))
>>> A = E.random_key()
>>> B = E.inverse_key(A)
>>> M = S("LAMAISONBLANCHE")
>>> e = E(A)
>>> c = E(B)
>>> c(e(M))
LAMAISONBLANCHE
random_key()[source]#

A random key within the key space of this Hill cipher. That is, generate a random \(m \times m\) matrix to be used as a block permutation, where \(m\) is the block length of this Hill cipher. If \(n\) is the size of the cryptosystem alphabet, then there are \(n^{m^2}\) possible keys. However the number of valid keys, i.e. invertible \(m \times m\) square matrices, is smaller than \(n^{m^2}\).

OUTPUT:

  • A random key within the key space of this Hill cipher.

EXAMPLES:

sage: # needs sage.modules
sage: A = AlphabeticStrings()
sage: n = 3
sage: H = HillCryptosystem(A, n)
sage: K = H.random_key()
sage: Ki = H.inverse_key(K)
sage: M = "LAMAISONBLANCHE"
sage: e = H(K)
sage: d = H(Ki)
sage: d(e(A(M))) == A(M)
True
>>> from sage.all import *
>>> # needs sage.modules
>>> A = AlphabeticStrings()
>>> n = Integer(3)
>>> H = HillCryptosystem(A, n)
>>> K = H.random_key()
>>> Ki = H.inverse_key(K)
>>> M = "LAMAISONBLANCHE"
>>> e = H(K)
>>> d = H(Ki)
>>> d(e(A(M))) == A(M)
True
class sage.crypto.classical.ShiftCryptosystem(A)[source]#

Bases: SymmetricKeyCryptosystem

Create a shift cryptosystem.

Let \(A = \{ a_0, a_1, a_2, \dots, a_{n-1} \}\) be a non-empty alphabet consisting of \(n\) unique elements. Define a mapping \(f : A \longrightarrow \ZZ/ n\ZZ\) from the alphabet \(A\) to the set \(\ZZ / n\ZZ\) of integers modulo \(n\), given by \(f(a_i) = i\). Thus we can identify each element of the alphabet \(A\) with a unique integer \(0 \leq i < n\). A key of the shift cipher is an integer \(0 \leq k < n\). Therefore the key space is \(\ZZ / n\ZZ\). Since we assume that \(A\) does not have repeated elements, the mapping \(f : A \longrightarrow \ZZ/ n\ZZ\) is bijective. Encryption works by moving along the alphabet by \(k\) positions, with wrap around. Decryption reverses the process by moving backwards by \(k\) positions, with wrap around. More generally, let \(k\) be a secret key, i.e. an element of the key space, and let \(p\) be a plaintext character and consequently \(p \in \ZZ / n\ZZ\). Then the ciphertext character \(c\) corresponding to \(p\) is given by

\[c \equiv p + k \pmod{n}\]

Similarly, given a ciphertext character \(c \in \ZZ / n\ZZ\) and a secret key \(k\), we can recover the corresponding plaintext character as follows:

\[p \equiv c - k \pmod{n}\]

Use the bijection \(f : A \longrightarrow \ZZ/ n\ZZ\) to convert \(c\) and \(p\) back to elements of the alphabet \(A\). Currently, the following alphabets are supported for the shift cipher:

  • capital letters of the English alphabet as implemented in AlphabeticStrings()

  • the alphabet consisting of the hexadecimal number system as implemented in HexadecimalStrings()

  • the alphabet consisting of the binary number system as implemented in BinaryStrings()

EXAMPLES:

Some examples illustrating encryption and decryption over various alphabets. Here is an example over the upper-case letters of the English alphabet:

sage: S = ShiftCryptosystem(AlphabeticStrings()); S
Shift cryptosystem on Free alphabetic string monoid on A-Z
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
sage: P
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
sage: K = 7
sage: C = S.enciphering(K, P); C
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
sage: S.deciphering(K, C)
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings()); S
Shift cryptosystem on Free alphabetic string monoid on A-Z
>>> P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
>>> P
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
>>> K = Integer(7)
>>> C = S.enciphering(K, P); C
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
>>> S.deciphering(K, C)
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
>>> S.deciphering(K, C) == P
True

The previous example can also be done as follows:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
sage: K = 7
sage: E = S(K); E
Shift cipher on Free alphabetic string monoid on A-Z
sage: C = E(P); C
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
sage: D = S(S.inverse_key(K)); D
Shift cipher on Free alphabetic string monoid on A-Z
sage: D(C) == P
True
sage: D(C) == P == D(E(P))
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
>>> K = Integer(7)
>>> E = S(K); E
Shift cipher on Free alphabetic string monoid on A-Z
>>> C = E(P); C
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
>>> D = S(S.inverse_key(K)); D
Shift cipher on Free alphabetic string monoid on A-Z
>>> D(C) == P
True
>>> D(C) == P == D(E(P))
True

Over the hexadecimal number system:

sage: S = ShiftCryptosystem(HexadecimalStrings()); S
Shift cryptosystem on Free hexadecimal string monoid
sage: P = S.encoding("Encryption & decryption shifts along the alphabet."); P
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
sage: K = 5
sage: C = S.enciphering(K, P); C
9ab3b8c7cec5c9beb4b3757b75b9bab8c7cec5c9beb4b375c8bdbebbc9c875b6b1b4b3bc75c9bdba75b6b1c5bdb6b7bac973
sage: S.deciphering(K, C)
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(HexadecimalStrings()); S
Shift cryptosystem on Free hexadecimal string monoid
>>> P = S.encoding("Encryption & decryption shifts along the alphabet."); P
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
>>> K = Integer(5)
>>> C = S.enciphering(K, P); C
9ab3b8c7cec5c9beb4b3757b75b9bab8c7cec5c9beb4b375c8bdbebbc9c875b6b1b4b3bc75c9bdba75b6b1c5bdb6b7bac973
>>> S.deciphering(K, C)
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
>>> S.deciphering(K, C) == P
True

And over the binary number system:

sage: S = ShiftCryptosystem(BinaryStrings()); S
Shift cryptosystem on Free binary string monoid
sage: P = S.encoding("The binary alphabet is very insecure."); P
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
sage: K = 1
sage: C = S.enciphering(K, P); C
10101011100101111001101011011111100111011001011010010001100111101000110110000110110111111001111010010011100011111001011110011110100111011001101010001011110111111001011010001100110111111000100110011010100011011000011011011111100101101001000110001100100110101001110010001010100011011001101011010001
sage: S.deciphering(K, C)
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(BinaryStrings()); S
Shift cryptosystem on Free binary string monoid
>>> P = S.encoding("The binary alphabet is very insecure."); P
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
>>> K = Integer(1)
>>> C = S.enciphering(K, P); C
10101011100101111001101011011111100111011001011010010001100111101000110110000110110111111001111010010011100011111001011110011110100111011001101010001011110111111001011010001100110111111000100110011010100011011000011011011111100101101001000110001100100110101001110010001010100011011001101011010001
>>> S.deciphering(K, C)
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
>>> S.deciphering(K, C) == P
True

A shift cryptosystem with key \(k = 3\) is commonly referred to as the Caesar cipher. Create a Caesar cipher over the upper-case letters of the English alphabet:

sage: caesar = ShiftCryptosystem(AlphabeticStrings())
sage: K = 3
sage: P = caesar.encoding("abcdef"); P
ABCDEF
sage: C = caesar.enciphering(K, P); C
DEFGHI
sage: caesar.deciphering(K, C) == P
True
>>> from sage.all import *
>>> caesar = ShiftCryptosystem(AlphabeticStrings())
>>> K = Integer(3)
>>> P = caesar.encoding("abcdef"); P
ABCDEF
>>> C = caesar.enciphering(K, P); C
DEFGHI
>>> caesar.deciphering(K, C) == P
True

Generate a random key for encryption and decryption:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("Shift cipher with a random key.")
sage: K = S.random_key()
sage: C = S.enciphering(K, P)
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("Shift cipher with a random key.")
>>> K = S.random_key()
>>> C = S.enciphering(K, P)
>>> S.deciphering(K, C) == P
True

Decrypting with the key K is equivalent to encrypting with its corresponding inverse key:

sage: S.enciphering(S.inverse_key(K), C) == P
True
>>> from sage.all import *
>>> S.enciphering(S.inverse_key(K), C) == P
True
brute_force(C, ranking='none')[source]#

Attempt a brute force cryptanalysis of the ciphertext C.

INPUT:

  • C – A ciphertext over one of the supported alphabets of this shift cryptosystem. See the class ShiftCryptosystem for documentation on the supported alphabets.

  • ranking – (default "none") the method to use for ranking all possible keys. If ranking="none", then do not use any ranking function. The following ranking functions are supported:

OUTPUT:

  • All the possible plaintext sequences corresponding to the ciphertext C. This method effectively uses all the possible keys in this shift cryptosystem to decrypt C. The method is also referred to as exhaustive key search. The output is a dictionary of key, plaintext pairs.

EXAMPLES:

Cryptanalyze using all possible keys for various alphabets. Over the upper-case letters of the English alphabet:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
sage: K = 7
sage: C = S.enciphering(K, P)
sage: Dict = S.brute_force(C)
sage: for k in range(len(Dict)):
....:     if Dict[k] == P:
....:         print("key = " + str(k))
key = 7
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
>>> K = Integer(7)
>>> C = S.enciphering(K, P)
>>> Dict = S.brute_force(C)
>>> for k in range(len(Dict)):
...     if Dict[k] == P:
...         print("key = " + str(k))
key = 7

Over the hexadecimal number system:

sage: S = ShiftCryptosystem(HexadecimalStrings())
sage: P = S.encoding("Encryption & decryption shifts along the alphabet.")
sage: K = 5
sage: C = S.enciphering(K, P)
sage: Dict = S.brute_force(C)
sage: for k in range(len(Dict)):
....:     if Dict[k] == P:
....:         print("key = " + str(k))
key = 5
>>> from sage.all import *
>>> S = ShiftCryptosystem(HexadecimalStrings())
>>> P = S.encoding("Encryption & decryption shifts along the alphabet.")
>>> K = Integer(5)
>>> C = S.enciphering(K, P)
>>> Dict = S.brute_force(C)
>>> for k in range(len(Dict)):
...     if Dict[k] == P:
...         print("key = " + str(k))
key = 5

And over the binary number system:

sage: S = ShiftCryptosystem(BinaryStrings())
sage: P = S.encoding("The binary alphabet is very insecure.")
sage: K = 1
sage: C = S.enciphering(K, P)
sage: Dict = S.brute_force(C)
sage: for k in range(len(Dict)):
....:     if Dict[k] == P:
....:         print("key = " + str(k))
key = 1
>>> from sage.all import *
>>> S = ShiftCryptosystem(BinaryStrings())
>>> P = S.encoding("The binary alphabet is very insecure.")
>>> K = Integer(1)
>>> C = S.enciphering(K, P)
>>> Dict = S.brute_force(C)
>>> for k in range(len(Dict)):
...     if Dict[k] == P:
...         print("key = " + str(k))
key = 1

Don’t use any ranking functions, i.e. ranking="none":

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("Shifting using modular arithmetic.")
sage: K = 8
sage: C = S.enciphering(K, P)
sage: pdict = S.brute_force(C)
sage: sorted(pdict.items())

[(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
(8, SHIFTINGUSINGMODULARARITHMETIC),
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL)]
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("Shifting using modular arithmetic.")
>>> K = Integer(8)
>>> C = S.enciphering(K, P)
>>> pdict = S.brute_force(C)
>>> sorted(pdict.items())
<BLANKLINE>
[(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
(8, SHIFTINGUSINGMODULARARITHMETIC),
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL)]

Use the chi-square ranking function, i.e. ranking="chisquare":

sage: S.brute_force(C, ranking="chisquare")

[(8, SHIFTINGUSINGMODULARARITHMETIC),
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT)]
>>> from sage.all import *
>>> S.brute_force(C, ranking="chisquare")
<BLANKLINE>
[(8, SHIFTINGUSINGMODULARARITHMETIC),
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT)]

Use the squared differences ranking function, i.e. ranking="squared_differences":

sage: S.brute_force(C, ranking="squared_differences")

[(8, SHIFTINGUSINGMODULARARITHMETIC),
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF)]
>>> from sage.all import *
>>> S.brute_force(C, ranking="squared_differences")
<BLANKLINE>
[(8, SHIFTINGUSINGMODULARARITHMETIC),
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF)]
deciphering(K, C)[source]#

Decrypt the ciphertext C with the key K using shift cipher decryption.

INPUT:

  • K – a secret key; a key belonging to the key space of this shift cipher. This key is an integer \(k\) satisfying the inequality \(0 \leq k < n\), where \(n\) is the size of the cipher domain.

  • C – a string of ciphertext; possibly an empty string. Characters in this string must be encoded using one of the supported alphabets. See the method encoding() for more information.

OUTPUT:

  • The plaintext corresponding to the ciphertext C.

EXAMPLES:

Let’s perform decryption over the supported alphabets. Here is decryption over the capital letters of the English alphabet:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("Stop shifting me."); P
STOPSHIFTINGME
sage: K = 13
sage: C = S.enciphering(K, P); C
FGBCFUVSGVATZR
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("Stop shifting me."); P
STOPSHIFTINGME
>>> K = Integer(13)
>>> C = S.enciphering(K, P); C
FGBCFUVSGVATZR
>>> S.deciphering(K, C) == P
True

Decryption over the hexadecimal number system:

sage: S = ShiftCryptosystem(HexadecimalStrings())
sage: P = S.encoding("Shift me now."); P
5368696674206d65206e6f772e
sage: K = 7
sage: C = S.enciphering(K, P); C
cadfd0ddeb97d4dc97d5d6ee95
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(HexadecimalStrings())
>>> P = S.encoding("Shift me now."); P
5368696674206d65206e6f772e
>>> K = Integer(7)
>>> C = S.enciphering(K, P); C
cadfd0ddeb97d4dc97d5d6ee95
>>> S.deciphering(K, C) == P
True

Decryption over the binary number system:

sage: S = ShiftCryptosystem(BinaryStrings())
sage: P = S.encoding("OK, enough shifting."); P
0100111101001011001011000010000001100101011011100110111101110101011001110110100000100000011100110110100001101001011001100111010001101001011011100110011100101110
sage: K = 1
sage: C = S.enciphering(K, P); C
1011000010110100110100111101111110011010100100011001000010001010100110001001011111011111100011001001011110010110100110011000101110010110100100011001100011010001
sage: S.deciphering(K, C) == P
True
>>> from sage.all import *
>>> S = ShiftCryptosystem(BinaryStrings())
>>> P = S.encoding("OK, enough shifting."); P
0100111101001011001011000010000001100101011011100110111101110101011001110110100000100000011100110110100001101001011001100111010001101001011011100110011100101110
>>> K = Integer(1)
>>> C = S.enciphering(K, P); C
1011000010110100110100111101111110011010100100011001000010001010100110001001011111011111100011001001011110010110100110011000101110010110100100011001100011010001
>>> S.deciphering(K, C) == P
True
enciphering(K, P)[source]#

Encrypt the plaintext P with the key K using shift cipher encryption.

INPUT:

  • K – a key belonging to the key space of this shift cipher. This key is an integer \(k\) satisfying the inequality \(0 \leq k < n\), where \(n\) is the size of the cipher domain.

  • P – a string of plaintext; possibly an empty string. Characters in this string must be encoded using one of the supported alphabets. See the method encoding() for more information.

OUTPUT:

  • The ciphertext corresponding to the plaintext P.

EXAMPLES:

Let’s perform encryption over the supported alphabets. Here is encryption over the capital letters of the English alphabet:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("Shift your gear."); P
SHIFTYOURGEAR
sage: K = 3
sage: S.enciphering(K, P)
VKLIWBRXUJHDU
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("Shift your gear."); P
SHIFTYOURGEAR
>>> K = Integer(3)
>>> S.enciphering(K, P)
VKLIWBRXUJHDU

Encryption over the hexadecimal number system:

sage: S = ShiftCryptosystem(HexadecimalStrings())
sage: P = S.encoding("Capitalize with the shift key."); P
4361706974616c697a65207769746820746865207368696674206b65792e
sage: K = 5
sage: S.enciphering(K, P)
98b6c5bec9b6b1becfba75ccbec9bd75c9bdba75c8bdbebbc975b0bace73
>>> from sage.all import *
>>> S = ShiftCryptosystem(HexadecimalStrings())
>>> P = S.encoding("Capitalize with the shift key."); P
4361706974616c697a65207769746820746865207368696674206b65792e
>>> K = Integer(5)
>>> S.enciphering(K, P)
98b6c5bec9b6b1becfba75ccbec9bd75c9bdba75c8bdbebbc975b0bace73

Encryption over the binary number system:

sage: S = ShiftCryptosystem(BinaryStrings())
sage: P = S.encoding("Don't shift."); P
010001000110111101101110001001110111010000100000011100110110100001101001011001100111010000101110
sage: K = 1
sage: S.enciphering(K, P)
101110111001000010010001110110001000101111011111100011001001011110010110100110011000101111010001
>>> from sage.all import *
>>> S = ShiftCryptosystem(BinaryStrings())
>>> P = S.encoding("Don't shift."); P
010001000110111101101110001001110111010000100000011100110110100001101001011001100111010000101110
>>> K = Integer(1)
>>> S.enciphering(K, P)
101110111001000010010001110110001000101111011111100011001001011110010110100110011000101111010001
encoding(S)[source]#

The encoding of the string S over the string monoid of this shift cipher. For example, if the string monoid of this cryptosystem is AlphabeticStringMonoid, then the encoding of S would be its upper-case equivalent stripped of all non-alphabetic characters. The following alphabets are supported for the shift cipher:

  • capital letters of the English alphabet as implemented in AlphabeticStrings()

  • the alphabet consisting of the hexadecimal number system as implemented in HexadecimalStrings()

  • the alphabet consisting of the binary number system as implemented in BinaryStrings()

INPUT:

  • S – a string, possibly empty.

OUTPUT:

  • The encoding of S over the string monoid of this cryptosystem. If S is an empty string, return an empty string.

EXAMPLES:

Encoding over the upper-case letters of the English alphabet:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: S.encoding("Shift cipher on capital letters of the English alphabet.")
SHIFTCIPHERONCAPITALLETTERSOFTHEENGLISHALPHABET
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> S.encoding("Shift cipher on capital letters of the English alphabet.")
SHIFTCIPHERONCAPITALLETTERSOFTHEENGLISHALPHABET

Encoding over the binary system:

sage: S = ShiftCryptosystem(BinaryStrings())
sage: S.encoding("Binary")
010000100110100101101110011000010111001001111001
>>> from sage.all import *
>>> S = ShiftCryptosystem(BinaryStrings())
>>> S.encoding("Binary")
010000100110100101101110011000010111001001111001

Encoding over the hexadecimal system:

sage: S = ShiftCryptosystem(HexadecimalStrings())
sage: S.encoding("Over hexadecimal system.")
4f7665722068657861646563696d616c2073797374656d2e
>>> from sage.all import *
>>> S = ShiftCryptosystem(HexadecimalStrings())
>>> S.encoding("Over hexadecimal system.")
4f7665722068657861646563696d616c2073797374656d2e

The argument S can be an empty string, in which case an empty string is returned:

sage: ShiftCryptosystem(AlphabeticStrings()).encoding("")

sage: ShiftCryptosystem(HexadecimalStrings()).encoding("")

sage: ShiftCryptosystem(BinaryStrings()).encoding("")
>>> from sage.all import *
>>> ShiftCryptosystem(AlphabeticStrings()).encoding("")
<BLANKLINE>
>>> ShiftCryptosystem(HexadecimalStrings()).encoding("")
<BLANKLINE>
>>> ShiftCryptosystem(BinaryStrings()).encoding("")
<BLANKLINE>
inverse_key(K)[source]#

The inverse key corresponding to the key K. For the shift cipher, the inverse key corresponding to K is \(-K \bmod n\), where \(n > 0\) is the size of the cipher domain, i.e. the plaintext/ciphertext space. A key \(k\) of the shift cipher is an integer \(0 \leq k < n\). The key \(k = 0\) has no effect on either the plaintext or the ciphertext.

INPUT:

  • K – a key for this shift cipher. This must be an integer \(k\) such that \(0 \leq k < n\), where \(n\) is the size of the cipher domain.

OUTPUT:

  • The inverse key corresponding to K.

EXAMPLES:

Some random keys and their respective inverse keys:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: key = S.random_key(); key  # random
2
sage: S.inverse_key(key)         # random
24
sage: S = ShiftCryptosystem(HexadecimalStrings())
sage: key = S.random_key(); key  # random
12
sage: S.inverse_key(key)         # random
4
sage: S = ShiftCryptosystem(BinaryStrings())
sage: key = S.random_key(); key  # random
1
sage: S.inverse_key(key)         # random
1
sage: key = S.random_key(); key  # random
0
sage: S.inverse_key(key)         # random
0
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> key = S.random_key(); key  # random
2
>>> S.inverse_key(key)         # random
24
>>> S = ShiftCryptosystem(HexadecimalStrings())
>>> key = S.random_key(); key  # random
12
>>> S.inverse_key(key)         # random
4
>>> S = ShiftCryptosystem(BinaryStrings())
>>> key = S.random_key(); key  # random
1
>>> S.inverse_key(key)         # random
1
>>> key = S.random_key(); key  # random
0
>>> S.inverse_key(key)         # random
0

Regardless of the value of a key, the addition of the key and its inverse must be equal to the alphabet size. This relationship holds exactly when the value of the key is non-zero:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: K = S.random_key()
sage: while K == 0:
....:     K = S.random_key()
sage: invK = S.inverse_key(K)
sage: K + invK == S.alphabet_size()
True
sage: invK + K == S.alphabet_size()
True
sage: K = S.random_key()
sage: while K != 0:
....:     K = S.random_key()
sage: invK = S.inverse_key(K)
sage: K + invK != S.alphabet_size()
True
sage: K; invK
0
0
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> K = S.random_key()
>>> while K == Integer(0):
...     K = S.random_key()
>>> invK = S.inverse_key(K)
>>> K + invK == S.alphabet_size()
True
>>> invK + K == S.alphabet_size()
True
>>> K = S.random_key()
>>> while K != Integer(0):
...     K = S.random_key()
>>> invK = S.inverse_key(K)
>>> K + invK != S.alphabet_size()
True
>>> K; invK
0
0
random_key()[source]#

Generate a random key within the key space of this shift cipher. The generated key is an integer \(0 \leq k < n\) with \(n\) being the size of the cipher domain. Thus there are \(n\) possible keys in the key space, which is the set \(\ZZ / n\ZZ\). The key \(k = 0\) has no effect on either the plaintext or the ciphertext.

OUTPUT:

  • A random key within the key space of this shift cryptosystem.

EXAMPLES:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: S.random_key()  # random
18
sage: S = ShiftCryptosystem(BinaryStrings())
sage: S.random_key()  # random
0
sage: S = ShiftCryptosystem(HexadecimalStrings())
sage: S.random_key()  # random
5
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> S.random_key()  # random
18
>>> S = ShiftCryptosystem(BinaryStrings())
>>> S.random_key()  # random
0
>>> S = ShiftCryptosystem(HexadecimalStrings())
>>> S.random_key()  # random
5

Regardless of the value of a key, the addition of the key and its inverse must be equal to the alphabet size. This relationship holds exactly when the value of the key is non-zero:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: K = S.random_key()
sage: while K == 0:
....:     K = S.random_key()
sage: invK = S.inverse_key(K)
sage: K + invK == S.alphabet_size()
True
sage: invK + K == S.alphabet_size()
True
sage: K = S.random_key()
sage: while K != 0:
....:     K = S.random_key()
sage: invK = S.inverse_key(K)
sage: K + invK != S.alphabet_size()
True
sage: K; invK
0
0
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> K = S.random_key()
>>> while K == Integer(0):
...     K = S.random_key()
>>> invK = S.inverse_key(K)
>>> K + invK == S.alphabet_size()
True
>>> invK + K == S.alphabet_size()
True
>>> K = S.random_key()
>>> while K != Integer(0):
...     K = S.random_key()
>>> invK = S.inverse_key(K)
>>> K + invK != S.alphabet_size()
True
>>> K; invK
0
0
rank_by_chi_square(C, pdict)[source]#

Use the chi-square statistic to rank all possible keys. Currently, this method only applies to the capital letters of the English alphabet.

ALGORITHM:

Consider a non-empty alphabet \(A\) consisting of \(n\) elements, and let \(C\) be a ciphertext encoded using elements of \(A\). The plaintext \(P\) corresponding to \(C\) is also encoded using elements of \(A\). Let \(M\) be a candidate decipherment of \(C\), i.e. \(M\) is the result of attempting to decrypt \(C\) using a key \(k \in \ZZ/n\ZZ\) which is not necessarily the same key used to encrypt \(P\). Suppose \(F_A(e)\) is the characteristic frequency probability of \(e \in A\) and let \(F_M(e)\) be the message frequency probability with respect to \(M\). The characteristic frequency probability distribution of an alphabet is the expected frequency probability distribution for that alphabet. The message frequency probability distribution of \(M\) provides a distribution of the ratio of character occurrences over message length. One can interpret the characteristic frequency probability \(F_A(e)\) as the expected probability, while the message frequency probability \(F_M(e)\) is the observed probability. If \(M\) is of length \(L\), then the observed frequency of \(e \in A\) is

\[O_M(e) = F_M(e) \cdot L\]

and the expected frequency of \(e \in A\) is

\[E_A(e) = F_A(e) \cdot L\]

The chi-square rank \(R_{\chi^2}(M)\) of \(M\) corresponding to a key \(k \in \ZZ/n\ZZ\) is given by

\[R_{\chi^2}(M) = \sum_{e \in A} \frac {\big( O_M(e) - E_A(e) \big)^2} {E_A(e)}\]

Cryptanalysis by exhaustive key search produces a candidate decipherment \(M_{k}\) for each possible key \(k \in \ZZ/n\ZZ\). For a set \(D = \big\{M_{k_1}, M_{k_2}, \dots, M_{k_r} \big\}\) of all candidate decipherments corresponding to a ciphertext \(C\), the smaller is the rank \(R_{\chi^2}(M_{k_i})\) the more likely that \(k_i\) is the secret key. This key ranking method is based on the Pearson chi-square test [PearsonTest].

INPUT:

  • C – The ciphertext, a non-empty string. The ciphertext must be encoded using the upper-case letters of the English alphabet.

  • pdict – A dictionary of key, possible plaintext pairs. This should be the output of brute_force() with ranking="none".

OUTPUT:

  • A list ranking the most likely keys first. Each element of the list is a tuple of key, possible plaintext pairs.

EXAMPLES:

Use the chi-square statistic to rank all possible keys and their corresponding decipherment:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("Shi."); P
SHI
sage: K = 5
sage: C = S.enciphering(K, P)
sage: Pdict = S.brute_force(C)
sage: S.rank_by_chi_square(C, Pdict)

[(9, ODE),
(5, SHI),
(20, DST),
(19, ETU),
(21, CRS),
(10, NCD),
(25, YNO),
(6, RGH),
(12, LAB),
(8, PEF),
(1, WLM),
(11, MBC),
(18, FUV),
(17, GVW),
(2, VKL),
(4, TIJ),
(3, UJK),
(0, XMN),
(16, HWX),
(15, IXY),
(23, APQ),
(24, ZOP),
(22, BQR),
(7, QFG),
(13, KZA),
(14, JYZ)]
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("Shi."); P
SHI
>>> K = Integer(5)
>>> C = S.enciphering(K, P)
>>> Pdict = S.brute_force(C)
>>> S.rank_by_chi_square(C, Pdict)
<BLANKLINE>
[(9, ODE),
(5, SHI),
(20, DST),
(19, ETU),
(21, CRS),
(10, NCD),
(25, YNO),
(6, RGH),
(12, LAB),
(8, PEF),
(1, WLM),
(11, MBC),
(18, FUV),
(17, GVW),
(2, VKL),
(4, TIJ),
(3, UJK),
(0, XMN),
(16, HWX),
(15, IXY),
(23, APQ),
(24, ZOP),
(22, BQR),
(7, QFG),
(13, KZA),
(14, JYZ)]

As more ciphertext is available, the reliability of the chi-square ranking function increases:

sage: P = S.encoding("Shift cipher."); P
SHIFTCIPHER
sage: C = S.enciphering(K, P)
sage: Pdict = S.brute_force(C)
sage: S.rank_by_chi_square(C, Pdict)

[(5, SHIFTCIPHER),
(9, ODEBPYELDAN),
(18, FUVSGPVCURE),
(2, VKLIWFLSKHU),
(20, DSTQENTASPC),
(19, ETURFOUBTQD),
(21, CRSPDMSZROB),
(6, RGHESBHOGDQ),
(7, QFGDRAGNFCP),
(12, LABYMVBIAXK),
(17, GVWTHQWDVSF),
(24, ZOPMAJPWOLY),
(1, WLMJXGMTLIV),
(0, XMNKYHNUMJW),
(11, MBCZNWCJBYL),
(8, PEFCQZFMEBO),
(25, YNOLZIOVNKX),
(10, NCDAOXDKCZM),
(3, UJKHVEKRJGT),
(4, TIJGUDJQIFS),
(22, BQROCLRYQNA),
(16, HWXUIRXEWTG),
(15, IXYVJSYFXUH),
(14, JYZWKTZGYVI),
(13, KZAXLUAHZWJ),
(23, APQNBKQXPMZ)]
>>> from sage.all import *
>>> P = S.encoding("Shift cipher."); P
SHIFTCIPHER
>>> C = S.enciphering(K, P)
>>> Pdict = S.brute_force(C)
>>> S.rank_by_chi_square(C, Pdict)
<BLANKLINE>
[(5, SHIFTCIPHER),
(9, ODEBPYELDAN),
(18, FUVSGPVCURE),
(2, VKLIWFLSKHU),
(20, DSTQENTASPC),
(19, ETURFOUBTQD),
(21, CRSPDMSZROB),
(6, RGHESBHOGDQ),
(7, QFGDRAGNFCP),
(12, LABYMVBIAXK),
(17, GVWTHQWDVSF),
(24, ZOPMAJPWOLY),
(1, WLMJXGMTLIV),
(0, XMNKYHNUMJW),
(11, MBCZNWCJBYL),
(8, PEFCQZFMEBO),
(25, YNOLZIOVNKX),
(10, NCDAOXDKCZM),
(3, UJKHVEKRJGT),
(4, TIJGUDJQIFS),
(22, BQROCLRYQNA),
(16, HWXUIRXEWTG),
(15, IXYVJSYFXUH),
(14, JYZWKTZGYVI),
(13, KZAXLUAHZWJ),
(23, APQNBKQXPMZ)]
rank_by_squared_differences(C, pdict)[source]#

Use the squared-differences measure to rank all possible keys. Currently, this method only applies to the capital letters of the English alphabet.

ALGORITHM:

Consider a non-empty alphabet \(A\) consisting of \(n\) elements, and let \(C\) be a ciphertext encoded using elements of \(A\). The plaintext \(P\) corresponding to \(C\) is also encoded using elements of \(A\). Let \(M\) be a candidate decipherment of \(C\), i.e. \(M\) is the result of attempting to decrypt \(C\) using a key \(k \in \ZZ/n\ZZ\) which is not necessarily the same key used to encrypt \(P\). Suppose \(F_A(e)\) is the characteristic frequency probability of \(e \in A\) and let \(F_M(e)\) be the message frequency probability with respect to \(M\). The characteristic frequency probability distribution of an alphabet is the expected frequency probability distribution for that alphabet. The message frequency probability distribution of \(M\) provides a distribution of the ratio of character occurrences over message length. One can interpret the characteristic frequency probability \(F_A(e)\) as the expected probability, while the message frequency probability \(F_M(e)\) is the observed probability. If \(M\) is of length \(L\), then the observed frequency of \(e \in A\) is

\[O_M(e) = F_M(e) \cdot L\]

and the expected frequency of \(e \in A\) is

\[E_A(e) = F_A(e) \cdot L\]

The squared-differences, or residual sum of squares, rank \(R_{RSS}(M)\) of \(M\) corresponding to a key \(k \in \ZZ/n\ZZ\) is given by

\[R_{RSS}(M) = \sum_{e \in A} \big( O_M(e) - E_A(e) \big)^2\]

Cryptanalysis by exhaustive key search produces a candidate decipherment \(M_{k}\) for each possible key \(k \in \ZZ/n\ZZ\). For a set \(D = \big\{M_{k_1}, M_{k_2}, \dots, M_{k_r} \big\}\) of all candidate decipherments corresponding to a ciphertext \(C\), the smaller is the rank \(R_{RSS}(M_{k_i})\) the more likely that \(k_i\) is the secret key. This key ranking method is based on the residual sum of squares measure [RSS].

INPUT:

  • C – The ciphertext, a non-empty string. The ciphertext must be encoded using the upper-case letters of the English alphabet.

  • pdict – A dictionary of key, possible plaintext pairs. This should be the output of brute_force() with ranking="none".

OUTPUT:

  • A list ranking the most likely keys first. Each element of the list is a tuple of key, possible plaintext pairs.

EXAMPLES:

Use the method of squared differences to rank all possible keys and their corresponding decipherment:

sage: S = ShiftCryptosystem(AlphabeticStrings())
sage: P = S.encoding("Shi."); P
SHI
sage: K = 5
sage: C = S.enciphering(K, P)
sage: Pdict = S.brute_force(C)
sage: S.rank_by_squared_differences(C, Pdict)

[(19, ETU),
(9, ODE),
(20, DST),
(5, SHI),
(8, PEF),
(4, TIJ),
(25, YNO),
(21, CRS),
(6, RGH),
(10, NCD),
(12, LAB),
(23, APQ),
(24, ZOP),
(0, XMN),
(13, KZA),
(15, IXY),
(1, WLM),
(16, HWX),
(22, BQR),
(11, MBC),
(18, FUV),
(2, VKL),
(17, GVW),
(7, QFG),
(3, UJK),
(14, JYZ)]
>>> from sage.all import *
>>> S = ShiftCryptosystem(AlphabeticStrings())
>>> P = S.encoding("Shi."); P
SHI
>>> K = Integer(5)
>>> C = S.enciphering(K, P)
>>> Pdict = S.brute_force(C)
>>> S.rank_by_squared_differences(C, Pdict)
<BLANKLINE>
[(19, ETU),
(9, ODE),
(20, DST),
(5, SHI),
(8, PEF),
(4, TIJ),
(25, YNO),
(21, CRS),
(6, RGH),
(10, NCD),
(12, LAB),
(23, APQ),
(24, ZOP),
(0, XMN),
(13, KZA),
(15, IXY),
(1, WLM),
(16, HWX),
(22, BQR),
(11, MBC),
(18, FUV),
(2, VKL),
(17, GVW),
(7, QFG),
(3, UJK),
(14, JYZ)]

As more ciphertext is available, the reliability of the squared differences ranking function increases:

sage: P = S.encoding("Shift cipher."); P
SHIFTCIPHER
sage: C = S.enciphering(K, P)
sage: Pdict = S.brute_force(C)
sage: S.rank_by_squared_differences(C, Pdict)

[(20, DSTQENTASPC),
(5, SHIFTCIPHER),
(9, ODEBPYELDAN),
(19, ETURFOUBTQD),
(6, RGHESBHOGDQ),
(16, HWXUIRXEWTG),
(8, PEFCQZFMEBO),
(21, CRSPDMSZROB),
(22, BQROCLRYQNA),
(25, YNOLZIOVNKX),
(3, UJKHVEKRJGT),
(18, FUVSGPVCURE),
(4, TIJGUDJQIFS),
(10, NCDAOXDKCZM),
(7, QFGDRAGNFCP),
(24, ZOPMAJPWOLY),
(2, VKLIWFLSKHU),
(12, LABYMVBIAXK),
(17, GVWTHQWDVSF),
(1, WLMJXGMTLIV),
(13, KZAXLUAHZWJ),
(0, XMNKYHNUMJW),
(15, IXYVJSYFXUH),
(14, JYZWKTZGYVI),
(11, MBCZNWCJBYL),
(23, APQNBKQXPMZ)]
>>> from sage.all import *
>>> P = S.encoding("Shift cipher."); P
SHIFTCIPHER
>>> C = S.enciphering(K, P)
>>> Pdict = S.brute_force(C)
>>> S.rank_by_squared_differences(C, Pdict)
<BLANKLINE>
[(20, DSTQENTASPC),
(5, SHIFTCIPHER),
(9, ODEBPYELDAN),
(19, ETURFOUBTQD),
(6, RGHESBHOGDQ),
(16, HWXUIRXEWTG),
(8, PEFCQZFMEBO),
(21, CRSPDMSZROB),
(22, BQROCLRYQNA),
(25, YNOLZIOVNKX),
(3, UJKHVEKRJGT),
(18, FUVSGPVCURE),
(4, TIJGUDJQIFS),
(10, NCDAOXDKCZM),
(7, QFGDRAGNFCP),
(24, ZOPMAJPWOLY),
(2, VKLIWFLSKHU),
(12, LABYMVBIAXK),
(17, GVWTHQWDVSF),
(1, WLMJXGMTLIV),
(13, KZAXLUAHZWJ),
(0, XMNKYHNUMJW),
(15, IXYVJSYFXUH),
(14, JYZWKTZGYVI),
(11, MBCZNWCJBYL),
(23, APQNBKQXPMZ)]
class sage.crypto.classical.SubstitutionCryptosystem(S)[source]#

Bases: SymmetricKeyCryptosystem

Create a substitution cryptosystem.

INPUT:

  • S – a string monoid over some alphabet

OUTPUT:

  • A substitution cryptosystem over the alphabet S.

EXAMPLES:

sage: M = AlphabeticStrings()
sage: E = SubstitutionCryptosystem(M)
sage: E
Substitution cryptosystem on Free alphabetic string monoid on A-Z
sage: K = M([ 25-i for i in range(26) ])
sage: K
ZYXWVUTSRQPONMLKJIHGFEDCBA
sage: e = E(K)
sage: m = M("THECATINTHEHAT")
sage: e(m)
GSVXZGRMGSVSZG
>>> from sage.all import *
>>> M = AlphabeticStrings()
>>> E = SubstitutionCryptosystem(M)
>>> E
Substitution cryptosystem on Free alphabetic string monoid on A-Z
>>> K = M([ Integer(25)-i for i in range(Integer(26)) ])
>>> K
ZYXWVUTSRQPONMLKJIHGFEDCBA
>>> e = E(K)
>>> m = M("THECATINTHEHAT")
>>> e(m)
GSVXZGRMGSVSZG
deciphering(K, C)[source]#

Decrypt the ciphertext C using the key K.

INPUT:

  • K – a key belonging to the key space of this substitution cipher

  • C – a string (possibly empty) over the string monoid of this cryptosystem.

OUTPUT:

  • The plaintext corresponding to the ciphertext C.

EXAMPLES:

sage: S = SubstitutionCryptosystem(AlphabeticStrings())
sage: K = S.random_key()
sage: M = S.encoding("Don't substitute me!")
sage: S.deciphering(K, S.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> S = SubstitutionCryptosystem(AlphabeticStrings())
>>> K = S.random_key()
>>> M = S.encoding("Don't substitute me!")
>>> S.deciphering(K, S.enciphering(K, M)) == M
True
enciphering(K, M)[source]#

Encrypt the plaintext M using the key K.

INPUT:

  • K – a key belonging to the key space of this substitution cipher

  • M – a string (possibly empty) over the string monoid of this cryptosystem.

OUTPUT:

  • The ciphertext corresponding to the plaintext M.

EXAMPLES:

sage: S = SubstitutionCryptosystem(AlphabeticStrings())
sage: K = S.random_key()
sage: M = S.encoding("Don't substitute me.")
sage: S.deciphering(K, S.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> S = SubstitutionCryptosystem(AlphabeticStrings())
>>> K = S.random_key()
>>> M = S.encoding("Don't substitute me.")
>>> S.deciphering(K, S.enciphering(K, M)) == M
True
encoding(M)[source]#

The encoding of the string M over the string monoid of this substitution cipher. For example, if the string monoid of this cryptosystem is AlphabeticStringMonoid, then the encoding of M would be its upper-case equivalent stripped of all non-alphabetic characters.

INPUT:

  • M – a string, possibly empty

OUTPUT:

  • The encoding of M over the string monoid of this cryptosystem.

EXAMPLES:

sage: M = "Peter Pan(ning) for gold."
sage: A = AlphabeticStrings()
sage: S = SubstitutionCryptosystem(A)
sage: S.encoding(M) == A.encoding(M)
True
>>> from sage.all import *
>>> M = "Peter Pan(ning) for gold."
>>> A = AlphabeticStrings()
>>> S = SubstitutionCryptosystem(A)
>>> S.encoding(M) == A.encoding(M)
True
inverse_key(K)[source]#

The inverse key corresponding to the key K. The specified key is a permutation of the cryptosystem alphabet.

INPUT:

  • K – a key belonging to the key space of this cryptosystem

OUTPUT:

  • The inverse key of K.

EXAMPLES:

sage: S = AlphabeticStrings()
sage: E = SubstitutionCryptosystem(S)
sage: K = E.random_key()
sage: L = E.inverse_key(K)
sage: M = S("THECATINTHEHAT")
sage: e = E(K)
sage: c = E(L)
sage: c(e(M))
THECATINTHEHAT
>>> from sage.all import *
>>> S = AlphabeticStrings()
>>> E = SubstitutionCryptosystem(S)
>>> K = E.random_key()
>>> L = E.inverse_key(K)
>>> M = S("THECATINTHEHAT")
>>> e = E(K)
>>> c = E(L)
>>> c(e(M))
THECATINTHEHAT
random_key()[source]#

Generate a random key within the key space of this substitution cipher. The generated key is a permutation of the cryptosystem alphabet. Let \(n\) be the length of the alphabet. Then there are \(n!\) possible keys in the key space.

OUTPUT:

  • A random key within the key space of this cryptosystem.

EXAMPLES:

sage: A = AlphabeticStrings()
sage: S = SubstitutionCryptosystem(A)
sage: K = S.random_key()
sage: Ki = S.inverse_key(K)
sage: M = "THECATINTHEHAT"
sage: e = S(K)
sage: d = S(Ki)
sage: d(e(A(M))) == A(M)
True
>>> from sage.all import *
>>> A = AlphabeticStrings()
>>> S = SubstitutionCryptosystem(A)
>>> K = S.random_key()
>>> Ki = S.inverse_key(K)
>>> M = "THECATINTHEHAT"
>>> e = S(K)
>>> d = S(Ki)
>>> d(e(A(M))) == A(M)
True
class sage.crypto.classical.TranspositionCryptosystem(S, n)[source]#

Bases: SymmetricKeyCryptosystem

Create a transposition cryptosystem of block length n.

INPUT:

  • S – a string monoid over some alphabet

  • n – integer \(> 0\); a block length of a block permutation

OUTPUT:

  • A transposition cryptosystem of block length n over the alphabet S.

EXAMPLES:

sage: S = AlphabeticStrings()
sage: E = TranspositionCryptosystem(S,14); E                                    # needs sage.groups
Transposition cryptosystem on
 Free alphabetic string monoid on A-Z of block length 14
sage: K = [14 - i for i in range(14)]; K
[14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
sage: e = E(K)                                                                  # needs sage.groups
sage: e(S("THECATINTHEHAT"))                                                    # needs sage.groups
TAHEHTNITACEHT
>>> from sage.all import *
>>> S = AlphabeticStrings()
>>> E = TranspositionCryptosystem(S,Integer(14)); E                                    # needs sage.groups
Transposition cryptosystem on
 Free alphabetic string monoid on A-Z of block length 14
>>> K = [Integer(14) - i for i in range(Integer(14))]; K
[14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> e = E(K)                                                                  # needs sage.groups
>>> e(S("THECATINTHEHAT"))                                                    # needs sage.groups
TAHEHTNITACEHT
deciphering(K, C)[source]#

Decrypt the ciphertext C using the key K.

INPUT:

  • K – a key belonging to the key space of this transposition cipher

  • C – a string (possibly empty) over the string monoid of this cryptosystem.

OUTPUT:

  • The plaintext corresponding to the ciphertext C.

EXAMPLES:

sage: # needs sage.groups
sage: T = TranspositionCryptosystem(AlphabeticStrings(), 14)
sage: K = T.random_key()
sage: M = T.encoding("The cat in the hat.")
sage: T.deciphering(K, T.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> # needs sage.groups
>>> T = TranspositionCryptosystem(AlphabeticStrings(), Integer(14))
>>> K = T.random_key()
>>> M = T.encoding("The cat in the hat.")
>>> T.deciphering(K, T.enciphering(K, M)) == M
True
enciphering(K, M)[source]#

Encrypt the plaintext M using the key K.

INPUT:

  • K – a key belonging to the key space of this transposition cipher

  • M – a string (possibly empty) over the string monoid of this cryptosystem

OUTPUT:

  • The ciphertext corresponding to the plaintext M.

EXAMPLES:

sage: # needs sage.groups
sage: T = TranspositionCryptosystem(AlphabeticStrings(), 14)
sage: K = T.random_key()
sage: M = T.encoding("The cat in the hat.")
sage: T.deciphering(K, T.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> # needs sage.groups
>>> T = TranspositionCryptosystem(AlphabeticStrings(), Integer(14))
>>> K = T.random_key()
>>> M = T.encoding("The cat in the hat.")
>>> T.deciphering(K, T.enciphering(K, M)) == M
True
encoding(M)[source]#

The encoding of the string M over the string monoid of this transposition cipher. For example, if the string monoid of this cryptosystem is AlphabeticStringMonoid, then the encoding of M would be its upper-case equivalent stripped of all non-alphabetic characters.

INPUT:

  • M – a string, possibly empty

OUTPUT:

  • The encoding of M over the string monoid of this cryptosystem.

EXAMPLES:

sage: M = "Transposition cipher is not about matrix transpose."
sage: A = AlphabeticStrings()
sage: T = TranspositionCryptosystem(A, 11)                                  # needs sage.groups
sage: T.encoding(M) == A.encoding(M)                                        # needs sage.groups
True
>>> from sage.all import *
>>> M = "Transposition cipher is not about matrix transpose."
>>> A = AlphabeticStrings()
>>> T = TranspositionCryptosystem(A, Integer(11))                                  # needs sage.groups
>>> T.encoding(M) == A.encoding(M)                                        # needs sage.groups
True
inverse_key(K, check=True)[source]#

The inverse key corresponding to the key K.

INPUT:

  • K – a key belonging to the key space of this transposition cipher

  • check – bool (default: True); check that K belongs to the key space of this cryptosystem.

OUTPUT:

  • The inverse key corresponding to K.

EXAMPLES:

sage: # needs sage.groups
sage: S = AlphabeticStrings()
sage: E = TranspositionCryptosystem(S, 14)
sage: K = E.random_key()
sage: Ki = E.inverse_key(K)
sage: e = E(K)
sage: d = E(Ki)
sage: M = "THECATINTHEHAT"
sage: C = e(S(M))
sage: d(S(C)) == S(M)
True
>>> from sage.all import *
>>> # needs sage.groups
>>> S = AlphabeticStrings()
>>> E = TranspositionCryptosystem(S, Integer(14))
>>> K = E.random_key()
>>> Ki = E.inverse_key(K)
>>> e = E(K)
>>> d = E(Ki)
>>> M = "THECATINTHEHAT"
>>> C = e(S(M))
>>> d(S(C)) == S(M)
True
random_key()[source]#

Generate a random key within the key space of this transposition cryptosystem. Let \(n > 0\) be the block length of this cryptosystem. Then there are \(n!\) possible keys.

OUTPUT:

  • A random key within the key space of this cryptosystem.

EXAMPLES:

sage: # needs sage.groups
sage: S = AlphabeticStrings()
sage: E = TranspositionCryptosystem(S, 14)
sage: K = E.random_key()
sage: Ki = E.inverse_key(K)
sage: e = E(K)
sage: d = E(Ki)
sage: M = "THECATINTHEHAT"
sage: C = e(S(M))
sage: d(S(C)) == S(M)
True
>>> from sage.all import *
>>> # needs sage.groups
>>> S = AlphabeticStrings()
>>> E = TranspositionCryptosystem(S, Integer(14))
>>> K = E.random_key()
>>> Ki = E.inverse_key(K)
>>> e = E(K)
>>> d = E(Ki)
>>> M = "THECATINTHEHAT"
>>> C = e(S(M))
>>> d(S(C)) == S(M)
True
class sage.crypto.classical.VigenereCryptosystem(S, n)[source]#

Bases: SymmetricKeyCryptosystem

Create a Vigenere cryptosystem of block length n.

INPUT:

  • S – a string monoid over some alphabet

  • n – integer \(> 0\); block length of an encryption/decryption key

OUTPUT:

  • A Vigenere cryptosystem of block length n over the alphabet S.

EXAMPLES:

sage: S = AlphabeticStrings()
sage: E = VigenereCryptosystem(S,14)
sage: E
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
sage: K = S('ABCDEFGHIJKLMN')
sage: K
ABCDEFGHIJKLMN
sage: e = E(K)
sage: e
Cipher on Free alphabetic string monoid on A-Z
sage: e(S("THECATINTHEHAT"))
TIGFEYOUBQOSMG
>>> from sage.all import *
>>> S = AlphabeticStrings()
>>> E = VigenereCryptosystem(S,Integer(14))
>>> E
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
>>> K = S('ABCDEFGHIJKLMN')
>>> K
ABCDEFGHIJKLMN
>>> e = E(K)
>>> e
Cipher on Free alphabetic string monoid on A-Z
>>> e(S("THECATINTHEHAT"))
TIGFEYOUBQOSMG
deciphering(K, C)[source]#

Decrypt the ciphertext C using the key K.

INPUT:

  • K – a key belonging to the key space of this Vigenere cipher

  • C – a string (possibly empty) over the string monoid of this cryptosystem

OUTPUT:

  • The plaintext corresponding to the ciphertext C.

EXAMPLES:

sage: V = VigenereCryptosystem(AlphabeticStrings(), 24)
sage: K = V.random_key()
sage: M = V.encoding("Jack and Jill went up the hill.")
sage: V.deciphering(K, V.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> V = VigenereCryptosystem(AlphabeticStrings(), Integer(24))
>>> K = V.random_key()
>>> M = V.encoding("Jack and Jill went up the hill.")
>>> V.deciphering(K, V.enciphering(K, M)) == M
True
enciphering(K, M)[source]#

Encrypt the plaintext M using the key K.

INPUT:

  • K – a key belonging to the key space of this Vigenere cipher

  • M – a string (possibly empty) over the string monoid of this cryptosystem

OUTPUT:

  • The ciphertext corresponding to the plaintext M.

EXAMPLES:

sage: V = VigenereCryptosystem(AlphabeticStrings(), 24)
sage: K = V.random_key()
sage: M = V.encoding("Jack and Jill went up the hill.")
sage: V.deciphering(K, V.enciphering(K, M)) == M
True
>>> from sage.all import *
>>> V = VigenereCryptosystem(AlphabeticStrings(), Integer(24))
>>> K = V.random_key()
>>> M = V.encoding("Jack and Jill went up the hill.")
>>> V.deciphering(K, V.enciphering(K, M)) == M
True
encoding(M)[source]#

The encoding of the string M over the string monoid of this Vigenere cipher. For example, if the string monoid of this cryptosystem is AlphabeticStringMonoid, then the encoding of M would be its upper-case equivalent stripped of all non-alphabetic characters.

INPUT:

  • M – a string, possibly empty

OUTPUT:

  • The encoding of M over the string monoid of this cryptosystem.

EXAMPLES:

sage: A = AlphabeticStrings()
sage: V = VigenereCryptosystem(A, 24)
sage: M = "Jack and Jill went up the hill."
sage: V.encoding(M) == A.encoding(M)
True
>>> from sage.all import *
>>> A = AlphabeticStrings()
>>> V = VigenereCryptosystem(A, Integer(24))
>>> M = "Jack and Jill went up the hill."
>>> V.encoding(M) == A.encoding(M)
True
inverse_key(K)[source]#

The inverse key corresponding to the key K.

INPUT:

  • K – a key within the key space of this Vigenere cryptosystem

OUTPUT:

  • The inverse key corresponding to K.

EXAMPLES:

sage: S = AlphabeticStrings()
sage: E = VigenereCryptosystem(S,14)
sage: K = E.random_key()
sage: L = E.inverse_key(K)
sage: M = S("THECATINTHEHAT")
sage: e = E(K)
sage: c = E(L)
sage: c(e(M))
THECATINTHEHAT
>>> from sage.all import *
>>> S = AlphabeticStrings()
>>> E = VigenereCryptosystem(S,Integer(14))
>>> K = E.random_key()
>>> L = E.inverse_key(K)
>>> M = S("THECATINTHEHAT")
>>> e = E(K)
>>> c = E(L)
>>> c(e(M))
THECATINTHEHAT
random_key()[source]#

Generate a random key within the key space of this Vigenere cryptosystem. Let \(n > 0\) be the length of the cryptosystem alphabet and let \(m > 0\) be the block length of this cryptosystem. Then there are \(n^m\) possible keys.

OUTPUT:

  • A random key within the key space of this cryptosystem.

EXAMPLES:

sage: A = AlphabeticStrings()
sage: V = VigenereCryptosystem(A, 14)
sage: M = "THECATINTHEHAT"
sage: K = V.random_key()
sage: Ki = V.inverse_key(K)
sage: e = V(K)
sage: d = V(Ki)
sage: d(e(A(M))) == A(M)
True
>>> from sage.all import *
>>> A = AlphabeticStrings()
>>> V = VigenereCryptosystem(A, Integer(14))
>>> M = "THECATINTHEHAT"
>>> K = V.random_key()
>>> Ki = V.inverse_key(K)
>>> e = V(K)
>>> d = V(Ki)
>>> d(e(A(M))) == A(M)
True