Repository: sympy/sympy

Issue:
Extend vigenère in crypto to accept repetitions in keys
In line 369 of function `encipher_vigenere`, which is part of `crypto.py` the method `uniq` from utilities.iterables is used to return an iterator of the key-phrase without possible spaces in the key. However, `uniq` will return an iterator with only unique characters of the key. Therefore, if one uses a key such as "cool", `uniq` returns "col" and the function will hence encrypt in a wrong way.

Example: `encipher_vigenere("wow", "cool")` returns "YCH". However, the right result would actually be "YCK".

Also, instead of using `capitalize` on each character of the key or the plaintext (see lines 370 or 373) we could also simplify the problem by using `key.upper` or `pt.upper`.

I would volunteer to fix this and if I am assuming right and `uniq` is used wrong also in other crypto functions such as `encipher_bifid5` or `bifid5_square` I could also fix these functions.

Discussion:
smichr: > Why does one have to import AZ here

no longer needed. In latter commits I clean up arg handling. I see that I have some more cleanup of doctests to do. Also, the `rst` file needs updating to clear the testing error.
smichr: Laguna has to be indented, apparently.

Patch:
diff --git a/doc/src/modules/crypto.rst b/doc/src/modules/crypto.rst
index 808928d034..54a6fdcb7b 100644
--- a/doc/src/modules/crypto.rst
+++ b/doc/src/modules/crypto.rst
@@ -1,29 +1,52 @@
 Basic Cryptography Module
 =========================
 
-Included in this module are both block ciphers and stream ciphers.
+Encryption is the process of hiding a message and a cipher is a
+means of doing so. Included in this module are both block and stream
+ciphers:
 
  * Shift cipher
  * Affine cipher
- * Bifid ciphers
- * Vigenere's cipher
  * substitution ciphers
+ * Vigenere's cipher
  * Hill's cipher
+ * Bifid ciphers
  * RSA
  * Kid RSA
- * linear feedback shift registers (a stream cipher)
+ * linear-feedback shift registers (for stream ciphers)
  * ElGamal encryption
 
+In a *substitution cipher* "units" (not necessarily single characters)
+of plaintext are replaced with ciphertext according to a regular system.
+
+A *transposition cipher* is a method of encryption by which
+the positions held by "units" of plaintext are replaced by a
+permutation of the plaintext. That is, the order of the units is
+changed using a bijective function on the position of the characters
+to perform the encryption.
+
+A *monoalphabetic cipher* uses fixed substitution over the entire
+message, whereas a *polyalphabetic cipher* uses a number of
+substitutions at different times in the message.
+
 .. module:: sympy.crypto.crypto
 
-.. autofunction:: alphabet_of_cipher
+.. autofunction:: AZ
+
+.. autofunction:: padded_key
+
+.. autofunction:: check_and_join
 
 .. autofunction:: cycle_list
 
 .. autofunction:: encipher_shift
 
+.. autofunction:: decipher_shift
+
 .. autofunction:: encipher_affine
 
+.. autofunction:: decipher_affine
+
 .. autofunction:: encipher_substitution
 
 .. autofunction:: encipher_vigenere
@@ -34,6 +57,12 @@ Included in this module are both block ciphers and stream ciphers.
 
 .. autofunction:: decipher_hill
 
+.. autofunction:: encipher_bifid
+
+.. autofunction:: decipher_bifid
+
+.. autofunction:: bifid5_square
+
 .. autofunction:: encipher_bifid5
 
 .. autofunction:: decipher_bifid5
@@ -46,10 +75,6 @@ Included in this module are both block ciphers and stream ciphers.
 
 .. autofunction:: bifid6_square
 
-.. autofunction:: encipher_bifid7
-
-.. autofunction:: bifid7_square
-
 .. autofunction:: rsa_public_key
 
 .. autofunction:: rsa_private_key
@@ -83,3 +108,13 @@ Included in this module are both block ciphers and stream ciphers.
 .. autofunction:: encipher_elgamal
 
 .. autofunction:: decipher_elgamal
+
+.. autofunction:: dh_public_key
+
+.. autofunction:: dh_private_key
+
+.. autofunction:: dh_shared_key
+
+.. autofunction:: encipher_elgamal
+
+.. autofunction:: decipher_elgamal
diff --git a/sympy/core/compatibility.py b/sympy/core/compatibility.py
index 251a7db5a6..702a02cfc6 100644
--- a/sympy/core/compatibility.py
+++ b/sympy/core/compatibility.py
@@ -327,6 +327,13 @@ def __ne__(self, other):
 except ImportError: # <= Python 2.7
     from itertools import izip_longest as zip_longest
 
+
+try:
+    from string import maketrans
+except ImportError:
+    maketrans = str.maketrans
+
+
 try:
     from itertools import combinations_with_replacement
 except ImportError:  # <= Python 2.6
diff --git a/sympy/crypto/__init__.py b/sympy/crypto/__init__.py
index 5b8bb665ad..4b19d5fb7e 100644
--- a/sympy/crypto/__init__.py
+++ b/sympy/crypto/__init__.py
@@ -1,12 +1,13 @@
-from sympy.crypto.crypto import (alphabet_of_cipher, cycle_list,
-      encipher_shift, encipher_affine, encipher_substitution,
-      encipher_vigenere, decipher_vigenere,
-      bifid5_square, bifid6_square, bifid7_square,
-      encipher_hill, decipher_hill, encipher_bifid5, encipher_bifid6,
-      encipher_bifid7, decipher_bifid5, decipher_bifid6, encipher_kid_rsa,
-      decipher_kid_rsa, kid_rsa_private_key, kid_rsa_public_key,
-      decipher_rsa, rsa_private_key, rsa_public_key, encipher_rsa,
-      lfsr_connection_polynomial, lfsr_autocorrelation, lfsr_sequence,
-      encode_morse, decode_morse, elgamal_private_key, elgamal_public_key,
-      decipher_elgamal, encipher_elgamal, dh_private_key, dh_public_key,
-      dh_shared_key)
+from sympy.crypto.crypto import (cycle_list,
+        encipher_shift, encipher_affine, encipher_substitution,
+        check_and_join, encipher_vigenere, decipher_vigenere, bifid5_square,
+        bifid6_square, encipher_hill, decipher_hill,
+        encipher_bifid5, encipher_bifid6, decipher_bifid5,
+        decipher_bifid6, encipher_kid_rsa, decipher_kid_rsa,
+        kid_rsa_private_key, kid_rsa_public_key, decipher_rsa, rsa_private_key,
+        rsa_public_key, encipher_rsa, lfsr_connection_polynomial,
+        lfsr_autocorrelation, lfsr_sequence, encode_morse, decode_morse,
+        elgamal_private_key, elgamal_public_key, decipher_elgamal,
+        encipher_elgamal, dh_private_key, dh_public_key, dh_shared_key,
+        padded_key, encipher_bifid, decipher_bifid, bifid_square, bifid5,
+        bifid6, bifid10)
diff --git a/sympy/crypto/crypto.py b/sympy/crypto/crypto.py
index ee55da732e..2b2968e4f3 100644
--- a/sympy/crypto/crypto.py
+++ b/sympy/crypto/crypto.py
@@ -1,102 +1,172 @@
 # -*- coding: utf-8 -*-
 
 """
-Classical ciphers and LFSRs
+This file contains some classical ciphers and routines
+implementing a linear-feedback shift register (LFSR)
+and the Diffie-Hellman key exchange.
 """
 
 from __future__ import print_function
 
-from random import randrange
+from string import whitespace, ascii_uppercase as uppercase, printable
 
 from sympy import nextprime
 from sympy.core import Rational, S, Symbol
-from sympy.core.numbers import igcdex
+from sympy.core.numbers import igcdex, mod_inverse
 from sympy.core.compatibility import range
 from sympy.matrices import Matrix
 from sympy.ntheory import isprime, totient, primitive_root
 from sympy.polys.domains import FF
-from sympy.polys.polytools import gcd, Poly, invert
+from sympy.polys.polytools import gcd, Poly
+from sympy.utilities.misc import filldedent, translate
 from sympy.utilities.iterables import flatten, uniq
+from sympy.utilities.randtest import _randrange
 
+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.
 
-def alphabet_of_cipher(symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
-    """
-    Returns the list of characters in the string input defining the alphabet.
+    Examples
+    ========
 
-    Notes
-    =====
+    >>> from sympy.crypto.crypto import AZ
+    >>> AZ('Hello, world!')
+    'HELLOWORLD'
+    >>> AZ('Hello, world!'.split())
+    ['HELLO', 'WORLD']
 
-    First, some basic definitions.
+    See Also
+    ========
+    check_and_join
+    """
+    if not s:
+        return uppercase
+    t = type(s) is 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, filter=True):
+    """Return a string of the distinct characters of ``symbols`` with
+    those of ``key`` appearing first, omitting characters in ``key``
+    that are not in ``symbols``. A ValueError is raised if a) there are
+    duplicate characters in ``symbols`` or b) there are characters
+    in ``key`` that are  not in ``symbols``.
 
-    A *substitution cipher* is a method of encryption by which
-    "units" (not necessarily characters) of plaintext are replaced with
-    ciphertext according to a regular system. The "units" may be
-    characters (ie, words of length `1`), words of length `2`, and so forth.
+    Examples
+    ========
 
-    A *transposition cipher* is a method of encryption by which
-    the positions held by "units" of plaintext are replaced by a
-    permutation of the plaintext. That is, the order of the units is
-    changed using a bijective function on the characters' positions
-    to perform the encryption.
+    >>> 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(set(
+            [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 + ''.join([i for i in syms if i not in 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``.
 
-    A *monoalphabetic cipher* uses fixed substitution over the entire
-    message, whereas a *polyalphabetic cipher* uses a number of substitutions
-    at different times in the message.
+    Parameters
+    ==========
 
-    Each of these ciphers require an alphabet for the messages to be
-    constructed from.
+    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 alphabet_of_cipher
-    >>> alphabet_of_cipher()
-    ['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']
-    >>> L = [str(i) for i in range(10)] + ['a', 'b', 'c']; L
-    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c']
-    >>> A = "".join(L); A
-    '0123456789abc'
-    >>> alphabet_of_cipher(A)
-    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c']
-    >>> alphabet_of_cipher()
-    ['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']
+    >>> 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"
 
     """
