Spaces:
Sleeping
Sleeping
| """ | |
| This file contains some classical ciphers and routines | |
| implementing a linear-feedback shift register (LFSR) | |
| and the Diffie-Hellman key exchange. | |
| .. warning:: | |
| This module is intended for educational purposes only. Do not use the | |
| functions in this module for real cryptographic applications. If you wish | |
| to encrypt real data, we recommend using something like the `cryptography | |
| <https://cryptography.io/en/latest/>`_ module. | |
| """ | |
| from string import whitespace, ascii_uppercase as uppercase, printable | |
| from functools import reduce | |
| import warnings | |
| from itertools import cycle | |
| from sympy.external.gmpy import GROUND_TYPES | |
| from sympy.core import Symbol | |
| from sympy.core.numbers import Rational | |
| from sympy.core.random import _randrange, _randint | |
| from sympy.external.gmpy import gcd, invert | |
| from sympy.functions.combinatorial.numbers import (totient as _euler, | |
| reduced_totient as _carmichael) | |
| from sympy.matrices import Matrix | |
| from sympy.ntheory import isprime, primitive_root, factorint | |
| from sympy.ntheory.generate import nextprime | |
| from sympy.ntheory.modular import crt | |
| from sympy.polys.domains import FF | |
| from sympy.polys.polytools import Poly | |
| from sympy.utilities.misc import as_int, filldedent, translate | |
| from sympy.utilities.iterables import uniq, multiset | |
| from sympy.utilities.decorator import doctest_depends_on | |
| if GROUND_TYPES == 'flint': | |
| __doctest_skip__ = ['lfsr_sequence'] | |
| class NonInvertibleCipherWarning(RuntimeWarning): | |
| """A warning raised if the cipher is not invertible.""" | |
| def __init__(self, msg): | |
| self.fullMessage = msg | |
| def __str__(self): | |
| return '\n\t' + self.fullMessage | |
| def warn(self, stacklevel=3): | |
| warnings.warn(self, stacklevel=stacklevel) | |
| def AZ(s=None): | |
| """Return the letters of ``s`` in uppercase. In case more than | |
| one string is passed, each of them will be processed and a list | |
| of upper case strings will be returned. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import AZ | |
| >>> AZ('Hello, world!') | |
| 'HELLOWORLD' | |
| >>> AZ('Hello, world!'.split()) | |
| ['HELLO', 'WORLD'] | |
| See Also | |
| ======== | |
| check_and_join | |
| """ | |
| if not s: | |
| return uppercase | |
| t = isinstance(s, str) | |
| if t: | |
| s = [s] | |
| rv = [check_and_join(i.upper().split(), uppercase, filter=True) | |
| for i in s] | |
| if t: | |
| return rv[0] | |
| return rv | |
| bifid5 = AZ().replace('J', '') | |
| bifid6 = AZ() + '0123456789' | |
| bifid10 = printable | |
| def padded_key(key, symbols): | |
| """Return a string of the distinct characters of ``symbols`` with | |
| those of ``key`` appearing first. A ValueError is raised if | |
| a) there are duplicate characters in ``symbols`` or | |
| b) there are characters in ``key`` that are not in ``symbols``. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import padded_key | |
| >>> padded_key('PUPPY', 'OPQRSTUVWXY') | |
| 'PUYOQRSTVWX' | |
| >>> padded_key('RSA', 'ARTIST') | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: duplicate characters in symbols: T | |
| """ | |
| syms = list(uniq(symbols)) | |
| if len(syms) != len(symbols): | |
| extra = ''.join(sorted({ | |
| i for i in symbols if symbols.count(i) > 1})) | |
| raise ValueError('duplicate characters in symbols: %s' % extra) | |
| extra = set(key) - set(syms) | |
| if extra: | |
| raise ValueError( | |
| 'characters in key but not symbols: %s' % ''.join( | |
| sorted(extra))) | |
| key0 = ''.join(list(uniq(key))) | |
| # remove from syms characters in key0 | |
| return key0 + translate(''.join(syms), None, key0) | |
| def check_and_join(phrase, symbols=None, filter=None): | |
| """ | |
| Joins characters of ``phrase`` and if ``symbols`` is given, raises | |
| an error if any character in ``phrase`` is not in ``symbols``. | |
| Parameters | |
| ========== | |
| phrase | |
| String or list of strings to be returned as a string. | |
| symbols | |
| Iterable of characters allowed in ``phrase``. | |
| If ``symbols`` is ``None``, no checking is performed. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import check_and_join | |
| >>> check_and_join('a phrase') | |
| 'a phrase' | |
| >>> check_and_join('a phrase'.upper().split()) | |
| 'APHRASE' | |
| >>> check_and_join('a phrase!'.upper().split(), 'ARE', filter=True) | |
| 'ARAE' | |
| >>> check_and_join('a phrase!'.upper().split(), 'ARE') | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: characters in phrase but not symbols: "!HPS" | |
| """ | |
| rv = ''.join(''.join(phrase)) | |
| if symbols is not None: | |
| symbols = check_and_join(symbols) | |
| missing = ''.join(sorted(set(rv) - set(symbols))) | |
| if missing: | |
| if not filter: | |
| raise ValueError( | |
| 'characters in phrase but not symbols: "%s"' % missing) | |
| rv = translate(rv, None, missing) | |
| return rv | |
| def _prep(msg, key, alp, default=None): | |
| if not alp: | |
| if not default: | |
| alp = AZ() | |
| msg = AZ(msg) | |
| key = AZ(key) | |
| else: | |
| alp = default | |
| else: | |
| alp = ''.join(alp) | |
| key = check_and_join(key, alp, filter=True) | |
| msg = check_and_join(msg, alp, filter=True) | |
| return msg, key, alp | |
| def cycle_list(k, n): | |
| """ | |
| Returns the elements of the list ``range(n)`` shifted to the | |
| left by ``k`` (so the list starts with ``k`` (mod ``n``)). | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import cycle_list | |
| >>> cycle_list(3, 10) | |
| [3, 4, 5, 6, 7, 8, 9, 0, 1, 2] | |
| """ | |
| k = k % n | |
| return list(range(k, n)) + list(range(k)) | |
| ######## shift cipher examples ############ | |
| def encipher_shift(msg, key, symbols=None): | |
| """ | |
| Performs shift cipher encryption on plaintext msg, and returns the | |
| ciphertext. | |
| Parameters | |
| ========== | |
| key : int | |
| The secret key. | |
| msg : str | |
| Plaintext of upper-case letters. | |
| Returns | |
| ======= | |
| str | |
| Ciphertext of upper-case letters. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_shift, decipher_shift | |
| >>> msg = "GONAVYBEATARMY" | |
| >>> ct = encipher_shift(msg, 1); ct | |
| 'HPOBWZCFBUBSNZ' | |
| To decipher the shifted text, change the sign of the key: | |
| >>> encipher_shift(ct, -1) | |
| 'GONAVYBEATARMY' | |
| There is also a convenience function that does this with the | |
| original key: | |
| >>> decipher_shift(ct, 1) | |
| 'GONAVYBEATARMY' | |
| Notes | |
| ===== | |
| ALGORITHM: | |
| STEPS: | |
| 0. Number the letters of the alphabet from 0, ..., N | |
| 1. Compute from the string ``msg`` a list ``L1`` of | |
| corresponding integers. | |
| 2. Compute from the list ``L1`` a new list ``L2``, given by | |
| adding ``(k mod 26)`` to each element in ``L1``. | |
| 3. Compute from the list ``L2`` a string ``ct`` of | |
| corresponding letters. | |
| The shift cipher is also called the Caesar cipher, after | |
| Julius Caesar, who, according to Suetonius, used it with a | |
| shift of three to protect messages of military significance. | |
| Caesar's nephew Augustus reportedly used a similar cipher, but | |
| with a right shift of 1. | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Caesar_cipher | |
| .. [2] https://mathworld.wolfram.com/CaesarsMethod.html | |
| See Also | |
| ======== | |
| decipher_shift | |
| """ | |
| msg, _, A = _prep(msg, '', symbols) | |
| shift = len(A) - key % len(A) | |
| key = A[shift:] + A[:shift] | |
| return translate(msg, key, A) | |
| def decipher_shift(msg, key, symbols=None): | |
| """ | |
| Return the text by shifting the characters of ``msg`` to the | |
| left by the amount given by ``key``. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_shift, decipher_shift | |
| >>> msg = "GONAVYBEATARMY" | |
| >>> ct = encipher_shift(msg, 1); ct | |
| 'HPOBWZCFBUBSNZ' | |
| To decipher the shifted text, change the sign of the key: | |
| >>> encipher_shift(ct, -1) | |
| 'GONAVYBEATARMY' | |
| Or use this function with the original key: | |
| >>> decipher_shift(ct, 1) | |
| 'GONAVYBEATARMY' | |
| """ | |
| return encipher_shift(msg, -key, symbols) | |
| def encipher_rot13(msg, symbols=None): | |
| """ | |
| Performs the ROT13 encryption on a given plaintext ``msg``. | |
| Explanation | |
| =========== | |
| ROT13 is a substitution cipher which substitutes each letter | |
| in the plaintext message for the letter furthest away from it | |
| in the English alphabet. | |
| Equivalently, it is just a Caeser (shift) cipher with a shift | |
| key of 13 (midway point of the alphabet). | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/ROT13 | |
| See Also | |
| ======== | |
| decipher_rot13 | |
| encipher_shift | |
| """ | |
| return encipher_shift(msg, 13, symbols) | |
| def decipher_rot13(msg, symbols=None): | |
| """ | |
| Performs the ROT13 decryption on a given plaintext ``msg``. | |
| Explanation | |
| ============ | |
| ``decipher_rot13`` is equivalent to ``encipher_rot13`` as both | |
| ``decipher_shift`` with a key of 13 and ``encipher_shift`` key with a | |
| key of 13 will return the same results. Nonetheless, | |
| ``decipher_rot13`` has nonetheless been explicitly defined here for | |
| consistency. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_rot13, decipher_rot13 | |
| >>> msg = 'GONAVYBEATARMY' | |
| >>> ciphertext = encipher_rot13(msg);ciphertext | |
| 'TBANILORNGNEZL' | |
| >>> decipher_rot13(ciphertext) | |
| 'GONAVYBEATARMY' | |
| >>> encipher_rot13(msg) == decipher_rot13(msg) | |
| True | |
| >>> msg == decipher_rot13(ciphertext) | |
| True | |
| """ | |
| return decipher_shift(msg, 13, symbols) | |
| ######## affine cipher examples ############ | |
| def encipher_affine(msg, key, symbols=None, _inverse=False): | |
| r""" | |
| Performs the affine cipher encryption on plaintext ``msg``, and | |
| returns the ciphertext. | |
| Explanation | |
| =========== | |
| Encryption is based on the map `x \rightarrow ax+b` (mod `N`) | |
| where ``N`` is the number of characters in the alphabet. | |
| Decryption is based on the map `x \rightarrow cx+d` (mod `N`), | |
| where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`). | |
| In particular, for the map to be invertible, we need | |
| `\mathrm{gcd}(a, N) = 1` and an error will be raised if this is | |
| not true. | |
| Parameters | |
| ========== | |
| msg : str | |
| Characters that appear in ``symbols``. | |
| a, b : int, int | |
| A pair integers, with ``gcd(a, N) = 1`` (the secret key). | |
| symbols | |
| String of characters (default = uppercase letters). | |
| When no symbols are given, ``msg`` is converted to upper case | |
| letters and all other characters are ignored. | |
| Returns | |
| ======= | |
| ct | |
| String of characters (the ciphertext message) | |
| Notes | |
| ===== | |
| ALGORITHM: | |
| STEPS: | |
| 0. Number the letters of the alphabet from 0, ..., N | |
| 1. Compute from the string ``msg`` a list ``L1`` of | |
| corresponding integers. | |
| 2. Compute from the list ``L1`` a new list ``L2``, given by | |
| replacing ``x`` by ``a*x + b (mod N)``, for each element | |
| ``x`` in ``L1``. | |
| 3. Compute from the list ``L2`` a string ``ct`` of | |
| corresponding letters. | |
| This is a straightforward generalization of the shift cipher with | |
| the added complexity of requiring 2 characters to be deciphered in | |
| order to recover the key. | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Affine_cipher | |
| See Also | |
| ======== | |
| decipher_affine | |
| """ | |
| msg, _, A = _prep(msg, '', symbols) | |
| N = len(A) | |
| a, b = key | |
| assert gcd(a, N) == 1 | |
| if _inverse: | |
| c = invert(a, N) | |
| d = -b*c | |
| a, b = c, d | |
| B = ''.join([A[(a*i + b) % N] for i in range(N)]) | |
| return translate(msg, A, B) | |
| def decipher_affine(msg, key, symbols=None): | |
| r""" | |
| Return the deciphered text that was made from the mapping, | |
| `x \rightarrow ax+b` (mod `N`), where ``N`` is the | |
| number of characters in the alphabet. Deciphering is done by | |
| reciphering with a new key: `x \rightarrow cx+d` (mod `N`), | |
| where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`). | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_affine, decipher_affine | |
| >>> msg = "GO NAVY BEAT ARMY" | |
| >>> key = (3, 1) | |
| >>> encipher_affine(msg, key) | |
| 'TROBMVENBGBALV' | |
| >>> decipher_affine(_, key) | |
| 'GONAVYBEATARMY' | |
| See Also | |
| ======== | |
| encipher_affine | |
| """ | |
| return encipher_affine(msg, key, symbols, _inverse=True) | |
| def encipher_atbash(msg, symbols=None): | |
| r""" | |
| Enciphers a given ``msg`` into its Atbash ciphertext and returns it. | |
| Explanation | |
| =========== | |
| Atbash is a substitution cipher originally used to encrypt the Hebrew | |
| alphabet. Atbash works on the principle of mapping each alphabet to its | |
| reverse / counterpart (i.e. a would map to z, b to y etc.) | |
| Atbash is functionally equivalent to the affine cipher with ``a = 25`` | |
| and ``b = 25`` | |
| See Also | |
| ======== | |
| decipher_atbash | |
| """ | |
| return encipher_affine(msg, (25, 25), symbols) | |
| def decipher_atbash(msg, symbols=None): | |
| r""" | |
| Deciphers a given ``msg`` using Atbash cipher and returns it. | |
| Explanation | |
| =========== | |
| ``decipher_atbash`` is functionally equivalent to ``encipher_atbash``. | |
| However, it has still been added as a separate function to maintain | |
| consistency. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_atbash, decipher_atbash | |
| >>> msg = 'GONAVYBEATARMY' | |
| >>> encipher_atbash(msg) | |
| 'TLMZEBYVZGZINB' | |
| >>> decipher_atbash(msg) | |
| 'TLMZEBYVZGZINB' | |
| >>> encipher_atbash(msg) == decipher_atbash(msg) | |
| True | |
| >>> msg == encipher_atbash(encipher_atbash(msg)) | |
| True | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Atbash | |
| See Also | |
| ======== | |
| encipher_atbash | |
| """ | |
| return decipher_affine(msg, (25, 25), symbols) | |
| #################### substitution cipher ########################### | |
| def encipher_substitution(msg, old, new=None): | |
| r""" | |
| Returns the ciphertext obtained by replacing each character that | |
| appears in ``old`` with the corresponding character in ``new``. | |
| If ``old`` is a mapping, then new is ignored and the replacements | |
| defined by ``old`` are used. | |
| Explanation | |
| =========== | |
| This is a more general than the affine cipher in that the key can | |
| only be recovered by determining the mapping for each symbol. | |
| Though in practice, once a few symbols are recognized the mappings | |
| for other characters can be quickly guessed. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_substitution, AZ | |
| >>> old = 'OEYAG' | |
| >>> new = '034^6' | |
| >>> msg = AZ("go navy! beat army!") | |
| >>> ct = encipher_substitution(msg, old, new); ct | |
| '60N^V4B3^T^RM4' | |
| To decrypt a substitution, reverse the last two arguments: | |
| >>> encipher_substitution(ct, new, old) | |
| 'GONAVYBEATARMY' | |
| In the special case where ``old`` and ``new`` are a permutation of | |
| order 2 (representing a transposition of characters) their order | |
| is immaterial: | |
| >>> old = 'NAVY' | |
| >>> new = 'ANYV' | |
| >>> encipher = lambda x: encipher_substitution(x, old, new) | |
| >>> encipher('NAVY') | |
| 'ANYV' | |
| >>> encipher(_) | |
| 'NAVY' | |
| The substitution cipher, in general, is a method | |
| whereby "units" (not necessarily single characters) of plaintext | |
| are replaced with ciphertext according to a regular system. | |
| >>> ords = dict(zip('abc', ['\\%i' % ord(i) for i in 'abc'])) | |
| >>> print(encipher_substitution('abc', ords)) | |
| \97\98\99 | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Substitution_cipher | |
| """ | |
| return translate(msg, old, new) | |
| ###################################################################### | |
| #################### Vigenere cipher examples ######################## | |
| ###################################################################### | |
| def encipher_vigenere(msg, key, symbols=None): | |
| """ | |
| Performs the Vigenere cipher encryption on plaintext ``msg``, and | |
| returns the ciphertext. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_vigenere, AZ | |
| >>> key = "encrypt" | |
| >>> msg = "meet me on monday" | |
| >>> encipher_vigenere(msg, key) | |
| 'QRGKKTHRZQEBPR' | |
| Section 1 of the Kryptos sculpture at the CIA headquarters | |
| uses this cipher and also changes the order of the | |
| alphabet [2]_. Here is the first line of that section of | |
| the sculpture: | |
| >>> from sympy.crypto.crypto import decipher_vigenere, padded_key | |
| >>> alp = padded_key('KRYPTOS', AZ()) | |
| >>> key = 'PALIMPSEST' | |
| >>> msg = 'EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJ' | |
| >>> decipher_vigenere(msg, key, alp) | |
| 'BETWEENSUBTLESHADINGANDTHEABSENC' | |
| Explanation | |
| =========== | |
| The Vigenere cipher is named after Blaise de Vigenere, a sixteenth | |
| century diplomat and cryptographer, by a historical accident. | |
| Vigenere actually invented a different and more complicated cipher. | |
| The so-called *Vigenere cipher* was actually invented | |
| by Giovan Batista Belaso in 1553. | |
| This cipher was used in the 1800's, for example, during the American | |
| Civil War. The Confederacy used a brass cipher disk to implement the | |
| Vigenere cipher (now on display in the NSA Museum in Fort | |
| Meade) [1]_. | |
| The Vigenere cipher is a generalization of the shift cipher. | |
| Whereas the shift cipher shifts each letter by the same amount | |
| (that amount being the key of the shift cipher) the Vigenere | |
| cipher shifts a letter by an amount determined by the key (which is | |
| a word or phrase known only to the sender and receiver). | |
| For example, if the key was a single letter, such as "C", then the | |
| so-called Vigenere cipher is actually a shift cipher with a | |
| shift of `2` (since "C" is the 2nd letter of the alphabet, if | |
| you start counting at `0`). If the key was a word with two | |
| letters, such as "CA", then the so-called Vigenere cipher will | |
| shift letters in even positions by `2` and letters in odd positions | |
| are left alone (shifted by `0`, since "A" is the 0th letter, if | |
| you start counting at `0`). | |
| ALGORITHM: | |
| INPUT: | |
| ``msg``: string of characters that appear in ``symbols`` | |
| (the plaintext) | |
| ``key``: a string of characters that appear in ``symbols`` | |
| (the secret key) | |
| ``symbols``: a string of letters defining the alphabet | |
| OUTPUT: | |
| ``ct``: string of characters (the ciphertext message) | |
| STEPS: | |
| 0. Number the letters of the alphabet from 0, ..., N | |
| 1. Compute from the string ``key`` a list ``L1`` of | |
| corresponding integers. Let ``n1 = len(L1)``. | |
| 2. Compute from the string ``msg`` a list ``L2`` of | |
| corresponding integers. Let ``n2 = len(L2)``. | |
| 3. Break ``L2`` up sequentially into sublists of size | |
| ``n1``; the last sublist may be smaller than ``n1`` | |
| 4. For each of these sublists ``L`` of ``L2``, compute a | |
| new list ``C`` given by ``C[i] = L[i] + L1[i] (mod N)`` | |
| to the ``i``-th element in the sublist, for each ``i``. | |
| 5. Assemble these lists ``C`` by concatenation into a new | |
| list of length ``n2``. | |
| 6. Compute from the new list a string ``ct`` of | |
| corresponding letters. | |
| Once it is known that the key is, say, `n` characters long, | |
| frequency analysis can be applied to every `n`-th letter of | |
| the ciphertext to determine the plaintext. This method is | |
| called *Kasiski examination* (although it was first discovered | |
| by Babbage). If they key is as long as the message and is | |
| comprised of randomly selected characters -- a one-time pad -- the | |
| message is theoretically unbreakable. | |
| The cipher Vigenere actually discovered is an "auto-key" cipher | |
| described as follows. | |
| ALGORITHM: | |
| INPUT: | |
| ``key``: a string of letters (the secret key) | |
| ``msg``: string of letters (the plaintext message) | |
| OUTPUT: | |
| ``ct``: string of upper-case letters (the ciphertext message) | |
| STEPS: | |
| 0. Number the letters of the alphabet from 0, ..., N | |
| 1. Compute from the string ``msg`` a list ``L2`` of | |
| corresponding integers. Let ``n2 = len(L2)``. | |
| 2. Let ``n1`` be the length of the key. Append to the | |
| string ``key`` the first ``n2 - n1`` characters of | |
| the plaintext message. Compute from this string (also of | |
| length ``n2``) a list ``L1`` of integers corresponding | |
| to the letter numbers in the first step. | |
| 3. Compute a new list ``C`` given by | |
| ``C[i] = L1[i] + L2[i] (mod N)``. | |
| 4. Compute from the new list a string ``ct`` of letters | |
| corresponding to the new integers. | |
| To decipher the auto-key ciphertext, the key is used to decipher | |
| the first ``n1`` characters and then those characters become the | |
| key to decipher the next ``n1`` characters, etc...: | |
| >>> m = AZ('go navy, beat army! yes you can'); m | |
| 'GONAVYBEATARMYYESYOUCAN' | |
| >>> key = AZ('gold bug'); n1 = len(key); n2 = len(m) | |
| >>> auto_key = key + m[:n2 - n1]; auto_key | |
| 'GOLDBUGGONAVYBEATARMYYE' | |
| >>> ct = encipher_vigenere(m, auto_key); ct | |
| 'MCYDWSHKOGAMKZCELYFGAYR' | |
| >>> n1 = len(key) | |
| >>> pt = [] | |
| >>> while ct: | |
| ... part, ct = ct[:n1], ct[n1:] | |
| ... pt.append(decipher_vigenere(part, key)) | |
| ... key = pt[-1] | |
| ... | |
| >>> ''.join(pt) == m | |
| True | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Vigenere_cipher | |
| .. [2] https://web.archive.org/web/20071116100808/https://filebox.vt.edu/users/batman/kryptos.html | |
| (short URL: https://goo.gl/ijr22d) | |
| """ | |
| msg, key, A = _prep(msg, key, symbols) | |
| map = {c: i for i, c in enumerate(A)} | |
| key = [map[c] for c in key] | |
| N = len(map) | |
| k = len(key) | |
| rv = [] | |
| for i, m in enumerate(msg): | |
| rv.append(A[(map[m] + key[i % k]) % N]) | |
| rv = ''.join(rv) | |
| return rv | |
| def decipher_vigenere(msg, key, symbols=None): | |
| """ | |
| Decode using the Vigenere cipher. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import decipher_vigenere | |
| >>> key = "encrypt" | |
| >>> ct = "QRGK kt HRZQE BPR" | |
| >>> decipher_vigenere(ct, key) | |
| 'MEETMEONMONDAY' | |
| """ | |
| msg, key, A = _prep(msg, key, symbols) | |
| map = {c: i for i, c in enumerate(A)} | |
| N = len(A) # normally, 26 | |
| K = [map[c] for c in key] | |
| n = len(K) | |
| C = [map[c] for c in msg] | |
| rv = ''.join([A[(-K[i % n] + c) % N] for i, c in enumerate(C)]) | |
| return rv | |
| #################### Hill cipher ######################## | |
| def encipher_hill(msg, key, symbols=None, pad="Q"): | |
| r""" | |
| Return the Hill cipher encryption of ``msg``. | |
| Explanation | |
| =========== | |
| The Hill cipher [1]_, invented by Lester S. Hill in the 1920's [2]_, | |
| was the first polygraphic cipher in which it was practical | |
| (though barely) to operate on more than three symbols at once. | |
| The following discussion assumes an elementary knowledge of | |
| matrices. | |
| First, each letter is first encoded as a number starting with 0. | |
| Suppose your message `msg` consists of `n` capital letters, with no | |
| spaces. This may be regarded an `n`-tuple M of elements of | |
| `Z_{26}` (if the letters are those of the English alphabet). A key | |
| in the Hill cipher is a `k x k` matrix `K`, all of whose entries | |
| are in `Z_{26}`, such that the matrix `K` is invertible (i.e., the | |
| linear transformation `K: Z_{N}^k \rightarrow Z_{N}^k` | |
| is one-to-one). | |
| Parameters | |
| ========== | |
| msg | |
| Plaintext message of `n` upper-case letters. | |
| key | |
| A `k \times k` invertible matrix `K`, all of whose entries are | |
| in `Z_{26}` (or whatever number of symbols are being used). | |
| pad | |
| Character (default "Q") to use to make length of text be a | |
| multiple of ``k``. | |
| Returns | |
| ======= | |
| ct | |
| Ciphertext of upper-case letters. | |
| Notes | |
| ===== | |
| ALGORITHM: | |
| STEPS: | |
| 0. Number the letters of the alphabet from 0, ..., N | |
| 1. Compute from the string ``msg`` a list ``L`` of | |
| corresponding integers. Let ``n = len(L)``. | |
| 2. Break the list ``L`` up into ``t = ceiling(n/k)`` | |
| sublists ``L_1``, ..., ``L_t`` of size ``k`` (with | |
| the last list "padded" to ensure its size is | |
| ``k``). | |
| 3. Compute new list ``C_1``, ..., ``C_t`` given by | |
| ``C[i] = K*L_i`` (arithmetic is done mod N), for each | |
| ``i``. | |
| 4. Concatenate these into a list ``C = C_1 + ... + C_t``. | |
| 5. Compute from ``C`` a string ``ct`` of corresponding | |
| letters. This has length ``k*t``. | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Hill_cipher | |
| .. [2] Lester S. Hill, Cryptography in an Algebraic Alphabet, | |
| The American Mathematical Monthly Vol.36, June-July 1929, | |
| pp.306-312. | |
| See Also | |
| ======== | |
| decipher_hill | |
| """ | |
| assert key.is_square | |
| assert len(pad) == 1 | |
| msg, pad, A = _prep(msg, pad, symbols) | |
| map = {c: i for i, c in enumerate(A)} | |
| P = [map[c] for c in msg] | |
| N = len(A) | |
| k = key.cols | |
| n = len(P) | |
| m, r = divmod(n, k) | |
| if r: | |
| P = P + [map[pad]]*(k - r) | |
| m += 1 | |
| rv = ''.join([A[c % N] for j in range(m) for c in | |
| list(key*Matrix(k, 1, [P[i] | |
| for i in range(k*j, k*(j + 1))]))]) | |
| return rv | |
| def decipher_hill(msg, key, symbols=None): | |
| """ | |
| Deciphering is the same as enciphering but using the inverse of the | |
| key matrix. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_hill, decipher_hill | |
| >>> from sympy import Matrix | |
| >>> key = Matrix([[1, 2], [3, 5]]) | |
| >>> encipher_hill("meet me on monday", key) | |
| 'UEQDUEODOCTCWQ' | |
| >>> decipher_hill(_, key) | |
| 'MEETMEONMONDAY' | |
| When the length of the plaintext (stripped of invalid characters) | |
| is not a multiple of the key dimension, extra characters will | |
| appear at the end of the enciphered and deciphered text. In order to | |
| decipher the text, those characters must be included in the text to | |
| be deciphered. In the following, the key has a dimension of 4 but | |
| the text is 2 short of being a multiple of 4 so two characters will | |
| be added. | |
| >>> key = Matrix([[1, 1, 1, 2], [0, 1, 1, 0], | |
| ... [2, 2, 3, 4], [1, 1, 0, 1]]) | |
| >>> msg = "ST" | |
| >>> encipher_hill(msg, key) | |
| 'HJEB' | |
| >>> decipher_hill(_, key) | |
| 'STQQ' | |
| >>> encipher_hill(msg, key, pad="Z") | |
| 'ISPK' | |
| >>> decipher_hill(_, key) | |
| 'STZZ' | |
| If the last two characters of the ciphertext were ignored in | |
| either case, the wrong plaintext would be recovered: | |
| >>> decipher_hill("HD", key) | |
| 'ORMV' | |
| >>> decipher_hill("IS", key) | |
| 'UIKY' | |
| See Also | |
| ======== | |
| encipher_hill | |
| """ | |
| assert key.is_square | |
| msg, _, A = _prep(msg, '', symbols) | |
| map = {c: i for i, c in enumerate(A)} | |
| C = [map[c] for c in msg] | |
| N = len(A) | |
| k = key.cols | |
| n = len(C) | |
| m, r = divmod(n, k) | |
| if r: | |
| C = C + [0]*(k - r) | |
| m += 1 | |
| key_inv = key.inv_mod(N) | |
| rv = ''.join([A[p % N] for j in range(m) for p in | |
| list(key_inv*Matrix( | |
| k, 1, [C[i] for i in range(k*j, k*(j + 1))]))]) | |
| return rv | |
| #################### Bifid cipher ######################## | |
| def encipher_bifid(msg, key, symbols=None): | |
| r""" | |
| Performs the Bifid cipher encryption on plaintext ``msg``, and | |
| returns the ciphertext. | |
| This is the version of the Bifid cipher that uses an `n \times n` | |
| Polybius square. | |
| Parameters | |
| ========== | |
| msg | |
| Plaintext string. | |
| key | |
| Short string for key. | |
| Duplicate characters are ignored and then it is padded with the | |
| characters in ``symbols`` that were not in the short key. | |
| symbols | |
| `n \times n` characters defining the alphabet. | |
| (default is string.printable) | |
| Returns | |
| ======= | |
| ciphertext | |
| Ciphertext using Bifid5 cipher without spaces. | |
| See Also | |
| ======== | |
| decipher_bifid, encipher_bifid5, encipher_bifid6 | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Bifid_cipher | |
| """ | |
| msg, key, A = _prep(msg, key, symbols, bifid10) | |
| long_key = ''.join(uniq(key)) or A | |
| n = len(A)**.5 | |
| if n != int(n): | |
| raise ValueError( | |
| 'Length of alphabet (%s) is not a square number.' % len(A)) | |
| N = int(n) | |
| if len(long_key) < N**2: | |
| long_key = list(long_key) + [x for x in A if x not in long_key] | |
| # the fractionalization | |
| row_col = {ch: divmod(i, N) for i, ch in enumerate(long_key)} | |
| r, c = zip(*[row_col[x] for x in msg]) | |
| rc = r + c | |
| ch = {i: ch for ch, i in row_col.items()} | |
| rv = ''.join(ch[i] for i in zip(rc[::2], rc[1::2])) | |
| return rv | |
| def decipher_bifid(msg, key, symbols=None): | |
| r""" | |
| Performs the Bifid cipher decryption on ciphertext ``msg``, and | |
| returns the plaintext. | |
| This is the version of the Bifid cipher that uses the `n \times n` | |
| Polybius square. | |
| Parameters | |
| ========== | |
| msg | |
| Ciphertext string. | |
| key | |
| Short string for key. | |
| Duplicate characters are ignored and then it is padded with the | |
| characters in symbols that were not in the short key. | |
| symbols | |
| `n \times n` characters defining the alphabet. | |
| (default=string.printable, a `10 \times 10` matrix) | |
| Returns | |
| ======= | |
| deciphered | |
| Deciphered text. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... encipher_bifid, decipher_bifid, AZ) | |
| Do an encryption using the bifid5 alphabet: | |
| >>> alp = AZ().replace('J', '') | |
| >>> ct = AZ("meet me on monday!") | |
| >>> key = AZ("gold bug") | |
| >>> encipher_bifid(ct, key, alp) | |
| 'IEILHHFSTSFQYE' | |
| When entering the text or ciphertext, spaces are ignored so it | |
| can be formatted as desired. Re-entering the ciphertext from the | |
| preceding, putting 4 characters per line and padding with an extra | |
| J, does not cause problems for the deciphering: | |
| >>> decipher_bifid(''' | |
| ... IEILH | |
| ... HFSTS | |
| ... FQYEJ''', key, alp) | |
| 'MEETMEONMONDAY' | |
| When no alphabet is given, all 100 printable characters will be | |
| used: | |
| >>> key = '' | |
| >>> encipher_bifid('hello world!', key) | |
| 'bmtwmg-bIo*w' | |
| >>> decipher_bifid(_, key) | |
| 'hello world!' | |
| If the key is changed, a different encryption is obtained: | |
| >>> key = 'gold bug' | |
| >>> encipher_bifid('hello world!', 'gold_bug') | |
| 'hg2sfuei7t}w' | |
| And if the key used to decrypt the message is not exact, the | |
| original text will not be perfectly obtained: | |
| >>> decipher_bifid(_, 'gold pug') | |
| 'heldo~wor6d!' | |
| """ | |
| msg, _, A = _prep(msg, '', symbols, bifid10) | |
| long_key = ''.join(uniq(key)) or A | |
| n = len(A)**.5 | |
| if n != int(n): | |
| raise ValueError( | |
| 'Length of alphabet (%s) is not a square number.' % len(A)) | |
| N = int(n) | |
| if len(long_key) < N**2: | |
| long_key = list(long_key) + [x for x in A if x not in long_key] | |
| # the reverse fractionalization | |
| row_col = { | |
| ch: divmod(i, N) for i, ch in enumerate(long_key)} | |
| rc = [i for c in msg for i in row_col[c]] | |
| n = len(msg) | |
| rc = zip(*(rc[:n], rc[n:])) | |
| ch = {i: ch for ch, i in row_col.items()} | |
| rv = ''.join(ch[i] for i in rc) | |
| return rv | |
| def bifid_square(key): | |
| """Return characters of ``key`` arranged in a square. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... bifid_square, AZ, padded_key, bifid5) | |
| >>> bifid_square(AZ().replace('J', '')) | |
| Matrix([ | |
| [A, B, C, D, E], | |
| [F, G, H, I, K], | |
| [L, M, N, O, P], | |
| [Q, R, S, T, U], | |
| [V, W, X, Y, Z]]) | |
| >>> bifid_square(padded_key(AZ('gold bug!'), bifid5)) | |
| Matrix([ | |
| [G, O, L, D, B], | |
| [U, A, C, E, F], | |
| [H, I, K, M, N], | |
| [P, Q, R, S, T], | |
| [V, W, X, Y, Z]]) | |
| See Also | |
| ======== | |
| padded_key | |
| """ | |
| A = ''.join(uniq(''.join(key))) | |
| n = len(A)**.5 | |
| if n != int(n): | |
| raise ValueError( | |
| 'Length of alphabet (%s) is not a square number.' % len(A)) | |
| n = int(n) | |
| f = lambda i, j: Symbol(A[n*i + j]) | |
| rv = Matrix(n, n, f) | |
| return rv | |
| def encipher_bifid5(msg, key): | |
| r""" | |
| Performs the Bifid cipher encryption on plaintext ``msg``, and | |
| returns the ciphertext. | |
| Explanation | |
| =========== | |
| This is the version of the Bifid cipher that uses the `5 \times 5` | |
| Polybius square. The letter "J" is ignored so it must be replaced | |
| with something else (traditionally an "I") before encryption. | |
| ALGORITHM: (5x5 case) | |
| STEPS: | |
| 0. Create the `5 \times 5` Polybius square ``S`` associated | |
| to ``key`` as follows: | |
| a) moving from left-to-right, top-to-bottom, | |
| place the letters of the key into a `5 \times 5` | |
| matrix, | |
| b) if the key has less than 25 letters, add the | |
| letters of the alphabet not in the key until the | |
| `5 \times 5` square is filled. | |
| 1. Create a list ``P`` of pairs of numbers which are the | |
| coordinates in the Polybius square of the letters in | |
| ``msg``. | |
| 2. Let ``L1`` be the list of all first coordinates of ``P`` | |
| (length of ``L1 = n``), let ``L2`` be the list of all | |
| second coordinates of ``P`` (so the length of ``L2`` | |
| is also ``n``). | |
| 3. Let ``L`` be the concatenation of ``L1`` and ``L2`` | |
| (length ``L = 2*n``), except that consecutive numbers | |
| are paired ``(L[2*i], L[2*i + 1])``. You can regard | |
| ``L`` as a list of pairs of length ``n``. | |
| 4. Let ``C`` be the list of all letters which are of the | |
| form ``S[i, j]``, for all ``(i, j)`` in ``L``. As a | |
| string, this is the ciphertext of ``msg``. | |
| Parameters | |
| ========== | |
| msg : str | |
| Plaintext string. | |
| Converted to upper case and filtered of anything but all letters | |
| except J. | |
| key | |
| Short string for key; non-alphabetic letters, J and duplicated | |
| characters are ignored and then, if the length is less than 25 | |
| characters, it is padded with other letters of the alphabet | |
| (in alphabetical order). | |
| Returns | |
| ======= | |
| ct | |
| Ciphertext (all caps, no spaces). | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... encipher_bifid5, decipher_bifid5) | |
| "J" will be omitted unless it is replaced with something else: | |
| >>> round_trip = lambda m, k: \ | |
| ... decipher_bifid5(encipher_bifid5(m, k), k) | |
| >>> key = 'a' | |
| >>> msg = "JOSIE" | |
| >>> round_trip(msg, key) | |
| 'OSIE' | |
| >>> round_trip(msg.replace("J", "I"), key) | |
| 'IOSIE' | |
| >>> j = "QIQ" | |
| >>> round_trip(msg.replace("J", j), key).replace(j, "J") | |
| 'JOSIE' | |
| Notes | |
| ===== | |
| The Bifid cipher was invented around 1901 by Felix Delastelle. | |
| It is a *fractional substitution* cipher, where letters are | |
| replaced by pairs of symbols from a smaller alphabet. The | |
| cipher uses a `5 \times 5` square filled with some ordering of the | |
| alphabet, except that "J" is replaced with "I" (this is a so-called | |
| Polybius square; there is a `6 \times 6` analog if you add back in | |
| "J" and also append onto the usual 26 letter alphabet, the digits | |
| 0, 1, ..., 9). | |
| According to Helen Gaines' book *Cryptanalysis*, this type of cipher | |
| was used in the field by the German Army during World War I. | |
| See Also | |
| ======== | |
| decipher_bifid5, encipher_bifid | |
| """ | |
| msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5) | |
| key = padded_key(key, bifid5) | |
| return encipher_bifid(msg, '', key) | |
| def decipher_bifid5(msg, key): | |
| r""" | |
| Return the Bifid cipher decryption of ``msg``. | |
| Explanation | |
| =========== | |
| This is the version of the Bifid cipher that uses the `5 \times 5` | |
| Polybius square; the letter "J" is ignored unless a ``key`` of | |
| length 25 is used. | |
| Parameters | |
| ========== | |
| msg | |
| Ciphertext string. | |
| key | |
| Short string for key; duplicated characters are ignored and if | |
| the length is less then 25 characters, it will be padded with | |
| other letters from the alphabet omitting "J". | |
| Non-alphabetic characters are ignored. | |
| Returns | |
| ======= | |
| plaintext | |
| Plaintext from Bifid5 cipher (all caps, no spaces). | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_bifid5, decipher_bifid5 | |
| >>> key = "gold bug" | |
| >>> encipher_bifid5('meet me on friday', key) | |
| 'IEILEHFSTSFXEE' | |
| >>> encipher_bifid5('meet me on monday', key) | |
| 'IEILHHFSTSFQYE' | |
| >>> decipher_bifid5(_, key) | |
| 'MEETMEONMONDAY' | |
| """ | |
| msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5) | |
| key = padded_key(key, bifid5) | |
| return decipher_bifid(msg, '', key) | |
| def bifid5_square(key=None): | |
| r""" | |
| 5x5 Polybius square. | |
| Produce the Polybius square for the `5 \times 5` Bifid cipher. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import bifid5_square | |
| >>> bifid5_square("gold bug") | |
| Matrix([ | |
| [G, O, L, D, B], | |
| [U, A, C, E, F], | |
| [H, I, K, M, N], | |
| [P, Q, R, S, T], | |
| [V, W, X, Y, Z]]) | |
| """ | |
| if not key: | |
| key = bifid5 | |
| else: | |
| _, key, _ = _prep('', key.upper(), None, bifid5) | |
| key = padded_key(key, bifid5) | |
| return bifid_square(key) | |
| def encipher_bifid6(msg, key): | |
| r""" | |
| Performs the Bifid cipher encryption on plaintext ``msg``, and | |
| returns the ciphertext. | |
| This is the version of the Bifid cipher that uses the `6 \times 6` | |
| Polybius square. | |
| Parameters | |
| ========== | |
| msg | |
| Plaintext string (digits okay). | |
| key | |
| Short string for key (digits okay). | |
| If ``key`` is less than 36 characters long, the square will be | |
| filled with letters A through Z and digits 0 through 9. | |
| Returns | |
| ======= | |
| ciphertext | |
| Ciphertext from Bifid cipher (all caps, no spaces). | |
| See Also | |
| ======== | |
| decipher_bifid6, encipher_bifid | |
| """ | |
| msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6) | |
| key = padded_key(key, bifid6) | |
| return encipher_bifid(msg, '', key) | |
| def decipher_bifid6(msg, key): | |
| r""" | |
| Performs the Bifid cipher decryption on ciphertext ``msg``, and | |
| returns the plaintext. | |
| This is the version of the Bifid cipher that uses the `6 \times 6` | |
| Polybius square. | |
| Parameters | |
| ========== | |
| msg | |
| Ciphertext string (digits okay); converted to upper case | |
| key | |
| Short string for key (digits okay). | |
| If ``key`` is less than 36 characters long, the square will be | |
| filled with letters A through Z and digits 0 through 9. | |
| All letters are converted to uppercase. | |
| Returns | |
| ======= | |
| plaintext | |
| Plaintext from Bifid cipher (all caps, no spaces). | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_bifid6, decipher_bifid6 | |
| >>> key = "gold bug" | |
| >>> encipher_bifid6('meet me on monday at 8am', key) | |
| 'KFKLJJHF5MMMKTFRGPL' | |
| >>> decipher_bifid6(_, key) | |
| 'MEETMEONMONDAYAT8AM' | |
| """ | |
| msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6) | |
| key = padded_key(key, bifid6) | |
| return decipher_bifid(msg, '', key) | |
| def bifid6_square(key=None): | |
| r""" | |
| 6x6 Polybius square. | |
| Produces the Polybius square for the `6 \times 6` Bifid cipher. | |
| Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "9". | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import bifid6_square | |
| >>> key = "gold bug" | |
| >>> bifid6_square(key) | |
| Matrix([ | |
| [G, O, L, D, B, U], | |
| [A, C, E, F, H, I], | |
| [J, K, M, N, P, Q], | |
| [R, S, T, V, W, X], | |
| [Y, Z, 0, 1, 2, 3], | |
| [4, 5, 6, 7, 8, 9]]) | |
| """ | |
| if not key: | |
| key = bifid6 | |
| else: | |
| _, key, _ = _prep('', key.upper(), None, bifid6) | |
| key = padded_key(key, bifid6) | |
| return bifid_square(key) | |
| #################### RSA ############################# | |
| def _decipher_rsa_crt(i, d, factors): | |
| """Decipher RSA using chinese remainder theorem from the information | |
| of the relatively-prime factors of the modulus. | |
| Parameters | |
| ========== | |
| i : integer | |
| Ciphertext | |
| d : integer | |
| The exponent component. | |
| factors : list of relatively-prime integers | |
| The integers given must be coprime and the product must equal | |
| the modulus component of the original RSA key. | |
| Examples | |
| ======== | |
| How to decrypt RSA with CRT: | |
| >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key | |
| >>> primes = [61, 53] | |
| >>> e = 17 | |
| >>> args = primes + [e] | |
| >>> puk = rsa_public_key(*args) | |
| >>> prk = rsa_private_key(*args) | |
| >>> from sympy.crypto.crypto import encipher_rsa, _decipher_rsa_crt | |
| >>> msg = 65 | |
| >>> crt_primes = primes | |
| >>> encrypted = encipher_rsa(msg, puk) | |
| >>> decrypted = _decipher_rsa_crt(encrypted, prk[1], primes) | |
| >>> decrypted | |
| 65 | |
| """ | |
| moduluses = [pow(i, d, p) for p in factors] | |
| result = crt(factors, moduluses) | |
| if not result: | |
| raise ValueError("CRT failed") | |
| return result[0] | |
| def _rsa_key(*args, public=True, private=True, totient='Euler', index=None, multipower=None): | |
| r"""A private subroutine to generate RSA key | |
| Parameters | |
| ========== | |
| public, private : bool, optional | |
| Flag to generate either a public key, a private key. | |
| totient : 'Euler' or 'Carmichael' | |
| Different notation used for totient. | |
| multipower : bool, optional | |
| Flag to bypass warning for multipower RSA. | |
| """ | |
| if len(args) < 2: | |
| return False | |
| if totient not in ('Euler', 'Carmichael'): | |
| raise ValueError( | |
| "The argument totient={} should either be " \ | |
| "'Euler', 'Carmichalel'." \ | |
| .format(totient)) | |
| if totient == 'Euler': | |
| _totient = _euler | |
| else: | |
| _totient = _carmichael | |
| if index is not None: | |
| index = as_int(index) | |
| if totient != 'Carmichael': | |
| raise ValueError( | |
| "Setting the 'index' keyword argument requires totient" | |
| "notation to be specified as 'Carmichael'.") | |
| primes, e = args[:-1], args[-1] | |
| if not all(isprime(p) for p in primes): | |
| new_primes = [] | |
| for i in primes: | |
| new_primes.extend(factorint(i, multiple=True)) | |
| primes = new_primes | |
| n = reduce(lambda i, j: i*j, primes) | |
| tally = multiset(primes) | |
| if all(v == 1 for v in tally.values()): | |
| phi = int(_totient(tally)) | |
| else: | |
| if not multipower: | |
| NonInvertibleCipherWarning( | |
| 'Non-distinctive primes found in the factors {}. ' | |
| 'The cipher may not be decryptable for some numbers ' | |
| 'in the complete residue system Z[{}], but the cipher ' | |
| 'can still be valid if you restrict the domain to be ' | |
| 'the reduced residue system Z*[{}]. You can pass ' | |
| 'the flag multipower=True if you want to suppress this ' | |
| 'warning.' | |
| .format(primes, n, n) | |
| # stacklevel=4 because most users will call a function that | |
| # calls this function | |
| ).warn(stacklevel=4) | |
| phi = int(_totient(tally)) | |
| if gcd(e, phi) == 1: | |
| if public and not private: | |
| if isinstance(index, int): | |
| e = e % phi | |
| e += index * phi | |
| return n, e | |
| if private and not public: | |
| d = invert(e, phi) | |
| if isinstance(index, int): | |
| d += index * phi | |
| return n, d | |
| return False | |
| def rsa_public_key(*args, **kwargs): | |
| r"""Return the RSA *public key* pair, `(n, e)` | |
| Parameters | |
| ========== | |
| args : naturals | |
| If specified as `p, q, e` where `p` and `q` are distinct primes | |
| and `e` is a desired public exponent of the RSA, `n = p q` and | |
| `e` will be verified against the totient | |
| `\phi(n)` (Euler totient) or `\lambda(n)` (Carmichael totient) | |
| to be `\gcd(e, \phi(n)) = 1` or `\gcd(e, \lambda(n)) = 1`. | |
| If specified as `p_1, p_2, \dots, p_n, e` where | |
| `p_1, p_2, \dots, p_n` are specified as primes, | |
| and `e` is specified as a desired public exponent of the RSA, | |
| it will be able to form a multi-prime RSA, which is a more | |
| generalized form of the popular 2-prime RSA. | |
| It can also be possible to form a single-prime RSA by specifying | |
| the argument as `p, e`, which can be considered a trivial case | |
| of a multiprime RSA. | |
| Furthermore, it can be possible to form a multi-power RSA by | |
| specifying two or more pairs of the primes to be same. | |
| However, unlike the two-distinct prime RSA or multi-prime | |
| RSA, not every numbers in the complete residue system | |
| (`\mathbb{Z}_n`) will be decryptable since the mapping | |
| `\mathbb{Z}_{n} \rightarrow \mathbb{Z}_{n}` | |
| will not be bijective. | |
| (Only except for the trivial case when | |
| `e = 1` | |
| or more generally, | |
| .. math:: | |
| e \in \left \{ 1 + k \lambda(n) | |
| \mid k \in \mathbb{Z} \land k \geq 0 \right \} | |
| when RSA reduces to the identity.) | |
| However, the RSA can still be decryptable for the numbers in the | |
| reduced residue system (`\mathbb{Z}_n^{\times}`), since the | |
| mapping | |
| `\mathbb{Z}_{n}^{\times} \rightarrow \mathbb{Z}_{n}^{\times}` | |
| can still be bijective. | |
| If you pass a non-prime integer to the arguments | |
| `p_1, p_2, \dots, p_n`, the particular number will be | |
| prime-factored and it will become either a multi-prime RSA or a | |
| multi-power RSA in its canonical form, depending on whether the | |
| product equals its radical or not. | |
| `p_1 p_2 \dots p_n = \text{rad}(p_1 p_2 \dots p_n)` | |
| totient : bool, optional | |
| If ``'Euler'``, it uses Euler's totient `\phi(n)` which is | |
| :meth:`sympy.functions.combinatorial.numbers.totient` in SymPy. | |
| If ``'Carmichael'``, it uses Carmichael's totient `\lambda(n)` | |
| which is :meth:`sympy.functions.combinatorial.numbers.reduced_totient` in SymPy. | |
| Unlike private key generation, this is a trivial keyword for | |
| public key generation because | |
| `\gcd(e, \phi(n)) = 1 \iff \gcd(e, \lambda(n)) = 1`. | |
| index : nonnegative integer, optional | |
| Returns an arbitrary solution of a RSA public key at the index | |
| specified at `0, 1, 2, \dots`. This parameter needs to be | |
| specified along with ``totient='Carmichael'``. | |
| Similarly to the non-uniquenss of a RSA private key as described | |
| in the ``index`` parameter documentation in | |
| :meth:`rsa_private_key`, RSA public key is also not unique and | |
| there is an infinite number of RSA public exponents which | |
| can behave in the same manner. | |
| From any given RSA public exponent `e`, there are can be an | |
| another RSA public exponent `e + k \lambda(n)` where `k` is an | |
| integer, `\lambda` is a Carmichael's totient function. | |
| However, considering only the positive cases, there can be | |
| a principal solution of a RSA public exponent `e_0` in | |
| `0 < e_0 < \lambda(n)`, and all the other solutions | |
| can be canonicalzed in a form of `e_0 + k \lambda(n)`. | |
| ``index`` specifies the `k` notation to yield any possible value | |
| an RSA public key can have. | |
| An example of computing any arbitrary RSA public key: | |
| >>> from sympy.crypto.crypto import rsa_public_key | |
| >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=0) | |
| (3233, 17) | |
| >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=1) | |
| (3233, 797) | |
| >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=2) | |
| (3233, 1577) | |
| multipower : bool, optional | |
| Any pair of non-distinct primes found in the RSA specification | |
| will restrict the domain of the cryptosystem, as noted in the | |
| explanation of the parameter ``args``. | |
| SymPy RSA key generator may give a warning before dispatching it | |
| as a multi-power RSA, however, you can disable the warning if | |
| you pass ``True`` to this keyword. | |
| Returns | |
| ======= | |
| (n, e) : int, int | |
| `n` is a product of any arbitrary number of primes given as | |
| the argument. | |
| `e` is relatively prime (coprime) to the Euler totient | |
| `\phi(n)`. | |
| False | |
| Returned if less than two arguments are given, or `e` is | |
| not relatively prime to the modulus. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import rsa_public_key | |
| A public key of a two-prime RSA: | |
| >>> p, q, e = 3, 5, 7 | |
| >>> rsa_public_key(p, q, e) | |
| (15, 7) | |
| >>> rsa_public_key(p, q, 30) | |
| False | |
| A public key of a multiprime RSA: | |
| >>> primes = [2, 3, 5, 7, 11, 13] | |
| >>> e = 7 | |
| >>> args = primes + [e] | |
| >>> rsa_public_key(*args) | |
| (30030, 7) | |
| Notes | |
| ===== | |
| Although the RSA can be generalized over any modulus `n`, using | |
| two large primes had became the most popular specification because a | |
| product of two large primes is usually the hardest to factor | |
| relatively to the digits of `n` can have. | |
| However, it may need further understanding of the time complexities | |
| of each prime-factoring algorithms to verify the claim. | |
| See Also | |
| ======== | |
| rsa_private_key | |
| encipher_rsa | |
| decipher_rsa | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29 | |
| .. [2] https://cacr.uwaterloo.ca/techreports/2006/cacr2006-16.pdf | |
| .. [3] https://link.springer.com/content/pdf/10.1007/BFb0055738.pdf | |
| .. [4] https://www.itiis.org/digital-library/manuscript/1381 | |
| """ | |
| return _rsa_key(*args, public=True, private=False, **kwargs) | |
| def rsa_private_key(*args, **kwargs): | |
| r"""Return the RSA *private key* pair, `(n, d)` | |
| Parameters | |
| ========== | |
| args : naturals | |
| The keyword is identical to the ``args`` in | |
| :meth:`rsa_public_key`. | |
| totient : bool, optional | |
| If ``'Euler'``, it uses Euler's totient convention `\phi(n)` | |
| which is :meth:`sympy.functions.combinatorial.numbers.totient` in SymPy. | |
| If ``'Carmichael'``, it uses Carmichael's totient convention | |
| `\lambda(n)` which is | |
| :meth:`sympy.functions.combinatorial.numbers.reduced_totient` in SymPy. | |
| There can be some output differences for private key generation | |
| as examples below. | |
| Example using Euler's totient: | |
| >>> from sympy.crypto.crypto import rsa_private_key | |
| >>> rsa_private_key(61, 53, 17, totient='Euler') | |
| (3233, 2753) | |
| Example using Carmichael's totient: | |
| >>> from sympy.crypto.crypto import rsa_private_key | |
| >>> rsa_private_key(61, 53, 17, totient='Carmichael') | |
| (3233, 413) | |
| index : nonnegative integer, optional | |
| Returns an arbitrary solution of a RSA private key at the index | |
| specified at `0, 1, 2, \dots`. This parameter needs to be | |
| specified along with ``totient='Carmichael'``. | |
| RSA private exponent is a non-unique solution of | |
| `e d \mod \lambda(n) = 1` and it is possible in any form of | |
| `d + k \lambda(n)`, where `d` is an another | |
| already-computed private exponent, and `\lambda` is a | |
| Carmichael's totient function, and `k` is any integer. | |
| However, considering only the positive cases, there can be | |
| a principal solution of a RSA private exponent `d_0` in | |
| `0 < d_0 < \lambda(n)`, and all the other solutions | |
| can be canonicalzed in a form of `d_0 + k \lambda(n)`. | |
| ``index`` specifies the `k` notation to yield any possible value | |
| an RSA private key can have. | |
| An example of computing any arbitrary RSA private key: | |
| >>> from sympy.crypto.crypto import rsa_private_key | |
| >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=0) | |
| (3233, 413) | |
| >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=1) | |
| (3233, 1193) | |
| >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=2) | |
| (3233, 1973) | |
| multipower : bool, optional | |
| The keyword is identical to the ``multipower`` in | |
| :meth:`rsa_public_key`. | |
| Returns | |
| ======= | |
| (n, d) : int, int | |
| `n` is a product of any arbitrary number of primes given as | |
| the argument. | |
| `d` is the inverse of `e` (mod `\phi(n)`) where `e` is the | |
| exponent given, and `\phi` is a Euler totient. | |
| False | |
| Returned if less than two arguments are given, or `e` is | |
| not relatively prime to the totient of the modulus. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import rsa_private_key | |
| A private key of a two-prime RSA: | |
| >>> p, q, e = 3, 5, 7 | |
| >>> rsa_private_key(p, q, e) | |
| (15, 7) | |
| >>> rsa_private_key(p, q, 30) | |
| False | |
| A private key of a multiprime RSA: | |
| >>> primes = [2, 3, 5, 7, 11, 13] | |
| >>> e = 7 | |
| >>> args = primes + [e] | |
| >>> rsa_private_key(*args) | |
| (30030, 823) | |
| See Also | |
| ======== | |
| rsa_public_key | |
| encipher_rsa | |
| decipher_rsa | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29 | |
| .. [2] https://cacr.uwaterloo.ca/techreports/2006/cacr2006-16.pdf | |
| .. [3] https://link.springer.com/content/pdf/10.1007/BFb0055738.pdf | |
| .. [4] https://www.itiis.org/digital-library/manuscript/1381 | |
| """ | |
| return _rsa_key(*args, public=False, private=True, **kwargs) | |
| def _encipher_decipher_rsa(i, key, factors=None): | |
| n, d = key | |
| if not factors: | |
| return pow(i, d, n) | |
| def _is_coprime_set(l): | |
| is_coprime_set = True | |
| for i in range(len(l)): | |
| for j in range(i+1, len(l)): | |
| if gcd(l[i], l[j]) != 1: | |
| is_coprime_set = False | |
| break | |
| return is_coprime_set | |
| prod = reduce(lambda i, j: i*j, factors) | |
| if prod == n and _is_coprime_set(factors): | |
| return _decipher_rsa_crt(i, d, factors) | |
| return _encipher_decipher_rsa(i, key, factors=None) | |
| def encipher_rsa(i, key, factors=None): | |
| r"""Encrypt the plaintext with RSA. | |
| Parameters | |
| ========== | |
| i : integer | |
| The plaintext to be encrypted for. | |
| key : (n, e) where n, e are integers | |
| `n` is the modulus of the key and `e` is the exponent of the | |
| key. The encryption is computed by `i^e \bmod n`. | |
| The key can either be a public key or a private key, however, | |
| the message encrypted by a public key can only be decrypted by | |
| a private key, and vice versa, as RSA is an asymmetric | |
| cryptography system. | |
| factors : list of coprime integers | |
| This is identical to the keyword ``factors`` in | |
| :meth:`decipher_rsa`. | |
| Notes | |
| ===== | |
| Some specifications may make the RSA not cryptographically | |
| meaningful. | |
| For example, `0`, `1` will remain always same after taking any | |
| number of exponentiation, thus, should be avoided. | |
| Furthermore, if `i^e < n`, `i` may easily be figured out by taking | |
| `e` th root. | |
| And also, specifying the exponent as `1` or in more generalized form | |
| as `1 + k \lambda(n)` where `k` is an nonnegative integer, | |
| `\lambda` is a carmichael totient, the RSA becomes an identity | |
| mapping. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_rsa | |
| >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key | |
| Public Key Encryption: | |
| >>> p, q, e = 3, 5, 7 | |
| >>> puk = rsa_public_key(p, q, e) | |
| >>> msg = 12 | |
| >>> encipher_rsa(msg, puk) | |
| 3 | |
| Private Key Encryption: | |
| >>> p, q, e = 3, 5, 7 | |
| >>> prk = rsa_private_key(p, q, e) | |
| >>> msg = 12 | |
| >>> encipher_rsa(msg, prk) | |
| 3 | |
| Encryption using chinese remainder theorem: | |
| >>> encipher_rsa(msg, prk, factors=[p, q]) | |
| 3 | |
| """ | |
| return _encipher_decipher_rsa(i, key, factors=factors) | |
| def decipher_rsa(i, key, factors=None): | |
| r"""Decrypt the ciphertext with RSA. | |
| Parameters | |
| ========== | |
| i : integer | |
| The ciphertext to be decrypted for. | |
| key : (n, d) where n, d are integers | |
| `n` is the modulus of the key and `d` is the exponent of the | |
| key. The decryption is computed by `i^d \bmod n`. | |
| The key can either be a public key or a private key, however, | |
| the message encrypted by a public key can only be decrypted by | |
| a private key, and vice versa, as RSA is an asymmetric | |
| cryptography system. | |
| factors : list of coprime integers | |
| As the modulus `n` created from RSA key generation is composed | |
| of arbitrary prime factors | |
| `n = {p_1}^{k_1}{p_2}^{k_2}\dots{p_n}^{k_n}` where | |
| `p_1, p_2, \dots, p_n` are distinct primes and | |
| `k_1, k_2, \dots, k_n` are positive integers, chinese remainder | |
| theorem can be used to compute `i^d \bmod n` from the | |
| fragmented modulo operations like | |
| .. math:: | |
| i^d \bmod {p_1}^{k_1}, i^d \bmod {p_2}^{k_2}, \dots, | |
| i^d \bmod {p_n}^{k_n} | |
| or like | |
| .. math:: | |
| i^d \bmod {p_1}^{k_1}{p_2}^{k_2}, | |
| i^d \bmod {p_3}^{k_3}, \dots , | |
| i^d \bmod {p_n}^{k_n} | |
| as long as every moduli does not share any common divisor each | |
| other. | |
| The raw primes used in generating the RSA key pair can be a good | |
| option. | |
| Note that the speed advantage of using this is only viable for | |
| very large cases (Like 2048-bit RSA keys) since the | |
| overhead of using pure Python implementation of | |
| :meth:`sympy.ntheory.modular.crt` may overcompensate the | |
| theoretical speed advantage. | |
| Notes | |
| ===== | |
| See the ``Notes`` section in the documentation of | |
| :meth:`encipher_rsa` | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import decipher_rsa, encipher_rsa | |
| >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key | |
| Public Key Encryption and Decryption: | |
| >>> p, q, e = 3, 5, 7 | |
| >>> prk = rsa_private_key(p, q, e) | |
| >>> puk = rsa_public_key(p, q, e) | |
| >>> msg = 12 | |
| >>> new_msg = encipher_rsa(msg, prk) | |
| >>> new_msg | |
| 3 | |
| >>> decipher_rsa(new_msg, puk) | |
| 12 | |
| Private Key Encryption and Decryption: | |
| >>> p, q, e = 3, 5, 7 | |
| >>> prk = rsa_private_key(p, q, e) | |
| >>> puk = rsa_public_key(p, q, e) | |
| >>> msg = 12 | |
| >>> new_msg = encipher_rsa(msg, puk) | |
| >>> new_msg | |
| 3 | |
| >>> decipher_rsa(new_msg, prk) | |
| 12 | |
| Decryption using chinese remainder theorem: | |
| >>> decipher_rsa(new_msg, prk, factors=[p, q]) | |
| 12 | |
| See Also | |
| ======== | |
| encipher_rsa | |
| """ | |
| return _encipher_decipher_rsa(i, key, factors=factors) | |
| #################### kid krypto (kid RSA) ############################# | |
| def kid_rsa_public_key(a, b, A, B): | |
| r""" | |
| Kid RSA is a version of RSA useful to teach grade school children | |
| since it does not involve exponentiation. | |
| Explanation | |
| =========== | |
| Alice wants to talk to Bob. Bob generates keys as follows. | |
| Key generation: | |
| * Select positive integers `a, b, A, B` at random. | |
| * Compute `M = a b - 1`, `e = A M + a`, `d = B M + b`, | |
| `n = (e d - 1)//M`. | |
| * The *public key* is `(n, e)`. Bob sends these to Alice. | |
| * The *private key* is `(n, d)`, which Bob keeps secret. | |
| Encryption: If `p` is the plaintext message then the | |
| ciphertext is `c = p e \pmod n`. | |
| Decryption: If `c` is the ciphertext message then the | |
| plaintext is `p = c d \pmod n`. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import kid_rsa_public_key | |
| >>> a, b, A, B = 3, 4, 5, 6 | |
| >>> kid_rsa_public_key(a, b, A, B) | |
| (369, 58) | |
| """ | |
| M = a*b - 1 | |
| e = A*M + a | |
| d = B*M + b | |
| n = (e*d - 1)//M | |
| return n, e | |
| def kid_rsa_private_key(a, b, A, B): | |
| """ | |
| Compute `M = a b - 1`, `e = A M + a`, `d = B M + b`, | |
| `n = (e d - 1) / M`. The *private key* is `d`, which Bob | |
| keeps secret. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import kid_rsa_private_key | |
| >>> a, b, A, B = 3, 4, 5, 6 | |
| >>> kid_rsa_private_key(a, b, A, B) | |
| (369, 70) | |
| """ | |
| M = a*b - 1 | |
| e = A*M + a | |
| d = B*M + b | |
| n = (e*d - 1)//M | |
| return n, d | |
| def encipher_kid_rsa(msg, key): | |
| """ | |
| Here ``msg`` is the plaintext and ``key`` is the public key. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... encipher_kid_rsa, kid_rsa_public_key) | |
| >>> msg = 200 | |
| >>> a, b, A, B = 3, 4, 5, 6 | |
| >>> key = kid_rsa_public_key(a, b, A, B) | |
| >>> encipher_kid_rsa(msg, key) | |
| 161 | |
| """ | |
| n, e = key | |
| return (msg*e) % n | |
| def decipher_kid_rsa(msg, key): | |
| """ | |
| Here ``msg`` is the plaintext and ``key`` is the private key. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... kid_rsa_public_key, kid_rsa_private_key, | |
| ... decipher_kid_rsa, encipher_kid_rsa) | |
| >>> a, b, A, B = 3, 4, 5, 6 | |
| >>> d = kid_rsa_private_key(a, b, A, B) | |
| >>> msg = 200 | |
| >>> pub = kid_rsa_public_key(a, b, A, B) | |
| >>> pri = kid_rsa_private_key(a, b, A, B) | |
| >>> ct = encipher_kid_rsa(msg, pub) | |
| >>> decipher_kid_rsa(ct, pri) | |
| 200 | |
| """ | |
| n, d = key | |
| return (msg*d) % n | |
| #################### Morse Code ###################################### | |
| morse_char = { | |
| ".-": "A", "-...": "B", | |
| "-.-.": "C", "-..": "D", | |
| ".": "E", "..-.": "F", | |
| "--.": "G", "....": "H", | |
| "..": "I", ".---": "J", | |
| "-.-": "K", ".-..": "L", | |
| "--": "M", "-.": "N", | |
| "---": "O", ".--.": "P", | |
| "--.-": "Q", ".-.": "R", | |
| "...": "S", "-": "T", | |
| "..-": "U", "...-": "V", | |
| ".--": "W", "-..-": "X", | |
| "-.--": "Y", "--..": "Z", | |
| "-----": "0", ".----": "1", | |
| "..---": "2", "...--": "3", | |
| "....-": "4", ".....": "5", | |
| "-....": "6", "--...": "7", | |
| "---..": "8", "----.": "9", | |
| ".-.-.-": ".", "--..--": ",", | |
| "---...": ":", "-.-.-.": ";", | |
| "..--..": "?", "-....-": "-", | |
| "..--.-": "_", "-.--.": "(", | |
| "-.--.-": ")", ".----.": "'", | |
| "-...-": "=", ".-.-.": "+", | |
| "-..-.": "/", ".--.-.": "@", | |
| "...-..-": "$", "-.-.--": "!"} | |
| char_morse = {v: k for k, v in morse_char.items()} | |
| def encode_morse(msg, sep='|', mapping=None): | |
| """ | |
| Encodes a plaintext into popular Morse Code with letters | |
| separated by ``sep`` and words by a double ``sep``. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encode_morse | |
| >>> msg = 'ATTACK RIGHT FLANK' | |
| >>> encode_morse(msg) | |
| '.-|-|-|.-|-.-.|-.-||.-.|..|--.|....|-||..-.|.-..|.-|-.|-.-' | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Morse_code | |
| """ | |
| mapping = mapping or char_morse | |
| assert sep not in mapping | |
| word_sep = 2*sep | |
| mapping[" "] = word_sep | |
| suffix = msg and msg[-1] in whitespace | |
| # normalize whitespace | |
| msg = (' ' if word_sep else '').join(msg.split()) | |
| # omit unmapped chars | |
| chars = set(''.join(msg.split())) | |
| ok = set(mapping.keys()) | |
| msg = translate(msg, None, ''.join(chars - ok)) | |
| morsestring = [] | |
| words = msg.split() | |
| for word in words: | |
| morseword = [] | |
| for letter in word: | |
| morseletter = mapping[letter] | |
| morseword.append(morseletter) | |
| word = sep.join(morseword) | |
| morsestring.append(word) | |
| return word_sep.join(morsestring) + (word_sep if suffix else '') | |
| def decode_morse(msg, sep='|', mapping=None): | |
| """ | |
| Decodes a Morse Code with letters separated by ``sep`` | |
| (default is '|') and words by `word_sep` (default is '||) | |
| into plaintext. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import decode_morse | |
| >>> mc = '--|---|...-|.||.|.-|...|-' | |
| >>> decode_morse(mc) | |
| 'MOVE EAST' | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Morse_code | |
| """ | |
| mapping = mapping or morse_char | |
| word_sep = 2*sep | |
| characterstring = [] | |
| words = msg.strip(word_sep).split(word_sep) | |
| for word in words: | |
| letters = word.split(sep) | |
| chars = [mapping[c] for c in letters] | |
| word = ''.join(chars) | |
| characterstring.append(word) | |
| rv = " ".join(characterstring) | |
| return rv | |
| #################### LFSRs ########################################## | |
| def lfsr_sequence(key, fill, n): | |
| r""" | |
| This function creates an LFSR sequence. | |
| Parameters | |
| ========== | |
| key : list | |
| A list of finite field elements, `[c_0, c_1, \ldots, c_k].` | |
| fill : list | |
| The list of the initial terms of the LFSR sequence, | |
| `[x_0, x_1, \ldots, x_k].` | |
| n | |
| Number of terms of the sequence that the function returns. | |
| Returns | |
| ======= | |
| L | |
| The LFSR sequence defined by | |
| `x_{n+1} = c_k x_n + \ldots + c_0 x_{n-k}`, for | |
| `n \leq k`. | |
| Notes | |
| ===== | |
| S. Golomb [G]_ gives a list of three statistical properties a | |
| sequence of numbers `a = \{a_n\}_{n=1}^\infty`, | |
| `a_n \in \{0,1\}`, should display to be considered | |
| "random". Define the autocorrelation of `a` to be | |
| .. math:: | |
| C(k) = C(k,a) = \lim_{N\rightarrow \infty} {1\over N}\sum_{n=1}^N (-1)^{a_n + a_{n+k}}. | |
| In the case where `a` is periodic with period | |
| `P` then this reduces to | |
| .. math:: | |
| C(k) = {1\over P}\sum_{n=1}^P (-1)^{a_n + a_{n+k}}. | |
| Assume `a` is periodic with period `P`. | |
| - balance: | |
| .. math:: | |
| \left|\sum_{n=1}^P(-1)^{a_n}\right| \leq 1. | |
| - low autocorrelation: | |
| .. math:: | |
| C(k) = \left\{ \begin{array}{cc} 1,& k = 0,\\ \epsilon, & k \ne 0. \end{array} \right. | |
| (For sequences satisfying these first two properties, it is known | |
| that `\epsilon = -1/P` must hold.) | |
| - proportional runs property: In each period, half the runs have | |
| length `1`, one-fourth have length `2`, etc. | |
| Moreover, there are as many runs of `1`'s as there are of | |
| `0`'s. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import lfsr_sequence | |
| >>> from sympy.polys.domains import FF | |
| >>> F = FF(2) | |
| >>> fill = [F(1), F(1), F(0), F(1)] | |
| >>> key = [F(1), F(0), F(0), F(1)] | |
| >>> lfsr_sequence(key, fill, 10) | |
| [1 mod 2, 1 mod 2, 0 mod 2, 1 mod 2, 0 mod 2, | |
| 1 mod 2, 1 mod 2, 0 mod 2, 0 mod 2, 1 mod 2] | |
| References | |
| ========== | |
| .. [G] Solomon Golomb, Shift register sequences, Aegean Park Press, | |
| Laguna Hills, Ca, 1967 | |
| """ | |
| if not isinstance(key, list): | |
| raise TypeError("key must be a list") | |
| if not isinstance(fill, list): | |
| raise TypeError("fill must be a list") | |
| p = key[0].modulus() | |
| F = FF(p) | |
| s = fill | |
| k = len(fill) | |
| L = [] | |
| for i in range(n): | |
| s0 = s[:] | |
| L.append(s[0]) | |
| s = s[1:k] | |
| x = sum(int(key[i]*s0[i]) for i in range(k)) | |
| s.append(F(x)) | |
| return L # use [int(x) for x in L] for int version | |
| def lfsr_autocorrelation(L, P, k): | |
| """ | |
| This function computes the LFSR autocorrelation function. | |
| Parameters | |
| ========== | |
| L | |
| A periodic sequence of elements of `GF(2)`. | |
| L must have length larger than P. | |
| P | |
| The period of L. | |
| k : int | |
| An integer `k` (`0 < k < P`). | |
| Returns | |
| ======= | |
| autocorrelation | |
| The k-th value of the autocorrelation of the LFSR L. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... lfsr_sequence, lfsr_autocorrelation) | |
| >>> from sympy.polys.domains import FF | |
| >>> F = FF(2) | |
| >>> fill = [F(1), F(1), F(0), F(1)] | |
| >>> key = [F(1), F(0), F(0), F(1)] | |
| >>> s = lfsr_sequence(key, fill, 20) | |
| >>> lfsr_autocorrelation(s, 15, 7) | |
| -1/15 | |
| >>> lfsr_autocorrelation(s, 15, 0) | |
| 1 | |
| """ | |
| if not isinstance(L, list): | |
| raise TypeError("L (=%s) must be a list" % L) | |
| P = int(P) | |
| k = int(k) | |
| L0 = L[:P] # slices makes a copy | |
| L1 = L0 + L0[:k] | |
| L2 = [(-1)**(int(L1[i]) + int(L1[i + k])) for i in range(P)] | |
| tot = sum(L2) | |
| return Rational(tot, P) | |
| def lfsr_connection_polynomial(s): | |
| """ | |
| This function computes the LFSR connection polynomial. | |
| Parameters | |
| ========== | |
| s | |
| A sequence of elements of even length, with entries in a finite | |
| field. | |
| Returns | |
| ======= | |
| C(x) | |
| The connection polynomial of a minimal LFSR yielding s. | |
| This implements the algorithm in section 3 of J. L. Massey's | |
| article [M]_. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... lfsr_sequence, lfsr_connection_polynomial) | |
| >>> from sympy.polys.domains import FF | |
| >>> F = FF(2) | |
| >>> fill = [F(1), F(1), F(0), F(1)] | |
| >>> key = [F(1), F(0), F(0), F(1)] | |
| >>> s = lfsr_sequence(key, fill, 20) | |
| >>> lfsr_connection_polynomial(s) | |
| x**4 + x + 1 | |
| >>> fill = [F(1), F(0), F(0), F(1)] | |
| >>> key = [F(1), F(1), F(0), F(1)] | |
| >>> s = lfsr_sequence(key, fill, 20) | |
| >>> lfsr_connection_polynomial(s) | |
| x**3 + 1 | |
| >>> fill = [F(1), F(0), F(1)] | |
| >>> key = [F(1), F(1), F(0)] | |
| >>> s = lfsr_sequence(key, fill, 20) | |
| >>> lfsr_connection_polynomial(s) | |
| x**3 + x**2 + 1 | |
| >>> fill = [F(1), F(0), F(1)] | |
| >>> key = [F(1), F(0), F(1)] | |
| >>> s = lfsr_sequence(key, fill, 20) | |
| >>> lfsr_connection_polynomial(s) | |
| x**3 + x + 1 | |
| References | |
| ========== | |
| .. [M] James L. Massey, "Shift-Register Synthesis and BCH Decoding." | |
| IEEE Trans. on Information Theory, vol. 15(1), pp. 122-127, | |
| Jan 1969. | |
| """ | |
| # Initialization: | |
| p = s[0].modulus() | |
| x = Symbol("x") | |
| C = 1*x**0 | |
| B = 1*x**0 | |
| m = 1 | |
| b = 1*x**0 | |
| L = 0 | |
| N = 0 | |
| while N < len(s): | |
| if L > 0: | |
| dC = Poly(C).degree() | |
| r = min(L + 1, dC + 1) | |
| coeffsC = [C.subs(x, 0)] + [C.coeff(x**i) | |
| for i in range(1, dC + 1)] | |
| d = (int(s[N]) + sum(coeffsC[i]*int(s[N - i]) | |
| for i in range(1, r))) % p | |
| if L == 0: | |
| d = int(s[N])*x**0 | |
| if d == 0: | |
| m += 1 | |
| N += 1 | |
| if d > 0: | |
| if 2*L > N: | |
| C = (C - d*((b**(p - 2)) % p)*x**m*B).expand() | |
| m += 1 | |
| N += 1 | |
| else: | |
| T = C | |
| C = (C - d*((b**(p - 2)) % p)*x**m*B).expand() | |
| L = N + 1 - L | |
| m = 1 | |
| b = d | |
| B = T | |
| N += 1 | |
| dC = Poly(C).degree() | |
| coeffsC = [C.subs(x, 0)] + [C.coeff(x**i) for i in range(1, dC + 1)] | |
| return sum(coeffsC[i] % p*x**i for i in range(dC + 1) | |
| if coeffsC[i] is not None) | |
| #################### ElGamal ############################# | |
| def elgamal_private_key(digit=10, seed=None): | |
| r""" | |
| Return three number tuple as private key. | |
| Explanation | |
| =========== | |
| Elgamal encryption is based on the mathematical problem | |
| called the Discrete Logarithm Problem (DLP). For example, | |
| `a^{b} \equiv c \pmod p` | |
| In general, if ``a`` and ``b`` are known, ``ct`` is easily | |
| calculated. If ``b`` is unknown, it is hard to use | |
| ``a`` and ``ct`` to get ``b``. | |
| Parameters | |
| ========== | |
| digit : int | |
| Minimum number of binary digits for key. | |
| Returns | |
| ======= | |
| tuple : (p, r, d) | |
| p = prime number. | |
| r = primitive root. | |
| d = random number. | |
| Notes | |
| ===== | |
| For testing purposes, the ``seed`` parameter may be set to control | |
| the output of this routine. See sympy.core.random._randrange. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import elgamal_private_key | |
| >>> from sympy.ntheory import is_primitive_root, isprime | |
| >>> a, b, _ = elgamal_private_key() | |
| >>> isprime(a) | |
| True | |
| >>> is_primitive_root(b, a) | |
| True | |
| """ | |
| randrange = _randrange(seed) | |
| p = nextprime(2**digit) | |
| return p, primitive_root(p), randrange(2, p) | |
| def elgamal_public_key(key): | |
| r""" | |
| Return three number tuple as public key. | |
| Parameters | |
| ========== | |
| key : (p, r, e) | |
| Tuple generated by ``elgamal_private_key``. | |
| Returns | |
| ======= | |
| tuple : (p, r, e) | |
| `e = r**d \bmod p` | |
| `d` is a random number in private key. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import elgamal_public_key | |
| >>> elgamal_public_key((1031, 14, 636)) | |
| (1031, 14, 212) | |
| """ | |
| p, r, e = key | |
| return p, r, pow(r, e, p) | |
| def encipher_elgamal(i, key, seed=None): | |
| r""" | |
| Encrypt message with public key. | |
| Explanation | |
| =========== | |
| ``i`` is a plaintext message expressed as an integer. | |
| ``key`` is public key (p, r, e). In order to encrypt | |
| a message, a random number ``a`` in ``range(2, p)`` | |
| is generated and the encryped message is returned as | |
| `c_{1}` and `c_{2}` where: | |
| `c_{1} \equiv r^{a} \pmod p` | |
| `c_{2} \equiv m e^{a} \pmod p` | |
| Parameters | |
| ========== | |
| msg | |
| int of encoded message. | |
| key | |
| Public key. | |
| Returns | |
| ======= | |
| tuple : (c1, c2) | |
| Encipher into two number. | |
| Notes | |
| ===== | |
| For testing purposes, the ``seed`` parameter may be set to control | |
| the output of this routine. See sympy.core.random._randrange. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_elgamal, elgamal_private_key, elgamal_public_key | |
| >>> pri = elgamal_private_key(5, seed=[3]); pri | |
| (37, 2, 3) | |
| >>> pub = elgamal_public_key(pri); pub | |
| (37, 2, 8) | |
| >>> msg = 36 | |
| >>> encipher_elgamal(msg, pub, seed=[3]) | |
| (8, 6) | |
| """ | |
| p, r, e = key | |
| if i < 0 or i >= p: | |
| raise ValueError( | |
| 'Message (%s) should be in range(%s)' % (i, p)) | |
| randrange = _randrange(seed) | |
| a = randrange(2, p) | |
| return pow(r, a, p), i*pow(e, a, p) % p | |
| def decipher_elgamal(msg, key): | |
| r""" | |
| Decrypt message with private key. | |
| `msg = (c_{1}, c_{2})` | |
| `key = (p, r, d)` | |
| According to extended Eucliden theorem, | |
| `u c_{1}^{d} + p n = 1` | |
| `u \equiv 1/{{c_{1}}^d} \pmod p` | |
| `u c_{2} \equiv \frac{1}{c_{1}^d} c_{2} \equiv \frac{1}{r^{ad}} c_{2} \pmod p` | |
| `\frac{1}{r^{ad}} m e^a \equiv \frac{1}{r^{ad}} m {r^{d a}} \equiv m \pmod p` | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import decipher_elgamal | |
| >>> from sympy.crypto.crypto import encipher_elgamal | |
| >>> from sympy.crypto.crypto import elgamal_private_key | |
| >>> from sympy.crypto.crypto import elgamal_public_key | |
| >>> pri = elgamal_private_key(5, seed=[3]) | |
| >>> pub = elgamal_public_key(pri); pub | |
| (37, 2, 8) | |
| >>> msg = 17 | |
| >>> decipher_elgamal(encipher_elgamal(msg, pub), pri) == msg | |
| True | |
| """ | |
| p, _, d = key | |
| c1, c2 = msg | |
| u = pow(c1, -d, p) | |
| return u * c2 % p | |
| ################ Diffie-Hellman Key Exchange ######################### | |
| def dh_private_key(digit=10, seed=None): | |
| r""" | |
| Return three integer tuple as private key. | |
| Explanation | |
| =========== | |
| Diffie-Hellman key exchange is based on the mathematical problem | |
| called the Discrete Logarithm Problem (see ElGamal). | |
| Diffie-Hellman key exchange is divided into the following steps: | |
| * Alice and Bob agree on a base that consist of a prime ``p`` | |
| and a primitive root of ``p`` called ``g`` | |
| * Alice choses a number ``a`` and Bob choses a number ``b`` where | |
| ``a`` and ``b`` are random numbers in range `[2, p)`. These are | |
| their private keys. | |
| * Alice then publicly sends Bob `g^{a} \pmod p` while Bob sends | |
| Alice `g^{b} \pmod p` | |
| * They both raise the received value to their secretly chosen | |
| number (``a`` or ``b``) and now have both as their shared key | |
| `g^{ab} \pmod p` | |
| Parameters | |
| ========== | |
| digit | |
| Minimum number of binary digits required in key. | |
| Returns | |
| ======= | |
| tuple : (p, g, a) | |
| p = prime number. | |
| g = primitive root of p. | |
| a = random number from 2 through p - 1. | |
| Notes | |
| ===== | |
| For testing purposes, the ``seed`` parameter may be set to control | |
| the output of this routine. See sympy.core.random._randrange. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import dh_private_key | |
| >>> from sympy.ntheory import isprime, is_primitive_root | |
| >>> p, g, _ = dh_private_key() | |
| >>> isprime(p) | |
| True | |
| >>> is_primitive_root(g, p) | |
| True | |
| >>> p, g, _ = dh_private_key(5) | |
| >>> isprime(p) | |
| True | |
| >>> is_primitive_root(g, p) | |
| True | |
| """ | |
| p = nextprime(2**digit) | |
| g = primitive_root(p) | |
| randrange = _randrange(seed) | |
| a = randrange(2, p) | |
| return p, g, a | |
| def dh_public_key(key): | |
| r""" | |
| Return three number tuple as public key. | |
| This is the tuple that Alice sends to Bob. | |
| Parameters | |
| ========== | |
| key : (p, g, a) | |
| A tuple generated by ``dh_private_key``. | |
| Returns | |
| ======= | |
| tuple : int, int, int | |
| A tuple of `(p, g, g^a \mod p)` with `p`, `g` and `a` given as | |
| parameters.s | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import dh_private_key, dh_public_key | |
| >>> p, g, a = dh_private_key(); | |
| >>> _p, _g, x = dh_public_key((p, g, a)) | |
| >>> p == _p and g == _g | |
| True | |
| >>> x == pow(g, a, p) | |
| True | |
| """ | |
| p, g, a = key | |
| return p, g, pow(g, a, p) | |
| def dh_shared_key(key, b): | |
| """ | |
| Return an integer that is the shared key. | |
| This is what Bob and Alice can both calculate using the public | |
| keys they received from each other and their private keys. | |
| Parameters | |
| ========== | |
| key : (p, g, x) | |
| Tuple `(p, g, x)` generated by ``dh_public_key``. | |
| b | |
| Random number in the range of `2` to `p - 1` | |
| (Chosen by second key exchange member (Bob)). | |
| Returns | |
| ======= | |
| int | |
| A shared key. | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import ( | |
| ... dh_private_key, dh_public_key, dh_shared_key) | |
| >>> prk = dh_private_key(); | |
| >>> p, g, x = dh_public_key(prk); | |
| >>> sk = dh_shared_key((p, g, x), 1000) | |
| >>> sk == pow(x, 1000, p) | |
| True | |
| """ | |
| p, _, x = key | |
| if 1 >= b or b >= p: | |
| raise ValueError(filldedent(''' | |
| Value of b should be greater 1 and less | |
| than prime %s.''' % p)) | |
| return pow(x, b, p) | |
| ################ Goldwasser-Micali Encryption ######################### | |
| def _legendre(a, p): | |
| """ | |
| Returns the legendre symbol of a and p | |
| assuming that p is a prime. | |
| i.e. 1 if a is a quadratic residue mod p | |
| -1 if a is not a quadratic residue mod p | |
| 0 if a is divisible by p | |
| Parameters | |
| ========== | |
| a : int | |
| The number to test. | |
| p : prime | |
| The prime to test ``a`` against. | |
| Returns | |
| ======= | |
| int | |
| Legendre symbol (a / p). | |
| """ | |
| sig = pow(a, (p - 1)//2, p) | |
| if sig == 1: | |
| return 1 | |
| elif sig == 0: | |
| return 0 | |
| else: | |
| return -1 | |
| def _random_coprime_stream(n, seed=None): | |
| randrange = _randrange(seed) | |
| while True: | |
| y = randrange(n) | |
| if gcd(y, n) == 1: | |
| yield y | |
| def gm_private_key(p, q, a=None): | |
| r""" | |
| Check if ``p`` and ``q`` can be used as private keys for | |
| the Goldwasser-Micali encryption. The method works | |
| roughly as follows. | |
| Explanation | |
| =========== | |
| #. Pick two large primes $p$ and $q$. | |
| #. Call their product $N$. | |
| #. Given a message as an integer $i$, write $i$ in its bit representation $b_0, \dots, b_n$. | |
| #. For each $k$, | |
| if $b_k = 0$: | |
| let $a_k$ be a random square | |
| (quadratic residue) modulo $p q$ | |
| such that ``jacobi_symbol(a, p*q) = 1`` | |
| if $b_k = 1$: | |
| let $a_k$ be a random non-square | |
| (non-quadratic residue) modulo $p q$ | |
| such that ``jacobi_symbol(a, p*q) = 1`` | |
| returns $\left[a_1, a_2, \dots\right]$ | |
| $b_k$ can be recovered by checking whether or not | |
| $a_k$ is a residue. And from the $b_k$'s, the message | |
| can be reconstructed. | |
| The idea is that, while ``jacobi_symbol(a, p*q)`` | |
| can be easily computed (and when it is equal to $-1$ will | |
| tell you that $a$ is not a square mod $p q$), quadratic | |
| residuosity modulo a composite number is hard to compute | |
| without knowing its factorization. | |
| Moreover, approximately half the numbers coprime to $p q$ have | |
| :func:`~.jacobi_symbol` equal to $1$ . And among those, approximately half | |
| are residues and approximately half are not. This maximizes the | |
| entropy of the code. | |
| Parameters | |
| ========== | |
| p, q, a | |
| Initialization variables. | |
| Returns | |
| ======= | |
| tuple : (p, q) | |
| The input value ``p`` and ``q``. | |
| Raises | |
| ====== | |
| ValueError | |
| If ``p`` and ``q`` are not distinct odd primes. | |
| """ | |
| if p == q: | |
| raise ValueError("expected distinct primes, " | |
| "got two copies of %i" % p) | |
| elif not isprime(p) or not isprime(q): | |
| raise ValueError("first two arguments must be prime, " | |
| "got %i of %i" % (p, q)) | |
| elif p == 2 or q == 2: | |
| raise ValueError("first two arguments must not be even, " | |
| "got %i of %i" % (p, q)) | |
| return p, q | |
| def gm_public_key(p, q, a=None, seed=None): | |
| """ | |
| Compute public keys for ``p`` and ``q``. | |
| Note that in Goldwasser-Micali Encryption, | |
| public keys are randomly selected. | |
| Parameters | |
| ========== | |
| p, q, a : int, int, int | |
| Initialization variables. | |
| Returns | |
| ======= | |
| tuple : (a, N) | |
| ``a`` is the input ``a`` if it is not ``None`` otherwise | |
| some random integer coprime to ``p`` and ``q``. | |
| ``N`` is the product of ``p`` and ``q``. | |
| """ | |
| p, q = gm_private_key(p, q) | |
| N = p * q | |
| if a is None: | |
| randrange = _randrange(seed) | |
| while True: | |
| a = randrange(N) | |
| if _legendre(a, p) == _legendre(a, q) == -1: | |
| break | |
| else: | |
| if _legendre(a, p) != -1 or _legendre(a, q) != -1: | |
| return False | |
| return (a, N) | |
| def encipher_gm(i, key, seed=None): | |
| """ | |
| Encrypt integer 'i' using public_key 'key' | |
| Note that gm uses random encryption. | |
| Parameters | |
| ========== | |
| i : int | |
| The message to encrypt. | |
| key : (a, N) | |
| The public key. | |
| Returns | |
| ======= | |
| list : list of int | |
| The randomized encrypted message. | |
| """ | |
| if i < 0: | |
| raise ValueError( | |
| "message must be a non-negative " | |
| "integer: got %d instead" % i) | |
| a, N = key | |
| bits = [] | |
| while i > 0: | |
| bits.append(i % 2) | |
| i //= 2 | |
| gen = _random_coprime_stream(N, seed) | |
| rev = reversed(bits) | |
| encode = lambda b: next(gen)**2*pow(a, b) % N | |
| return [ encode(b) for b in rev ] | |
| def decipher_gm(message, key): | |
| """ | |
| Decrypt message 'message' using public_key 'key'. | |
| Parameters | |
| ========== | |
| message : list of int | |
| The randomized encrypted message. | |
| key : (p, q) | |
| The private key. | |
| Returns | |
| ======= | |
| int | |
| The encrypted message. | |
| """ | |
| p, q = key | |
| res = lambda m, p: _legendre(m, p) > 0 | |
| bits = [res(m, p) * res(m, q) for m in message] | |
| m = 0 | |
| for b in bits: | |
| m <<= 1 | |
| m += not b | |
| return m | |
| ########### RailFence Cipher ############# | |
| def encipher_railfence(message,rails): | |
| """ | |
| Performs Railfence Encryption on plaintext and returns ciphertext | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import encipher_railfence | |
| >>> message = "hello world" | |
| >>> encipher_railfence(message,3) | |
| 'horel ollwd' | |
| Parameters | |
| ========== | |
| message : string, the message to encrypt. | |
| rails : int, the number of rails. | |
| Returns | |
| ======= | |
| The Encrypted string message. | |
| References | |
| ========== | |
| .. [1] https://en.wikipedia.org/wiki/Rail_fence_cipher | |
| """ | |
| r = list(range(rails)) | |
| p = cycle(r + r[-2:0:-1]) | |
| return ''.join(sorted(message, key=lambda i: next(p))) | |
| def decipher_railfence(ciphertext,rails): | |
| """ | |
| Decrypt the message using the given rails | |
| Examples | |
| ======== | |
| >>> from sympy.crypto.crypto import decipher_railfence | |
| >>> decipher_railfence("horel ollwd",3) | |
| 'hello world' | |
| Parameters | |
| ========== | |
| message : string, the message to encrypt. | |
| rails : int, the number of rails. | |
| Returns | |
| ======= | |
| The Decrypted string message. | |
| """ | |
| r = list(range(rails)) | |
| p = cycle(r + r[-2:0:-1]) | |
| idx = sorted(range(len(ciphertext)), key=lambda i: next(p)) | |
| res = [''] * len(ciphertext) | |
| for i, c in zip(idx, ciphertext): | |
| res[i] = c | |
| return ''.join(res) | |
| ################ Blum-Goldwasser cryptosystem ######################### | |
| def bg_private_key(p, q): | |
| """ | |
| Check if p and q can be used as private keys for | |
| the Blum-Goldwasser cryptosystem. | |
| Explanation | |
| =========== | |
| The three necessary checks for p and q to pass | |
| so that they can be used as private keys: | |
| 1. p and q must both be prime | |
| 2. p and q must be distinct | |
| 3. p and q must be congruent to 3 mod 4 | |
| Parameters | |
| ========== | |
| p, q | |
| The keys to be checked. | |
| Returns | |
| ======= | |
| p, q | |
| Input values. | |
| Raises | |
| ====== | |
| ValueError | |
| If p and q do not pass the above conditions. | |
| """ | |
| if not isprime(p) or not isprime(q): | |
| raise ValueError("the two arguments must be prime, " | |
| "got %i and %i" %(p, q)) | |
| elif p == q: | |
| raise ValueError("the two arguments must be distinct, " | |
| "got two copies of %i. " %p) | |
| elif (p - 3) % 4 != 0 or (q - 3) % 4 != 0: | |
| raise ValueError("the two arguments must be congruent to 3 mod 4, " | |
| "got %i and %i" %(p, q)) | |
| return p, q | |
| def bg_public_key(p, q): | |
| """ | |
| Calculates public keys from private keys. | |
| Explanation | |
| =========== | |
| The function first checks the validity of | |
| private keys passed as arguments and | |
| then returns their product. | |
| Parameters | |
| ========== | |
| p, q | |
| The private keys. | |
| Returns | |
| ======= | |
| N | |
| The public key. | |
| """ | |
| p, q = bg_private_key(p, q) | |
| N = p * q | |
| return N | |
| def encipher_bg(i, key, seed=None): | |
| """ | |
| Encrypts the message using public key and seed. | |
| Explanation | |
| =========== | |
| ALGORITHM: | |
| 1. Encodes i as a string of L bits, m. | |
| 2. Select a random element r, where 1 < r < key, and computes | |
| x = r^2 mod key. | |
| 3. Use BBS pseudo-random number generator to generate L random bits, b, | |
| using the initial seed as x. | |
| 4. Encrypted message, c_i = m_i XOR b_i, 1 <= i <= L. | |
| 5. x_L = x^(2^L) mod key. | |
| 6. Return (c, x_L) | |
| Parameters | |
| ========== | |
| i | |
| Message, a non-negative integer | |
| key | |
| The public key | |
| Returns | |
| ======= | |
| Tuple | |
| (encrypted_message, x_L) | |
| Raises | |
| ====== | |
| ValueError | |
| If i is negative. | |
| """ | |
| if i < 0: | |
| raise ValueError( | |
| "message must be a non-negative " | |
| "integer: got %d instead" % i) | |
| enc_msg = [] | |
| while i > 0: | |
| enc_msg.append(i % 2) | |
| i //= 2 | |
| enc_msg.reverse() | |
| L = len(enc_msg) | |
| r = _randint(seed)(2, key - 1) | |
| x = r**2 % key | |
| x_L = pow(int(x), int(2**L), int(key)) | |
| rand_bits = [] | |
| for _ in range(L): | |
| rand_bits.append(x % 2) | |
| x = x**2 % key | |
| encrypt_msg = [m ^ b for (m, b) in zip(enc_msg, rand_bits)] | |
| return (encrypt_msg, x_L) | |
| def decipher_bg(message, key): | |
| """ | |
| Decrypts the message using private keys. | |
| Explanation | |
| =========== | |
| ALGORITHM: | |
| 1. Let, c be the encrypted message, y the second number received, | |
| and p and q be the private keys. | |
| 2. Compute, r_p = y^((p+1)/4 ^ L) mod p and | |
| r_q = y^((q+1)/4 ^ L) mod q. | |
| 3. Compute x_0 = (q(q^-1 mod p)r_p + p(p^-1 mod q)r_q) mod N. | |
| 4. From, recompute the bits using the BBS generator, as in the | |
| encryption algorithm. | |
| 5. Compute original message by XORing c and b. | |
| Parameters | |
| ========== | |
| message | |
| Tuple of encrypted message and a non-negative integer. | |
| key | |
| Tuple of private keys. | |
| Returns | |
| ======= | |
| orig_msg | |
| The original message | |
| """ | |
| p, q = key | |
| encrypt_msg, y = message | |
| public_key = p * q | |
| L = len(encrypt_msg) | |
| p_t = ((p + 1)/4)**L | |
| q_t = ((q + 1)/4)**L | |
| r_p = pow(int(y), int(p_t), int(p)) | |
| r_q = pow(int(y), int(q_t), int(q)) | |
| x = (q * invert(q, p) * r_p + p * invert(p, q) * r_q) % public_key | |
| orig_bits = [] | |
| for _ in range(L): | |
| orig_bits.append(x % 2) | |
| x = x**2 % public_key | |
| orig_msg = 0 | |
| for (m, b) in zip(encrypt_msg, orig_bits): | |
| orig_msg = orig_msg * 2 | |
| orig_msg += (m ^ b) | |
| return orig_msg | |