| """ |
| 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 string |
| 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() + string.digits |
| 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))) |
| |
| 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)) |
|
|
|
|
| |
|
|
|
|
| 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) |
|
|
| |
|
|
|
|
| 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) |
|
|
| |
|
|
|
|
| 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) |
|
|
|
|
| |
| |
| |
|
|
| 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) |
| 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 |
|
|
|
|
| |
|
|
|
|
| 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 |
|
|
|
|
| |
|
|
|
|
| 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] |
|
|
| |
| 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] |
|
|
| |
| 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) |
|
|
|
|
| |
|
|
| 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) |
| |
| |
| ).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) |
|
|
|
|
| |
|
|
|
|
| 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_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 |
|
|
| |
| msg = (' ' if word_sep else '').join(msg.split()) |
| |
| 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 |
|
|
|
|
| |
|
|
|
|
| @doctest_depends_on(ground_types=['python', 'gmpy']) |
| 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 |
|
|
|
|
| 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] |
| 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. |
| |
| """ |
| |
| 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) |
|
|
|
|
| |
|
|
|
|
| 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 encrypted 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 |
|
|
|
|
| |
|
|
| 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) |
|
|
|
|
| |
|
|
|
|
| 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 |
|
|
|
|
|
|
| |
|
|
| 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) |
|
|
|
|
| |
|
|
| 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 |
|
|