-    symbols = "".join(symbols)
-    return list(symbols)
-
-
-######## shift cipher examples ############
+    rv = ''.join(''.join(phrase))
+    if symbols is not None:
+        symbols = check_and_join(symbols)
+        missing = ''.join(list(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 cyclic shift of the list range(n) by k.
+    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, alphabet_of_cipher
-    >>> L = cycle_list(3,26); L
-    [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
-    17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 1, 2]
-    >>> A = alphabet_of_cipher(); A
-    ['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']
-    >>> [A[i] for i in L]
-    ['D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
-     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C']
+    >>> from sympy.crypto.crypto import cycle_list
+    >>> cycle_list(3, 10)
+    [3, 4, 5, 6, 7, 8, 9, 0, 1, 2]
 
     """
-    L = list(range(n))
-    return L[k:] + L[:k]
+    k = k % n
+    return list(range(k, n)) + list(range(k))
+
+
+######## shift cipher examples ############
 
 
-def encipher_shift(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+def encipher_shift(msg, key, symbols=None):
     """
-    Performs shift cipher encryption on plaintext pt, and returns the ciphertext.
+    Performs shift cipher encryption on plaintext msg, and returns the
+    ciphertext.
 
     Notes
     =====
@@ -104,7 +174,7 @@ def encipher_shift(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
     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 reportedtly used a similar cipher, but
+    Caesar's nephew Augustus reportedly used a similar cipher, but
     with a right shift of 1.
 
 
@@ -112,154 +182,249 @@ def encipher_shift(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
 
         INPUT:
 
-            ``k``: an integer from 0 to 25 (the secret key)
+            ``key``: an integer (the secret key)
 
-            ``m``: string of upper-case letters (the plaintext message)
+            ``msg``: plaintext of upper-case letters
 
         OUTPUT:
 
-            ``c``: string of upper-case letters (the ciphertext message)
+            ``ct``: ciphertext of upper-case letters
 
         STEPS:
-            0. Identify the alphabet A, ..., Z with the integers 0, ..., 25.
-            1. Compute from the string ``m`` a list ``L1`` of corresponding
-               integers.
+            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 ``c`` of corresponding
-               letters.
+            3. Compute from the list ``L2`` a string ``ct`` of
+               corresponding letters.
 
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import encipher_shift
-    >>> pt = "GONAVYBEATARMY"
-    >>> encipher_shift(pt, 1)
+    >>> from sympy.crypto.crypto import encipher_shift, decipher_shift
+    >>> msg = "GONAVYBEATARMY"
+    >>> ct = encipher_shift(msg, 1); ct
     'HPOBWZCFBUBSNZ'
-    >>> encipher_shift(pt, 0)
+
+    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'
-    >>> encipher_shift(pt, -1)
-    'FNMZUXADZSZQLX'
+    """
+    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):
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
-    n = len(A)
-    L = cycle_list(key, n)
-    C = [A[(A.index(pt[i]) + key) % n] for i in range(len(pt))]
-    return "".join(C)
+    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)
 
 
 ######## affine cipher examples ############
 
 
-def encipher_affine(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+def encipher_affine(msg, key, symbols=None, _inverse=False):
     r"""
-    Performs the affine cipher encryption on plaintext ``pt``, and returns the ciphertext.
+    Performs the affine cipher encryption on plaintext ``msg``, and
+    returns the ciphertext.
 
-    Encryption is based on the map `x \rightarrow ax+b` (mod `26`). Decryption is based on
-    the map `x \rightarrow cx+d` (mod `26`), where `c = a^{-1}` (mod `26`) and
-    `d = -a^{-1}c` (mod `26`). (In particular, for the map to be invertible,
-    we need `\mathrm{gcd}(a, 26) = 1.`)
+    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.
 
     Notes
     =====
 
-    This is a straightforward generalization of the shift cipher.
+    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.
 
     ALGORITHM:
 
         INPUT:
 
-            ``a, b``: a pair integers, where ``gcd(a, 26) = 1`` (the secret key)
+            ``msg``: string of characters that appear in ``symbols``
 
-            ``m``: string of upper-case letters (the plaintext message)
+            ``a, b``: 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 charactes are ignored.
 
         OUTPUT:
 
-            ``c``: string of upper-case letters (the ciphertext message)
+            ``ct``: string of characters (the ciphertext message)
 
         STEPS:
-            0. Identify the alphabet "A", ..., "Z" with the integers 0, ..., 25.
-            1. Compute from the string ``m`` 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 26)``, for each element ``x`` in ``L1``.
-            3. Compute from the list ``L2`` a string ``c`` of corresponding
-               letters.
+            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.
+
+    See Also
+    ========
+    decipher_affine
+
+    """
+    msg, _, A = _prep(msg, '', symbols)
+    N = len(A)
+    a, b = key
+    assert gcd(a, N) == 1
+    if _inverse:
+        c = mod_inverse(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
-    >>> pt = "GONAVYBEATARMY"
-    >>> encipher_affine(pt, (1, 1))
-    'HPOBWZCFBUBSNZ'
-    >>> encipher_affine(pt, (1, 0))
-    'GONAVYBEATARMY'
-    >>> pt = "GONAVYBEATARMY"
-    >>> encipher_affine(pt, (3, 1))
+    >>> from sympy.crypto.crypto import encipher_affine, decipher_affine
+    >>> msg = "GO NAVY BEAT ARMY"
+    >>> key = (3, 1)
+    >>> encipher_affine(msg, key)
     'TROBMVENBGBALV'
-    >>> ct = "TROBMVENBGBALV"
-    >>> encipher_affine(ct, (9, 17))
+    >>> decipher_affine(_, key)
     'GONAVYBEATARMY'
 
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
-    n = len(A)
-    k1 = key[0] # multiplicative coeff "a"
-    k2 = key[1] # additive coeff "b"
-    L = cycle_list(k2, n)
-    C = [A[(k1*A.index(pt[i]) + k2) % n] for i in range(len(pt))]
-    return "".join(C)
+    return encipher_affine(msg, key, symbols, _inverse=True)
 
 
 #################### substitution cipher ###########################
 
 
-def encipher_substitution(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+def encipher_substitution(msg, old, new=None):
     """
-    Performs the substitution cipher encryption on plaintext ``pt``, and returns the ciphertext.
+    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.
+
+    Notes
+    =====
 
-    Assumes the ``pt`` has only letters taken from ``symbols``.
-    Assumes ``key`` is a permutation of the symbols. This function permutes the
-    letters of the plaintext using the permutation given in ``key``.
-    The decription uses the inverse permutation.
-    Note that if the permutation in key is order 2 (eg, a transposition) then
-    the encryption permutation and the decryption permutation are the same.
+    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 alphabet_of_cipher, encipher_substitution
-    >>> symbols = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-    >>> A = alphabet_of_cipher(symbols)
-    >>> key = "BACDEFGHIJKLMNOPQRSTUVWXYZ"
-    >>> pt = "go navy! beat army!"
-    >>> encipher_substitution(pt, key)
-    'GONBVYAEBTBRMY'
-    >>> ct = 'GONBVYAEBTBRMY'
-    >>> encipher_substitution(ct, key)
+    >>> 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 permuation 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
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
-    n = len(A)
-    pt0 = [x.capitalize() for x in pt if x.isalnum()]
-    ct = [key[A.index(x)] for x in pt0]
-    return "".join(ct)
+    return translate(msg, old, new)
 
 
 ######################################################################
 #################### Vigenère cipher examples ########################
 ######################################################################
 
-
-def encipher_vigenere(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+def encipher_vigenere(msg, key, symbols=None):
     """
-    Performs the Vigenère cipher encryption on plaintext ``pt``, and returns the ciphertext.
+    Performs the Vigenère 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 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'
 
     Notes
     =====
@@ -270,15 +435,16 @@ def encipher_vigenere(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
     The so-called *Vigenère 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 Vigenère cipher
-    (now on display in the NSA Museum in Fort Meade) [1]_.
+    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
+    Vigenère cipher (now on display in the NSA Museum in Fort
+    Meade) [1]_.
 
     The Vigenère 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 Vigenère cipher shifts
-    a letter by an amount determined by the key (which is a word or
-    phrase known only to the sender and receiver).
+    Whereas the shift cipher shifts each letter by the same amount
+    (that amount being the key of the shift cipher) the Vigenère
+    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
@@ -294,32 +460,42 @@ def encipher_vigenere(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
 
         INPUT:
 
-            ``key``: a string of upper-case letters (the secret key)
+            ``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
 
-            ``m``: string of upper-case letters (the plaintext message)
 
         OUTPUT:
 
-            ``c``: string of upper-case letters (the ciphertext message)
+            ``ct``: string of characters (the ciphertext message)
 
         STEPS:
-            0. Identify the alphabet A, ..., Z with the integers 0, ..., 25.
-            1. Compute from the string ``key`` a list ``L1`` of corresponding
-               integers. Let ``n1 = len(L1)``.
-            2. Compute from the string ``m`` a list ``L2`` of corresponding
-               integers. Let ``n2 = len(L2)``.
-            3. Break ``L2`` up sequencially into sublists of size ``n1``, and one sublist
-               at the end of size smaller or equal to ``n1``.
-            4. For each of these sublists ``L`` of ``L2``, compute a new list ``C`` given by
-               ``C[i] = L[i] + L1[i] (mod 26)`` 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 ``c`` 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).
+            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 sequencially 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 Vigenère actually discovered is an "auto-key" cipher
     described as follows.
@@ -328,57 +504,70 @@ def encipher_vigenere(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
 
         INPUT:
 
-          ``key``: a string of upper-case letters (the secret key)
+          ``key``: a string of letters (the secret key)
 
-          ``m``: string of upper-case letters (the plaintext message)
+          ``msg``: string of letters (the plaintext message)
 
         OUTPUT:
 
-          ``c``: string of upper-case letters (the ciphertext message)
+          ``ct``: string of upper-case letters (the ciphertext message)
 
         STEPS:
-            0. Identify the alphabet A, ..., Z with the integers 0, ..., 25.
-            1. Compute from the string ``m`` a list ``L2`` of corresponding
-               integers. Let ``n2 = len(L2)``.
-            2. Let ``n1`` be the length of the key. Concatenate the string
-               ``key`` with the first ``n2 - n1`` characters of the plaintext message.
-               Compute from this string of length ``n2`` a list ``L1`` of corresponding
-               integers. Note ``n2 = len(L1)``.
-            3. Compute a new list ``C`` given by ``C[i] = L1[i] + L2[i] (mod 26)``,
-               for each ``i``. Note ``n2 = len(C)``.
-            4. Compute from the new list a string ``c`` of corresponding letters.
+            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] http://en.wikipedia.org/wiki/Vigenere_cipher
-
-    Examples
-    ========
-
-    >>> from sympy.crypto.crypto import encipher_vigenere
-    >>> key = "encrypt"
-    >>> pt = "meet me on monday"
-    >>> encipher_vigenere(pt, key)
-    'QRGKKTHRZQEBPR'
+    .. [2] http://web.archive.org/web/20071116100808/
+       http://filebox.vt.edu/users/batman/kryptos.html
 
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
-    N = len(A)   # normally, 26
-    key0 = uniq(key)
-    key0 = [x.capitalize() for x in key0 if x.isalnum()]
-    K = [A.index(x) for x in key0]
-    k = len(K)
-    pt0 = [x.capitalize() for x in pt if x.isalnum()]
-    P = [A.index(x) for x in pt0]
-    n = len(P)
-    #m = n//k
-    C = [(K[i % k] + P[i]) % N for i in range(n)]
-    return "".join([str(A[x]) for x in C])
-
-
-def decipher_vigenere(ct, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+    msg, key, A = _prep(msg, key, symbols)
+    map = dict([(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 Vigenère cipher.
 
@@ -390,157 +579,360 @@ def decipher_vigenere(ct, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
     >>> ct = "QRGK kt HRZQE BPR"
     >>> decipher_vigenere(ct, key)
     'MEETMEONMONDAY'
-
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
+    msg, key, A = _prep(msg, key, symbols)
+    map = dict([(c, i) for i, c in enumerate(A)])
     N = len(A)   # normally, 26
-    key0 = uniq(key)
-    key0 = [x.capitalize() for x in key0 if x.isalnum()]
-    K = [A.index(x) for x in key0]
-    k = len(K)
-    ct0 = [x.capitalize() for x in ct if x.isalnum()]
-    C = [A.index(x) for x in ct0]
-    n = len(C)
-    #m = n//k
-    P = [(-K[i % k] + C[i]) % N for i in range(n)]
-    return "".join([str(A[x]) for x in P])
+    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(pt, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+def encipher_hill(msg, key, symbols=None, pad="Q"):
     r"""
-    Performs the Hill cipher encryption on plaintext ``pt``, and returns the ciphertext.
+    Return the Hill cipher encryption of ``msg``.
 
     Notes
     =====
 
     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. We assume here that
-    "A" `\leftrightarrow` 0, "B" `\leftrightarrow` 1, ..., "Z" `\leftrightarrow` 25.
-    We denote the integers `\{0, 1, ..., 25\}`
-    by `Z_{26}`. Suppose your message m consists of `n` capital letters, with no spaces.
-    This may be regarded an `n`-tuple M of elements of `Z_{26}`. 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 (ie, that the linear transformation `K: Z_{26}^k \rightarrow Z_{26}^k`
+    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).
 
     ALGORITHM:
 
         INPUT:
 
-            ``key``: a `k x k` invertible matrix `K`, all of whose entries are in `Z_{26}`
+            ``msg``: plaintext message of `n` upper-case letters
 
-            ``m``: string of `n` upper-case letters (the plaintext message)
-            (Note: Sage assumes that `n` is a multiple of `k`.)
+            ``key``: a `k x 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``
 
         OUTPUT:
 
-            ``c``: string of upper-case letters (the ciphertext message)
+            ``ct``: ciphertext of upper-case letters
 
         STEPS:
-            0. Identify the alphabet A, ..., Z with the integers 0, ..., 25.
-            1. Compute from the string ``m`` 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`` (where the last list might be
-               "padded" by 0's to ensure it is size ``k``).
-            3. Compute new list ``C_1``, ..., ``C_t`` given by ``C[i] = K*L_i``
-               (arithmetic is done mod 26), for each ``i``.
+            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 ``c`` of corresponding letters.
-               This has length ``k*t``.
+            5. Compute from ``C`` a string ``ct`` of corresponding
+               letters. This has length ``k*t``.
 
     References
     ==========
 
     .. [1] 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.
+    .. [2] Lester S. Hill, Cryptography in an Algebraic Alphabet,
+       The American Mathematical Monthly Vol.36, June-July 1929,
+       pp.306-312.
 
-    Examples
+    See Also
     ========
-
-    >>> from sympy.crypto.crypto import encipher_hill
-    >>> from sympy import Matrix
-    >>> pt = "meet me on monday"
-    >>> key = Matrix([[1, 2], [3, 5]])
-    >>> encipher_hill(pt, key)
-    'UEQDUEODOCTCWQ'
-    >>> pt = "meet me on tuesday"
-    >>> encipher_hill(pt, key)
-    'UEQDUEODHBOYDJYU'
-    >>> pt = "GONAVYBEATARMY"
-    >>> key = Matrix([[1, 0, 1], [0, 1, 1], [2, 2, 3]])
-    >>> encipher_hill(pt, key)
-    'TBBYTKBEKKRLMYU'
+    decipher_hill
 
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
-    N = len(A)   # normally, 26
+    assert key.is_square
+    assert len(pad) == 1
+    msg, pad, A = _prep(msg, pad, symbols)
+    map = dict([(c, i) for i, c in enumerate(A)])
+    P = [map[c] for c in msg]
+    N = len(A)
     k = key.cols
-    pt0 = [x.capitalize() for x in pt if x.isalnum()]
-    P = [A.index(x) for x in pt0]
     n = len(P)
-    m = n//k
-    if n > m*k:
-        P = P + [0]*(n - m*k)
-        m = m + 1
-    C = [list(key*Matrix(k, 1, [P[i] for i in range(k*j, k*(j + 1))])) for j in range(m)]
-    C = flatten(C)
-    return "".join([A[i % N] for i in C])
+    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(ct, key, symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
+def decipher_hill(msg, key, symbols=None):
     """
-    Deciphering is the same as enciphering but using the inverse of the key matrix.
+    Deciphering is the same as enciphering but using the inverse of the
+    key matrix.
 
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import decipher_hill
+    >>> from sympy.crypto.crypto import encipher_hill, decipher_hill
     >>> from sympy import Matrix
-    >>> ct = "UEQDUEODOCTCWQ"
+
     >>> key = Matrix([[1, 2], [3, 5]])
-    >>> decipher_hill(ct, key)
+    >>> encipher_hill("meet me on monday", key)
+    'UEQDUEODOCTCWQ'
+    >>> decipher_hill(_, key)
     'MEETMEONMONDAY'
-    >>> ct = "UEQDUEODHBOYDJYU"
-    >>> decipher_hill(ct, key)
-    'MEETMEONTUESDAYA'
+
+    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'
 
     """
-    symbols = "".join(symbols)
-    A = alphabet_of_cipher(symbols)
-    N = len(A)   # normally, 26
+    assert key.is_square
+    msg, _, A = _prep(msg, '', symbols)
+    map = dict([(c, i) for i, c in enumerate(A)])
+    C = [map[c] for c in msg]
+    N = len(A)
     k = key.cols
-    ct0 = [x.capitalize() for x in ct if x.isalnum()]
-    C = [A.index(x) for x in ct0]
     n = len(C)
-    m = n//k
-    if n > m*k:
-        C = C + [0]*(n - m*k)
-        m = m + 1
+    m, r = divmod(n, k)
+    if r:
+        C = C + [0]*(k - r)
+        m += 1
     key_inv = key.inv_mod(N)
-    P = [list(key_inv*Matrix(k, 1, [C[i] for i in range(k*j, k*(j + 1))])) for j in range(m)]
-    P = flatten(P)
-    return "".join([A[i % N] for i in P])
+    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_bifid5(pt, key):
+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.
+
+        INPUT:
+
+            ``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)
+
+        OUTPUT:
+
+            ciphertext (using Bifid5 cipher without spaces)
+
+    See Also
+    ========
+    decipher_bifid, encipher_bifid5, encipher_bifid6
+
+    """
+    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 = dict([(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 = dict([(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 encryption on plaintext ``pt``, and returns the ciphertext.
+    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.
+
+        INPUT:
+
+            ``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)
+
+        OUTPUT:
 
-    This is the version of the Bifid cipher that uses the `5 \times 5` Polybius square.
+            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 = dict(
+        [(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 = dict([(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.
+
+    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.
 
     Notes
     =====
@@ -548,10 +940,11 @@ def encipher_bifid5(pt, key):
     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 "i"s and "j"s are identified (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).
+    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.
 
@@ -559,110 +952,114 @@ def encipher_bifid5(pt, key):
 
         INPUT:
 
-            ``pt``: plaintext string (no "j"s)
+            ``msg``: plaintext string; converted to upper case and
+            filtered of anything but all letters except J.
 
-            ``key``: short string for key (no repetitions, no "j"s)
+            ``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).
 
         OUTPUT:
 
-            ciphertext (using Bifid5 cipher in all caps, no spaces, no "J"s)
+            ciphertext (all caps, no spaces)
 
         STEPS:
-            1. Create the `5 \times 5` Polybius square ``S`` associated to the ``k`` as
-               follows:
-
-                a) starting top left, moving left-to-right, top-to-bottom,
-                   place the letters of the key into a 5x5 matrix,
-                b) when finished, add the letters of the alphabet
-                   not in the key until the 5x5 square is filled
-
-            2. Create a list ``P`` of pairs of numbers which are the coordinates
-               in the Polybius square of the letters in ``pt``.
-            3. 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``).
-            4. 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``.
-            5. 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 ``ct``.
+            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``.
 
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import encipher_bifid5
-    >>> pt = "meet me on monday"
-    >>> key = "encrypt"
-    >>> encipher_bifid5(pt, key)
-    'LNLLQNPPNPGADK'
-    >>> pt = "meet me on friday"
-    >>> encipher_bifid5(pt, key)
-    'LNLLFGPPNPGRSK'
-
-    """
-    A = alphabet_of_cipher()
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    pt0 = [x.capitalize() for x in pt if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if (not(x in key0) and x != "J")]
-    n = len(pt0)
-    # the fractionalization
-    pairs = [[long_key.index(x)//5, long_key.index(x) % 5] for x in pt0]
-    tmp_cipher = flatten([x[0] for x in pairs] + [x[1] for x in pairs])
-    ct = "".join([long_key[5*tmp_cipher[2*i] + tmp_cipher[2*i + 1]] for i in range(n)])
-    return ct
+    >>> from sympy.crypto.crypto import (
+    ...     encipher_bifid5, decipher_bifid5)
+
+    "J" will be omitted unless it is replaced with somthing 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'
+
+    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(ct, key):
+def decipher_bifid5(msg, key):
     r"""
-    Performs the Bifid cipher decryption on ciphertext ``ct``, and returns the plaintext.
+    Return the Bifid cipher decryption of ``msg``.
 
-    This is the version of the Bifid cipher that uses the `5 \times 5` Polybius square.
+    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.
 
     INPUT:
 
-        ``ct``: ciphertext string (digits okay)
+        ``msg``: ciphertext string
 
-        ``key``: short string for key (no repetitions, digits okay)
+        ``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.
 
     OUTPUT:
 
-        plaintext from Bifid5 cipher (all caps, no spaces, no "J"s)
+        plaintext from Bifid5 cipher (all caps, no spaces)
 
     Examples
     ========
 
     >>> from sympy.crypto.crypto import encipher_bifid5, decipher_bifid5
-    >>> key = "encrypt"
-    >>> pt = "meet me on monday"
-    >>> encipher_bifid5(pt, key)
-    'LNLLQNPPNPGADK'
-    >>> ct = 'LNLLQNPPNPGADK'
-    >>> decipher_bifid5(ct, key)
+    >>> key = "gold bug"
+    >>> encipher_bifid5('meet me on friday', key)
+    'IEILEHFSTSFXEE'
+    >>> encipher_bifid5('meet me on monday', key)
+    'IEILHHFSTSFQYE'
+    >>> decipher_bifid5(_, key)
     'MEETMEONMONDAY'
 
     """
-    A = alphabet_of_cipher()
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    ct0 = [x.capitalize() for x in ct if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if (not(x in key0) and x != "J")]
-    n = len(ct0)
-    # the fractionalization
-    pairs = flatten([[long_key.index(x)//5, long_key.index(x) % 5] for x in ct0 if x != "J"])
-    tmp_plain = flatten([[pairs[i], pairs[n + i]] for i in range(n)])
-    pt = "".join([long_key[5*tmp_plain[2*i] + tmp_plain[2*i + 1]] for i in range(n)])
-    return pt
+    msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5)
+    key = padded_key(key, bifid5)
+    return decipher_bifid(msg, '', key)
 
 
-def bifid5_square(key):
+def bifid5_square(key=None):
     r"""
     5x5 Polybius square.
 
@@ -681,75 +1078,60 @@ def bifid5_square(key):
     [V, W, X, Y, Z]])
 
     """
-    A = alphabet_of_cipher()
-    # first make sure the letters are capitalized
-    # and key has no spaces or duplicates
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if (not(x in key0) and x != "J")]
-    f = lambda i, j: Symbol(long_key[5*i + j])
-    M = Matrix(5, 5, f)
-    return M
+    if not key:
+        key = bifid5
+    else:
+        _, key, _ = _prep('', key.upper(), None, bifid5)
+        key = padded_key(key, bifid5)
+    return bifid_square(key)
 
 
-def encipher_bifid6(pt, key):
+def encipher_bifid6(msg, key):
     r"""
-    Performs the Bifid cipher encryption on plaintext ``pt``, and returns the ciphertext.
+    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.
-    Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "9".
+    This is the version of the Bifid cipher that uses the `6 \times 6`
+    Polybius square.
 
     INPUT:
 
-        ``pt``: plaintext string (digits okay)
+        ``msg``: plaintext string (digits okay)
 
-        ``key``: short string for key (no repetitions, 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.
 
     OUTPUT:
 
         ciphertext from Bifid cipher (all caps, no spaces)
 
-    Examples
+    See Also
     ========
+    decipher_bifid6, encipher_bifid
 
-    >>> from sympy.crypto.crypto import encipher_bifid6
-    >>> key = "encrypt"
-    >>> pt = "meet me on monday at 8am"
-    >>> encipher_bifid6(pt, key)
-    'HNHOKNTA5MEPEGNQZYG'
-    >>> encipher_bifid6(pt, key)
-    'HNHOKNTA5MEPEGNQZYG'
-
-    """
-    A = alphabet_of_cipher() + [str(a) for a in range(10)]
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    pt0 = [x.capitalize() for x in pt if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if not(x in key0)]
-    n = len(pt0)
-    # the fractionalization
-    pairs = [[long_key.index(x)//6, long_key.index(x) % 6] for x in pt0]
-    tmp_cipher = flatten([x[0] for x in pairs] + [x[1] for x in pairs])
-    ct = "".join([long_key[6*tmp_cipher[2*i] + tmp_cipher[2*i + 1]] for i in range(n)])
-    return ct
+    """
+    msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6)
+    key = padded_key(key, bifid6)
+    return encipher_bifid(msg, '', key)
 
 
-def decipher_bifid6(ct, key):
+def decipher_bifid6(msg, key):
     r"""
-    Performs the Bifid cipher decryption on ciphertext ``ct``, and returns the plaintext.
+    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.
-    Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "9".
+    This is the version of the Bifid cipher that uses the `6 \times 6`
+    Polybius square.
 
     INPUT:
 
-        ``ct``: ciphertext string (digits okay)
+        ``msg``: ciphertext string (digits okay); converted to upper case
 
-        ``key``: short string for key (no repetitions, 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. All letters are
+        converted to uppercase.
 
     OUTPUT:
 
@@ -759,32 +1141,19 @@ def decipher_bifid6(ct, key):
     ========
 
     >>> from sympy.crypto.crypto import encipher_bifid6, decipher_bifid6
-    >>> key = "encrypt"
-    >>> pt = "meet me on monday at 8am"
-    >>> encipher_bifid6(pt, key)
-    'HNHOKNTA5MEPEGNQZYG'
-    >>> ct = "HNHOKNTA5MEPEGNQZYG"
-    >>> decipher_bifid6(ct, key)
+    >>> key = "gold bug"
+    >>> encipher_bifid6('meet me on monday at 8am', key)
+    'KFKLJJHF5MMMKTFRGPL'
+    >>> decipher_bifid6(_, key)
     'MEETMEONMONDAYAT8AM'
 
     """
-    A = alphabet_of_cipher() + [str(a) for a in range(10)]
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    ct0 = [x.capitalize() for x in ct if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if not(x in key0)]
-    n = len(ct0)
-    # the fractionalization
-    pairs = flatten([[long_key.index(x)//6, long_key.index(x) % 6] for x in ct0])
-    tmp_plain = flatten([[pairs[i], pairs[n + i]] for i in range(n)])
-    pt = "".join([long_key[6*tmp_plain[2*i] + tmp_plain[2*i + 1]] for i in range(n)])
-    return pt
+    msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6)
+    key = padded_key(key, bifid6)
+    return decipher_bifid(msg, '', key)
 
 
-def bifid6_square(key):
+def bifid6_square(key=None):
     r"""
     6x6 Polybius square.
 
@@ -795,108 +1164,22 @@ def bifid6_square(key):
     ========
 
     >>> from sympy.crypto.crypto import bifid6_square
-    >>> key = "encrypt"
+    >>> key = "gold bug"
     >>> bifid6_square(key)
     Matrix([
-    [E, N, C, R, Y, P],
-    [T, A, B, D, F, G],
-    [H, I, J, K, L, M],
-    [O, Q, S, U, V, W],
-    [X, Z, 0, 1, 2, 3],
+    [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]])
-
     """
-    A = alphabet_of_cipher() + [str(a) for a in range(10)]
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if not(x in key0)]
-    f = lambda i, j: Symbol(long_key[6*i + j])
-    M = Matrix(6, 6, f)
-    return M
-
-
-def encipher_bifid7(pt, key):
-    r"""
-    Performs the Bifid cipher encryption on plaintext ``pt``, and returns the ciphertext.
-
-    This is the version of the Bifid cipher that uses the `7 \times 7` Polybius square.
-    Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "22".
-    (Also, assumes you have some way of distinguishing "22"
-    from "2", "2" juxtaposed together for deciphering...)
-
-    INPUT:
-
-        ``pt``: plaintext string (digits okay)
-
-        ``key``: short string for key (no repetitions, digits okay)
-
-    OUTPUT:
-
-        ciphertext from Bifid7 cipher (all caps, no spaces)
-
-    Examples
-    ========
-
-    >>> from sympy.crypto.crypto import encipher_bifid7
-    >>> key = "encrypt"
-    >>> pt = "meet me on monday at 8am"
-    >>> encipher_bifid7(pt, key)
-    'JEJJLNAA3ME19YF3J222R'
-
-    """
-    A = alphabet_of_cipher() + [str(a) for a in range(23)]
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    pt0 = [x.capitalize() for x in pt if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if not(x in key0)]
-    n = len(pt0)
-    # the fractionalization
-    pairs = [[long_key.index(x)//7, long_key.index(x) % 7] for x in pt0]
-    tmp_cipher = flatten([x[0] for x in pairs] + [x[1] for x in pairs])
-    ct = "".join([long_key[7*tmp_cipher[2*i] + tmp_cipher[2*i + 1]] for i in range(n)])
-    return ct
-
-
-def bifid7_square(key):
-    r"""
-    7x7 Polybius square.
-
-    Produce the Polybius square for the `7 \times 7` Bifid cipher.
-    Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "22".
-    (Also, assumes you have some way of distinguishing "22"
-    from "2", "2" juxtaposed together for deciphering...)
-
-    Examples
-    ========
-
-    >>> from sympy.crypto.crypto import bifid7_square
-    >>> bifid7_square("gold bug")
-    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, 10, 11, 12, 13, 14, 15],
-    [16, 17, 18, 19, 20, 21, 22]])
-
-    """
-    A = alphabet_of_cipher() + [str(a) for a in range(23)]
-    # first make sure the letters are capitalized
-    # and text has no spaces
-    key = uniq(key)
-    key0 = [x.capitalize() for x in key if x.isalnum()]
-    # create long key
-    long_key = key0 + [x for x in A if (not(x in key0))]
-    f = lambda i, j: Symbol(long_key[7*i + j])
-    M = Matrix(7, 7, f)
-    return M
+    if not key:
+        key = bifid6
+    else:
+        _, key, _ = _prep('', key.upper(), None, bifid6)
+        key = padded_key(key, bifid6)
+    return bifid_square(key)
 
 
 #################### RSA  #############################
@@ -904,34 +1187,36 @@ def bifid7_square(key):
 
 def rsa_public_key(p, q, e):
     r"""
-    The RSA *public key* is the pair `(n,e)`, where `n`
+    Return the RSA *public key* pair, `(n, e)`, where `n`
     is a product of two primes and `e` is relatively
-    prime (coprime) to the Euler totient `\phi(n)`.
+    prime (coprime) to the Euler totient `\phi(n)`. False
+    is returned if any assumption is violated.
 
     Examples
     ========
 
     >>> from sympy.crypto.crypto import rsa_public_key
     >>> p, q, e = 3, 5, 7
-    >>> n, e = rsa_public_key(p, q, e)
-    >>> n
-    15
-    >>> e
-    7
+    >>> rsa_public_key(p, q, e)
+    (15, 7)
+    >>> rsa_public_key(p, q, 30)
+    False
 
     """
     n = p*q
-    phi = totient(n)
-    if isprime(p) and isprime(q) and gcd(e, phi) == 1:
-        return n, e
+    if isprime(p) and isprime(q):
+        phi = totient(n)
+        if gcd(e, phi) == 1:
+            return n, e
     return False
 
 
 def rsa_private_key(p, q, e):
     r"""
-    The RSA *private key* is the pair `(n,d)`, where `n`
+    Return the RSA *private key*, `(n,d)`, where `n`
     is a product of two primes and `d` is the inverse of
-    `e` (mod `\phi(n)`).
+    `e` (mod `\phi(n)`). False is returned if any assumption
+    is violated.
 
     Examples
     ========
@@ -940,20 +1225,23 @@ def rsa_private_key(p, q, e):
     >>> p, q, e = 3, 5, 7
     >>> rsa_private_key(p, q, e)
     (15, 7)
+    >>> rsa_private_key(p, q, 30)
+    False
 
     """
     n = p*q
-    phi = totient(n)
-    if isprime(p) and isprime(q) and gcd(e, phi) == 1:
-        d = int(invert(e,phi))
-        return n, d
+    if isprime(p) and isprime(q):
+        phi = totient(n)
+        if gcd(e, phi) == 1:
+            d = mod_inverse(e, phi)
+            return n, d
     return False
 
 
-def encipher_rsa(pt, puk):
+def encipher_rsa(i, key):
     """
-    In RSA, a message `m` is encrypted by computing
-    `m^e` (mod `n`), where ``puk`` is the public key `(n,e)`.
+    Return encryption of ``i`` by computing `i^e` (mod `n`),
+    where ``key`` is the public key `(n, e)`.
 
     Examples
     ========
@@ -961,19 +1249,19 @@ def encipher_rsa(pt, puk):
     >>> from sympy.crypto.crypto import encipher_rsa, rsa_public_key
     >>> p, q, e = 3, 5, 7
     >>> puk = rsa_public_key(p, q, e)
-    >>> pt = 12
-    >>> encipher_rsa(pt, puk)
+    >>> msg = 12
+    >>> encipher_rsa(msg, puk)
     3
 
     """
-    n, e = puk
-    return pow(pt, e, n)
+    n, e = key
+    return pow(i, e, n)
 
 
-def decipher_rsa(ct, prk):
+def decipher_rsa(i, key):
     """
-    In RSA, a ciphertext `c` is decrypted by computing
-    `c^d` (mod `n`), where ``prk`` is the private key `(n, d)`.
+    Return decyption of ``i`` by computing `i^d` (mod `n`),
+    where ``key`` is the private key `(n, d)`.
 
     Examples
     ========
@@ -981,13 +1269,13 @@ def decipher_rsa(ct, prk):
     >>> from sympy.crypto.crypto import decipher_rsa, rsa_private_key
     >>> p, q, e = 3, 5, 7
     >>> prk = rsa_private_key(p, q, e)
-    >>> ct = 3
-    >>> decipher_rsa(ct, prk)
+    >>> msg = 3
+    >>> decipher_rsa(msg, prk)
     12
 
     """
-    n, d = prk
-    return pow(ct, d, n)
+    n, d = key
+    return pow(i, d, n)
 
 
 #################### kid krypto (kid RSA) #############################
@@ -1002,15 +1290,16 @@ def kid_rsa_public_key(a, b, A, B):
     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`.
+    * 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 `d`, which Bob keeps secret.
+    * The *private key* is `(n, d)`, which Bob keeps secret.
 
-    Encryption: If `m` is the plaintext message then the
-    ciphertext is `c = m e \pmod n`.
+    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 `m = c d \pmod n`.
+    plaintext is `p = c d \pmod n`.
 
     Examples
     ========
@@ -1021,17 +1310,18 @@ def kid_rsa_public_key(a, b, A, B):
     (369, 58)
 
     """
-    M = S(a*b - 1)
-    e = S(A*M + a)
-    d = S(B*M + b)
-    n = S((e*d - 1)//M)
+    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.
+    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
     ========
@@ -1042,61 +1332,93 @@ def kid_rsa_private_key(a, b, A, B):
     (369, 70)
 
     """
-    M = S(a*b - 1)
-    e = S(A*M + a)
-    d = S(B*M + b)
-    n = S((e*d - 1)//M)
+    M = a*b - 1
+    e = A*M + a
+    d = B*M + b
+    n = (e*d - 1)//M
     return n, d
 
 
-def encipher_kid_rsa(pt, puk):
+def encipher_kid_rsa(msg, key):
     """
-    Here ``pt`` is the plaintext and ``puk`` is the public 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
-    >>> pt = 200
+    >>> from sympy.crypto.crypto import (
+    ...     encipher_kid_rsa, kid_rsa_public_key)
+    >>> msg = 200
     >>> a, b, A, B = 3, 4, 5, 6
-    >>> pk = kid_rsa_public_key(a, b, A, B)
-    >>> encipher_kid_rsa(pt, pk)
+    >>> key = kid_rsa_public_key(a, b, A, B)
+    >>> encipher_kid_rsa(msg, key)
     161
 
     """
-    return (pt*puk[1]) % puk[0]
+    n, e = key
+    return (msg*e) % n
 
 
-def decipher_kid_rsa(ct, prk):
+def decipher_kid_rsa(msg, key):
     """
-    Here ``pt`` is the plaintext and ``prk`` is the private 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
+    >>> 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)
-    >>> pt = 200
-    >>> pk = kid_rsa_public_key(a, b, A, B)
-    >>> prk = kid_rsa_private_key(a, b, A, B)
-    >>> ct = encipher_kid_rsa(pt, pk)
-    >>> decipher_kid_rsa(ct, prk)
+    >>> 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 = prk[0]
-    d = prk[1]
-    return (ct*d) % n
+    n, d = key
+    return (msg*d) % n
 
 
 #################### Morse Code ######################################
 
-
-def encode_morse(pt):
+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 = dict([(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 "|"
-    and words by "||".
+    Encodes a plaintext into popular Morse Code with letters
+    separated by `sep` and words by a double `sep`.
 
     References
     ==========
@@ -1107,64 +1429,44 @@ def encode_morse(pt):
     ========
 
     >>> from sympy.crypto.crypto import encode_morse
-    >>> pt = 'ATTACK THE RIGHT FLANK'
-    >>> encode_morse(pt)
-    '.-|-|-|.-|-.-.|-.-||-|....|.||.-.|..|--.|....|-||..-.|.-..|.-|-.|-.-'
-
-    """
-
-    morse_encoding_map = {"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": "----.",
-                     ".": ".-.-.-", ",": "--..--",
-                     ":": "---...", ";": "-.-.-.",
-                     "?": "..--..", "-": "-...-",
-                     "_": "..--.-", "(": "-.--.",
-                     ")": "-.--.-", "'": ".----.",
-                     "=": "-...-", "+": ".-.-.",
-                     "/": "-..-.", "@": ".--.-.",
-                     "$": "...-..-", "!": "-.-.--" }
-
-    unusable_chars = "\"#%&*<>[\]^`{|}~"
-    morsestring = []
+    >>> msg = 'ATTACK RIGHT FLANK'
+    >>> encode_morse(msg)
+    '.-|-|-|.-|-.-.|-.-||.-.|..|--.|....|-||..-.|.-..|.-|-.|-.-'
+
+    """
+
+    mapping = mapping or char_morse
+    assert sep not in mapping
+    word_sep = 2*sep
+    mapping[" "] = word_sep
+    suffix = msg and msg[-1] in whitespace
 
-    for i in unusable_chars:
-        pt = pt.replace(i, "")
-    pt = pt.upper()
+    # 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))
 
-    words = pt.split(" ")
+    morsestring = []
+    words = msg.split()
     for word in words:
-        letters = list(word)
         morseword = []
-        for letter in letters:
-            morseletter = morse_encoding_map[letter]
+        for letter in word:
+            morseletter = mapping[letter]
             morseword.append(morseletter)
 
-        word = "|".join(morseword)
+        word = sep.join(morseword)
         morsestring.append(word)
 
-    return "||".join(morsestring)
+    return word_sep.join(morsestring) + (word_sep if suffix else '')
 
 
-def decode_morse(mc):
+def decode_morse(msg, sep='|', mapping=None):
     """
-    Decodes a Morse Code with letters separated by "|"
-    and words by "||" into plaintext.
+    Decodes a Morse Code with letters separated by `sep`
+    (default is '|') and words by `word_sep` (default is '||)
+    into plaintext.
 
     References
     ==========
@@ -1181,51 +1483,17 @@ def decode_morse(mc):
 
     """
 
-    morse_decoding_map = {".-": "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",
-                          ".-.-.-": ".", "--..--": ",",
-                          "---...": ":", "-.-.-.": ";",
-                          "..--..": "?", "-...-": "-",
-                          "..--.-": "_", "-.--.": "(",
-                          "-.--.-": ")", ".----.": "'",
-                          "-...-": "=", ".-.-.": "+",
-                          "-..-.": "/", ".--.-.": "@",
-                          "...-..-": "$", "-.-.--": "!"}
-
+    mapping = mapping or morse_char
+    word_sep = 2*sep
     characterstring = []
-
-    if mc[-1] == "|" and mc[-2] == "|":
-        mc = mc[:-2]
-    words = mc.split("||")
+    words = msg.strip(word_sep).split(word_sep)
     for word in words:
-        letters = word.split("|")
-        characterword = []
-        for letter in letters:
-            try:
-                characterletter = morse_decoding_map[letter]
-            except KeyError:
-                return "Invalid Morse Code"
-            characterword.append(characterletter)
-
-        word = "".join(characterword)
+        letters = word.split(sep)
+        chars = [mapping[c] for c in letters]
+        word = ''.join(chars)
         characterstring.append(word)
-    return " ".join(characterstring)
+    rv = " ".join(characterstring)
+    return rv
 
 
 #################### LFSRs  ##########################################
@@ -1248,7 +1516,8 @@ def lfsr_sequence(key, fill, n):
 
     OUTPUT:
 
-        The lfsr sequence defined by `x_{n+1} = c_k x_n + \ldots + c_0 x_{n-k}`, for
+        The lfsr sequence defined by
+        `x_{n+1} = c_k x_n + \ldots + c_0 x_{n-k}`, for
         `n \leq k`.
 
     Notes
@@ -1295,7 +1564,8 @@ def lfsr_sequence(key, fill, n):
     References
     ==========
 
-    .. [G] Solomon Golomb, Shift register sequences, Aegean Park Press, Laguna Hills, Ca, 1967
+    .. [G] Solomon Golomb, Shift register sequences, Aegean Park Press,
+       Laguna Hills, Ca, 1967
 
     Examples
     ========
@@ -1306,7 +1576,8 @@ def lfsr_sequence(key, fill, n):
     >>> 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]
+    [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]
 
     """
     if not isinstance(key, list):
@@ -1329,7 +1600,7 @@ def lfsr_sequence(key, fill, n):
 
 def lfsr_autocorrelation(L, P, k):
     """
-    This function computes the lsfr autocorrelation function.
+    This function computes the LFSR autocorrelation function.
 
     INPUT:
 
@@ -1347,7 +1618,8 @@ def lfsr_autocorrelation(L, P, k):
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import lfsr_sequence, lfsr_autocorrelation
+    >>> 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)]
@@ -1372,28 +1644,33 @@ def lfsr_autocorrelation(L, P, k):
 
 def lfsr_connection_polynomial(s):
     """
-    This function computes the lsfr connection polynomial.
+    This function computes the LFSR connection polynomial.
 
     INPUT:
 
-        ``s``: a sequence of elements of even length, with entries in a finite field
+        ``s``: a sequence of elements of even length, with entries in
+        a finite field
 
     OUTPUT:
 
-        ``C(x)``: the connection polynomial of a minimal LFSR yielding ``s``.
+        ``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]_.
+    This implements the algorithm in section 3 of J. L. Massey's
+    article [M]_.
 
     References
     ==========
 
     .. [M] James L. Massey, "Shift-Register Synthesis and BCH Decoding."
-        IEEE Trans. on Information Theory, vol. 15(1), pp. 122-127, Jan 1969.
+        IEEE Trans. on Information Theory, vol. 15(1), pp. 122-127,
+        Jan 1969.
 
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import lfsr_sequence, lfsr_connection_polynomial
+    >>> 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)]
@@ -1432,8 +1709,10 @@ def lfsr_connection_polynomial(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 = (s[N].to_int() + sum([coeffsC[i]*s[N - i].to_int() for i in range(1, r)])) % p
+            coeffsC = [C.subs(x, 0)] + [C.coeff(x**i)
+                for i in range(1, dC + 1)]
+            d = (s[N].to_int() + sum([coeffsC[i]*s[N - i].to_int()
+                for i in range(1, r)])) % p
         if L == 0:
             d = s[N].to_int()*x**0
         if d == 0:
@@ -1454,13 +1733,14 @@ def lfsr_connection_polynomial(s):
                 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])
+    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):
+def elgamal_private_key(digit=10, seed=None):
     """
     Return three number tuple as private key.
 
@@ -1469,20 +1749,25 @@ def elgamal_private_key(digit=10):
 
     `a^{b} \equiv c \pmod p`
 
-    In general, if a and b are known, c is easily
-    calculated. If b is unknown, it is hard to use
-    a and c to get b.
+    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 : Key length in binary
+    digit : minimum number of binary digits for key
 
     Returns
     =======
 
     (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.utilities.randtest._randrange.
 
     Examples
     ========
@@ -1496,18 +1781,19 @@ def elgamal_private_key(digit=10):
     True
 
     """
+    randrange = _randrange(seed)
     p = nextprime(2**digit)
     return p, primitive_root(p), randrange(2, p)
 
 
-def elgamal_public_key(prk):
+def elgamal_public_key(key):
     """
     Return three number tuple as public key.
 
     Parameters
     ==========
 
-    prk : Tuple (p, r, e)  generated by ``elgamal_private_key``
+    key : Tuple (p, r, e)  generated by ``elgamal_private_key``
 
     Returns
     =======
@@ -1521,17 +1807,19 @@ def elgamal_public_key(prk):
     (1031, 14, 212)
 
     """
-    return prk[0], prk[1], pow(prk[1], prk[2], prk[0])
+    p, r, e = key
+    return p, r, pow(r, e, p)
 
 
-def encipher_elgamal(m, puk):
+def encipher_elgamal(i, key, seed=None):
     """
     Encrypt message with public key
 
-    m is plain text message in int. puk is
-    public key (p, r, e). In order to encrypt
-    a message, a random number ``a`` between ``2`` and ``p``,
-    encryped message is `c_{1}` and `c_{2}`
+    ``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`
 
@@ -1540,35 +1828,49 @@ def encipher_elgamal(m, puk):
     Parameters
     ==========
 
-    m : int of encoded message
-    puk : public key
+    msg : int of encoded message
+    key : public key
 
     Returns
     =======
 
     (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.utilities.randtest._randrange.
+
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import encipher_elgamal
-    >>> encipher_elgamal(100, (1031, 14, 212))     # doctest: +SKIP
-    (835, 271)
+    >>> 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)
 
     """
-    if m > puk[0]:
-        raise ValueError('Message {} should be less than prime {}'.format(m, puk[0]))
-    r = randrange(2, puk[0])
-    return pow(puk[1], r, puk[0]), m * pow(puk[2], r, puk[0]) % puk[0]
+    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(ct, prk):
+def decipher_elgamal(msg, key):
     r"""
     Decrypt message with private key
 
-    `ct = (c_{1}, c_{2})`
+    `msg = (c_{1}, c_{2})`
 
-    `prk = (p, r, d)`
+    `key = (p, r, d)`
 
     According to extended Eucliden theorem,
     `u c_{1}^{d} + p n = 1`
@@ -1583,45 +1885,62 @@ def decipher_elgamal(ct, prk):
     ========
 
     >>> from sympy.crypto.crypto import decipher_elgamal
-    >>> decipher_elgamal((835, 271), (1031, 14, 636))
-    100
+    >>> 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
 
     """
-    u = igcdex(ct[0] ** prk[2], prk[0])[0]
-    return u * ct[1] % prk[0]
+    p, r, d = key
+    c1, c2 = msg
+    u = igcdex(c1**d, p)[0]
+    return u * c2 % p
 
 
-#################### Diffie-Hellman Key Exchange  #############################
+################ Diffie-Hellman Key Exchange  #########################
 
-def dh_private_key(digit = 10):
+def dh_private_key(digit=10, seed=None):
     """
-    Return two number tuple as private key.
+    Return three integer tuple as private key.
 
     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 with 1 < a, b < p. These are their
-        private keys.
+    *   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 chose number
-        (a or b) and now have both as their shared key `g^{ab} \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: Key length in binary
+    digit: minimum number of binary digits required in key
 
     Returns
     =======
 
     (p, g, a) : p = prime number, g = primitive root of p,
-                a = random number in between 2 and p - 1
+                a = random number from 2 thru p - 1
+
+    Notes
+    =====
+
+    For testing purposes, the ``seed`` parameter may be set to control
+    the output of this routine. See sympy.utilities.randtest._randrange.
 
     Examples
     ========
@@ -1640,21 +1959,23 @@ def dh_private_key(digit = 10):
     True
 
     """
-    p = nextprime(2 ** digit)
+    p = nextprime(2**digit)
     g = primitive_root(p)
+    randrange = _randrange(seed)
     a = randrange(2, p)
     return p, g, a
 
-def dh_public_key(prk):
+
+def dh_public_key(key):
     """
-    Return two number tuple as public key.
+    Return three number tuple as public key.
 
     This is the tuple that Alice sends to Bob.
 
     Parameters
     ==========
 
-    prk: Tuple (p, g, a) generated by ``dh_private_key``
+    key: Tuple (p, g, a) generated by ``dh_private_key``
 
     Returns
     =======
@@ -1673,12 +1994,13 @@ def dh_public_key(prk):
     True
 
     """
-    p, g, a = prk
+    p, g, a = key
     return p, g, pow(g, a, p)
 
-def dh_shared_key(puk, b):
+
+def dh_shared_key(key, b):
     """
-    Return int as shared key.
+    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.
@@ -1686,19 +2008,20 @@ def dh_shared_key(puk, b):
     Parameters
     ==========
 
-    puk: Tuple (p, g, x) generated by ``dh_public_key``
+    key: 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
     =======
 
-    sk: int as shared key
+    shared key (int)
 
     Examples
     ========
 
-    >>> from sympy.crypto.crypto import dh_private_key, dh_public_key, dh_shared_key
+    >>> 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)
@@ -1706,8 +2029,10 @@ def dh_shared_key(puk, b):
     True
 
     """
-    p, _, x = puk
+    p, _, x = key
     if 1 >= b or b >= p:
-        raise ValueError('Value of b should be greater 1 and less than prime {}'\
-                .format(p))
+        raise ValueError(filldedent('''
+            Value of b should be greater 1 and less
+            than prime %s.''' % p))
+
     return pow(x, b, p)
diff --git a/sympy/utilities/misc.py b/sympy/utilities/misc.py
index 5d0921f8da..2bb228cc06 100644
--- a/sympy/utilities/misc.py
+++ b/sympy/utilities/misc.py
@@ -4,10 +4,12 @@
 
 import sys
 import os
+import re as _re
 from textwrap import fill, dedent
 from sympy.core.compatibility import get_function_name, range
 
 
+
 def filldedent(s, w=70):
     """
     Strips leading and trailing empty lines from a copy of `s`, then dedents,
@@ -217,5 +219,183 @@ def find_executable(executable, path=None):
 
 
 def func_name(x):
-    '''return function name of `x` (if defined) else the `type(x)`.'''
+    '''Return function name of `x` (if defined) else the `type(x)`.
+    See Also
+    ========
+    sympy.core.compatibility get_function_name
+    '''
     return getattr(getattr(x, 'func', x), '__name__', type(x))
+
+
+def _replace(reps):
+    """Return a function that can make the replacements, given in
+    ``reps``, on a string. The replacements should be given as mapping.
+
+    Examples
+    ========
+
+    >>> from sympy.utilities.misc import _replace
+    >>> f = _replace(dict(foo='bar', d='t'))
+    >>> f('food')
+    'bart'
+    >>> f = _replace({})
+    >>> f('food')
+    'food'
+    """
+    if not reps:
+        return lambda x: x
+    D = lambda match: reps[match.group(0)]
+    pattern = _re.compile("|".join(
+        [_re.escape(k) for k, v in reps.items()]), _re.M)
+    return lambda string: pattern.sub(D, string)
+
+
+def replace(string, *reps):
+    """Return ``string`` with all keys in ``reps`` replaced with
+    their corresponding values, longer strings first, irrespective
+    of the order they are given.  ``reps`` may be passed as tuples
+    or a single mapping.
+
+    Examples
+    ========
+
+    >>> from sympy.utilities.misc import replace
+    >>> replace('foo', {'oo': 'ar', 'f': 'b'})
+    'bar'
+    >>> replace("spamham sha", ("spam", "eggs"), ("sha","md5"))
+    'eggsham md5'
+
+    There is no guarantee that a unique answer will be
+    obtained if keys in a mapping overlap (i.e. are the same
+    length and have some identical sequence at the
+    beginning/end):
+
+    >>> reps = [
+    ...     ('ab', 'x'),
+    ...     ('bc', 'y')]
+    >>> replace('abc', *reps) in ('xc', 'ay')
+    True
+
+    References
+    ==========
+
+    .. [1] http://stackoverflow.com/questions/6116978/python-replace-multiple-strings
+    """
+    if len(reps) == 1:
+        kv = reps[0]
+        if type(kv) is dict:
+            reps = kv
+        else:
+            return string.replace(*kv)
+    else:
+        reps = dict(reps)
+    return _replace(reps)(string)
+
+
+def translate(s, a, b=None, c=None):
+    """Return ``s`` where characters have been replaced or deleted.
+
+    SYNTAX
+    ======
+
+    translate(s, None, deletechars):
+        all characters in ``deletechars`` are deleted
+    translate(s, map [,deletechars]):
+        all characters in ``deletechars`` (if provided) are deleted
+        then the replacements defined by map are made; if the keys
+        of map are strings then the longer ones are handled first.
+        Multicharacter deletions should have a value of ''.
+    translate(s, oldchars, newchars, deletechars)
+        all characters in ``deletechars`` are deleted
+        then each character in ``oldchars`` is replaced with the
+        corresponding character in ``newchars``
+
+    Examples
+    ========
+
+    >>> from sympy.utilities.misc import translate
+    >>> from sympy.core.compatibility import unichr
+    >>> abc = 'abc'
+    >>> translate(abc, None, 'a')
+    'bc'
+    >>> translate(abc, {'a': 'x'}, 'c')
+    'xb'
+    >>> translate(abc, {'abc': 'x', 'a': 'y'})
+    'x'
+
+    >>> translate('abcd', 'ac', 'AC', 'd')
+    'AbC'
+
+    There is no guarantee that a unique answer will be
+    obtained if keys in a mapping overlap are the same
+    length and have some identical sequences at the
+    beginning/end:
+
+    >>> translate(abc, {'ab': 'x', 'bc': 'y'}) in ('xc', 'ay')
+    True
+    """
+    from sympy.core.compatibility import maketrans
+
+    # when support for Python 2 is dropped, this try/except can be
+    #removed
+    try:
+        ''.translate(None, '')
+        py3 = False
+    except TypeError:
+        py3 = True
+
+    mr = {}
+    if a is None:
+        assert c is None
+        if not b:
+            return s
+        c = b
+        a = b = ''
+    else:
+        if type(a) is dict:
+            short = {}
+            for k in list(a.keys()):
+                if (len(k) == 1 and len(a[k]) == 1):
+                    short[k] = a.pop(k)
+            mr = a
+            c = b
+            if short:
+                a, b = [''.join(i) for i in list(zip(*short.items()))]
+            else:
+                a = b = ''
+        else:
+            assert len(a) == len(b)
+    if py3:
+        if c:
+            s = s.translate(maketrans('', '', c))
+        s = replace(s, mr)
+        return s.translate(maketrans(a, b))
+    else:
+        # when support for Python 2 is dropped, this if-else-block
+        # can be replaced with the if-clause
+        if c:
+            c = list(c)
+            rem = {}
+            for i in range(-1, -1 - len(c), -1):
+                if ord(c[i]) > 255:
+                    rem[c[i]] = ''
+                    c.pop(i)
+            s = s.translate(None, ''.join(c))
+            s = replace(s, rem)
+            if a:
+                a = list(a)
+                b = list(b)
+                for i in range(-1, -1 - len(a), -1):
+                    if ord(a[i]) > 255 or ord(b[i]) > 255:
+                        mr[a.pop(i)] = b.pop(i)
+                a = ''.join(a)
+                b = ''.join(b)
+        s = replace(s, mr)
+        table = maketrans(a, b)
+        # s may have become unicode which uses the py3 syntax for translate
+        if type(table) is str and type(s) is str:
+            s = s.translate(table)
+        else:
+            s = s.translate(dict(
+                [(i, ord(c)) for i, c in enumerate(table)]))
+        return s


Test patch:
diff --git a/sympy/crypto/tests/test_crypto.py b/sympy/crypto/tests/test_crypto.py
index e6af68682f..333e68a0a3 100644
--- a/sympy/crypto/tests/test_crypto.py
+++ b/sympy/crypto/tests/test_crypto.py
@@ -1,26 +1,25 @@
 from sympy.core import symbols
 from sympy.core.compatibility import range
-from sympy.crypto.crypto import (alphabet_of_cipher, cycle_list,
+from sympy.crypto.crypto import (cycle_list,
       encipher_shift, encipher_affine, encipher_substitution,
-      encipher_vigenere, decipher_vigenere,
+      check_and_join, encipher_vigenere, decipher_vigenere,
       encipher_hill, decipher_hill, encipher_bifid5, encipher_bifid6,
-      bifid5_square, bifid6_square, bifid7_square,
-      encipher_bifid7, decipher_bifid5, decipher_bifid6, encipher_kid_rsa,
+      bifid5_square, bifid6_square, bifid5, bifid6, bifid10,
+      decipher_bifid5, decipher_bifid6, encipher_kid_rsa,
       decipher_kid_rsa, kid_rsa_private_key, kid_rsa_public_key,
       decipher_rsa, rsa_private_key, rsa_public_key, encipher_rsa,
       lfsr_connection_polynomial, lfsr_autocorrelation, lfsr_sequence,
       encode_morse, decode_morse, elgamal_private_key, elgamal_public_key,
       encipher_elgamal, decipher_elgamal, dh_private_key, dh_public_key,
-      dh_shared_key)
+      dh_shared_key, decipher_shift, decipher_affine, encipher_bifid,
+      decipher_bifid, bifid_square, padded_key, uniq)
 from sympy.matrices import Matrix
 from sympy.ntheory import isprime, is_primitive_root
 from sympy.polys.domains import FF
-from sympy.utilities.pytest import raises
-from random import randrange
 
-def test_alphabet_of_cipher():
-    assert alphabet_of_cipher()[0] == "A"
-    assert alphabet_of_cipher(symbols="1z") == ["1", "z"]
+from sympy.utilities.pytest import raises, slow
+
+from random import randrange
 
 
 def test_cycle_list():
@@ -33,6 +32,7 @@ def test_encipher_shift():
     assert encipher_shift("ABC", 0) == "ABC"
     assert encipher_shift("ABC", 1) == "BCD"
     assert encipher_shift("ABC", -1) == "ZAB"
+    assert decipher_shift("ZAB", -1) == "ABC"
 
 
 def test_encipher_affine():
@@ -41,11 +41,21 @@ def test_encipher_affine():
     assert encipher_affine("ABC", (-1, 0)) == "AZY"
     assert encipher_affine("ABC", (-1, 1), symbols="ABCD") == "BAD"
     assert encipher_affine("123", (-1, 1), symbols="1234") == "214"
+    assert encipher_affine("ABC", (3, 16)) == "QTW"
+    assert decipher_affine("QTW", (3, 16)) == "ABC"
 
 
 def test_encipher_substitution():
-    assert encipher_substitution("ABC", "BAC", symbols="ABC") == "BAC"
-    assert encipher_substitution("123", "124", symbols="1234") == "124"
+    assert encipher_substitution("ABC", "BAC", "ABC") == "BAC"
+    assert encipher_substitution("123", "1243", "1234") == "124"
+
+
+def test_check_and_join():
+    assert check_and_join("abc") == "abc"
+    assert check_and_join(uniq("aaabc")) == "abc"
+    assert check_and_join("ab c".split()) == "abc"
+    assert check_and_join("abc", "a", filter=True) == "a"
+    raises(ValueError, lambda: check_and_join('ab', 'a'))
 
 
 def test_encipher_vigenere():
@@ -73,8 +83,10 @@ def test_encipher_hill():
     A = Matrix(2, 2, [1, 2, 3, 5])
     assert encipher_hill("ABCD", A, symbols="ABCD") == "CBAB"
     assert encipher_hill("AB", A, symbols="ABCD") == "CB"
-    # n does not need to be a multiple of k
-    assert encipher_hill("ABA", A) == "CFAA"
+    # message length, n, does not need to be a multiple of k;
+    # it is padded
+    assert encipher_hill("ABA", A) == "CFGC"
+    assert encipher_hill("ABA", A, pad="Z") == "CFYV"
 
 
 def test_decipher_hill():
@@ -98,8 +110,7 @@ def test_encipher_bifid5():
 
 
 def test_bifid5_square():
-    A = alphabet_of_cipher()
-    A.remove("J")
+    A = bifid5
     f = lambda i, j: symbols(A[5*i + j])
     M = Matrix(5, 5, f)
     assert bifid5_square("") == M
@@ -112,20 +123,6 @@ def test_decipher_bifid5():
     assert decipher_bifid5("b ac", "b") == "ABC"
 
 
-def test_bifid7_square():
-    A = alphabet_of_cipher() + [str(a) for a in range(23)]
-    f = lambda i, j: symbols(A[7*i + j])
-    M = Matrix(7, 7, f)
-    assert bifid7_square("") == M
-
-
-def test_encipher_bifid7():
-    assert encipher_bifid7("AB", "AB") == "AB"
-    assert encipher_bifid7("AB", "CD") == "CR"
-    assert encipher_bifid7("ab", "c") == "CJ"
-    assert encipher_bifid7("a bc", "b") == "BAC"
-
-
 def test_encipher_bifid6():
     assert encipher_bifid6("AB", "AB") == "AB"
     assert encipher_bifid6("AB", "CD") == "CP"
@@ -141,7 +138,7 @@ def test_decipher_bifid6():
 
 
 def test_bifid6_square():
-    A = alphabet_of_cipher() + [str(a) for a in range(10)]
+    A = bifid6
     f = lambda i, j: symbols(A[6*i + j])
     M = Matrix(6, 6, f)
     assert bifid6_square("") == M
@@ -207,13 +204,18 @@ def test_decipher_kid_rsa():
 def test_encode_morse():
     assert encode_morse('ABC') == '.-|-...|-.-.'
     assert encode_morse('SMS ') == '...|--|...||'
+    assert encode_morse('SMS\n') == '...|--|...||'
+    assert encode_morse('') == ''
+    assert encode_morse(' ') == '||'
+    assert encode_morse(' ', sep='`') == '``'
+    assert encode_morse(' ', sep='``') == '````'
     assert encode_morse('!@#$%^&*()_+') == '-.-.--|.--.-.|...-..-|-.--.|-.--.-|..--.-|.-.-.'
 
 
 def test_decode_morse():
     assert decode_morse('-.-|.|-.--') == 'KEY'
     assert decode_morse('.-.|..-|-.||') == 'RUN'
-    assert decode_morse('.....----') == 'Invalid Morse Code'
+    raises(KeyError, lambda: decode_morse('.....----'))
 
 
 def test_lfsr_sequence():
@@ -244,18 +246,22 @@ def test_lfsr_connection_polynomial():
     s = lfsr_sequence([F(1), F(1)], [F(0), F(1)], 5)
     assert lfsr_connection_polynomial(s) == x**2 + x + 1
 
+
 def test_elgamal_private_key():
     a, b, _ = elgamal_private_key(digit=100)
     assert isprime(a)
     assert is_primitive_root(b, a)
     assert len(bin(a)) >= 102
 
+
 def test_elgamal():
-    dk = elgamal_private_key(20)
+    dk = elgamal_private_key(5)
     ek = elgamal_public_key(dk)
-    m = 12345
-    assert m == decipher_elgamal(encipher_elgamal(m, ek), dk)
-    raises(ValueError, lambda: encipher_elgamal(2000, (1031, 14, 212)))
+    P = ek[0]
+    assert P - 1 == decipher_elgamal(encipher_elgamal(P - 1, ek), dk)
+    raises(ValueError, lambda: encipher_elgamal(P, dk))
+    raises(ValueError, lambda: encipher_elgamal(-1, dk))
+
 
 def test_dh_private_key():
     p, g, _ = dh_private_key(digit = 100)
@@ -263,6 +269,7 @@ def test_dh_private_key():
     assert is_primitive_root(g, p)
     assert len(bin(p)) >= 102
 
+
 def test_dh_public_key():
     p1, g1, a = dh_private_key(digit = 100)
     p2, g2, ga = dh_public_key((p1, g1, a))
@@ -270,6 +277,7 @@ def test_dh_public_key():
     assert g1 == g2
     assert ga == pow(g1, a, p1)
 
+
 def test_dh_shared_key():
     prk = dh_private_key(digit = 100)
     p, _, ga = dh_public_key(prk)
@@ -277,3 +285,21 @@ def test_dh_shared_key():
     sk = dh_shared_key((p, _, ga), b)
     assert sk == pow(ga, b, p)
     raises(ValueError, lambda: dh_shared_key((1031, 14, 565), 2000))
+
+
+def test_padded_key():
+    assert padded_key('b', 'ab') == 'ba'
+    raises(ValueError, lambda: padded_key('ab', 'ace'))
+    raises(ValueError, lambda: padded_key('ab', 'abba'))
+
+
+def test_bifid():
+    raises(ValueError, lambda: encipher_bifid('abc', 'b', 'abcde'))
+    assert encipher_bifid('abc', 'b', 'abcd') == 'bdb'
+    raises(ValueError, lambda: decipher_bifid('bdb', 'b', 'abcde'))
+    assert encipher_bifid('bdb', 'b', 'abcd') == 'abc'
+    raises(ValueError, lambda: bifid_square('abcde'))
+    assert bifid5_square("B") == \
+        bifid5_square('BACDEFGHIKLMNOPQRSTUVWXYZ')
+    assert bifid6_square('B0') == \
+        bifid6_square('B0ACDEFGHIJKLMNOPQRSTUVWXYZ123456789')
diff --git a/sympy/utilities/tests/test_misc.py b/sympy/utilities/tests/test_misc.py
new file mode 100644
index 0000000000..14df99ab74
--- /dev/null
+++ b/sympy/utilities/tests/test_misc.py
@@ -0,0 +1,22 @@
+from sympy.core.compatibility import unichr
+from sympy.utilities.misc import translate, replace
+
+def test_translate():
+    abc = 'abc'
+    translate(abc, None, 'a') == 'bc'
+    translate(abc, None, '') == 'abc'
+    translate(abc, {'a': 'x'}, 'c') == 'xb'
+    assert translate(abc, {'a': 'bc'}, 'c') == 'bcb'
+    assert translate(abc, {'ab': 'x'}, 'c') == 'x'
+    assert translate(abc, {'ab': ''}, 'c') == ''
+    assert translate(abc, {'bc': 'x'}, 'c') == 'ab'
+    assert translate(abc, {'abc': 'x', 'a': 'y'}) == 'x'
+    u = unichr(4096)
+    assert translate(abc, 'a', 'x', u) == 'xbc'
+    assert (u in translate(abc, 'a', u, u)) is True
+
+
+def test_replace():
+    assert replace('abc', ('a', 'b')) == 'bbc'
+    assert replace('abc', {'a': 'Aa'}) == 'Aabc'
+    assert replace('abc', ('a', 'b'), ('c', 'C')) == 'bbC'