Issue #10924: Adding salt and Modular Crypt Format to crypt library.

This commit is contained in:
Sean Reifscheider 2011-02-22 10:55:44 +00:00
parent f3042782af
commit e2dfefbe85
7 changed files with 183 additions and 13 deletions

View File

@ -15,9 +15,9 @@
This module implements an interface to the :manpage:`crypt(3)` routine, which is This module implements an interface to the :manpage:`crypt(3)` routine, which is
a one-way hash function based upon a modified DES algorithm; see the Unix man a one-way hash function based upon a modified DES algorithm; see the Unix man
page for further details. Possible uses include allowing Python scripts to page for further details. Possible uses include storing hashed passwords
accept typed passwords from the user, or attempting to crack Unix passwords with so you can check passwords without storing the actual password, or attempting
a dictionary. to crack Unix passwords with a dictionary.
.. index:: single: crypt(3) .. index:: single: crypt(3)
@ -26,15 +26,67 @@ the :manpage:`crypt(3)` routine in the running system. Therefore, any
extensions available on the current implementation will also be available on extensions available on the current implementation will also be available on
this module. this module.
Hashing Methods
---------------
.. function:: crypt(word, salt) The :mod:`crypt` module defines the list of hashing methods (not all methods
are available on all platforms):
.. data:: METHOD_SHA512
A Modular Crypt Format method with 16 character salt and 86 character
hash. This is the strongest method.
.. versionadded:: 3.3
.. data:: METHOD_SHA256
Another Modular Crypt Format method with 16 character salt and 43
character hash.
.. versionadded:: 3.3
.. data:: METHOD_MD5
Another Modular Crypt Format method with 8 character salt and 22
character hash.
.. versionadded:: 3.3
.. data:: METHOD_CRYPT
The traditional method with a 2 character salt and 13 characters of
hash. This is the weakest method.
.. versionadded:: 3.3
Module Functions
----------------
The :mod:`crypt` module defines the following functions:
.. function:: crypt(word, salt=None)
*word* will usually be a user's password as typed at a prompt or in a graphical *word* will usually be a user's password as typed at a prompt or in a graphical
interface. *salt* is usually a random two-character string which will be used interface. The optional *salt* is either a string as returned from
to perturb the DES algorithm in one of 4096 ways. The characters in *salt* must :func:`mksalt`, one of the ``crypt.METHOD_*`` values (though not all
be in the set ``[./a-zA-Z0-9]``. Returns the hashed password as a string, which may be available on all platforms), or a full encrypted password
will be composed of characters from the same alphabet as the salt (the first two including salt, as returned by this function. If *salt* is not
characters represent the salt itself). provided, the strongest method will be used (as returned by
:func:`methods`.
Checking a password is usually done by passing the plain-text password
as *word* and the full results of a previous :func:`crypt` call,
which should be the same as the results of this call.
*salt* (either a random 2 or 16 character string, possibly prefixed with
``$digit$`` to indicate the method) which will be used to perturb the
encryption algorithm. The characters in *salt* must be in the set
``[./a-zA-Z0-9]``, with the exception of Modular Crypt Format which
prefixes a ``$digit$``.
Returns the hashed password as a string, which will be composed of
characters from the same alphabet as the salt.
.. index:: single: crypt(3) .. index:: single: crypt(3)
@ -42,6 +94,34 @@ this module.
different sizes in the *salt*, it is recommended to use the full crypted different sizes in the *salt*, it is recommended to use the full crypted
password as salt when checking for a password. password as salt when checking for a password.
.. versionchanged:: 3.3
Before version 3.3, *salt* must be specified as a string and cannot
accept ``crypt.METHOD_*`` values (which don't exist anyway).
.. function:: methods()
Return a list of available password hashing algorithms, as
``crypt.METHOD_*`` objects. This list is sorted from strongest to
weakest, and is guaranteed to have at least ``crypt.METHOD_CRYPT``.
.. versionadded:: 3.3
.. function:: mksalt(method=None)
Return a randomly generated salt of the specified method. If no
*method* is given, the strongest method available as returned by
:func:`methods` is used.
The return value is a string either of 2 characters in length for
``crypt.METHOD_CRYPT``, or 19 characters starting with ``$digit$`` and
16 random characters from the set ``[./a-zA-Z0-9]``, suitable for
passing as the *salt* argument to :func:`crypt`.
.. versionadded:: 3.3
Examples
--------
A simple example illustrating typical use:: A simple example illustrating typical use::
import crypt, getpass, pwd import crypt, getpass, pwd
@ -57,3 +137,11 @@ A simple example illustrating typical use::
else: else:
return 1 return 1
To generate a hash of a password using the strongest available method and
check it against the original::
import crypt
hashed = crypt.crypt(plaintext)
if hashed != crypt.crypt(plaintext, hashed):
raise "Hashed version doesn't validate against original"

61
Lib/crypt.py Normal file
View File

@ -0,0 +1,61 @@
'''Wrapper to the POSIX crypt library call and associated functionality.
'''
import _crypt
saltchars = 'abcdefghijklmnopqrstuvwxyz'
saltchars += saltchars.upper()
saltchars += '0123456789./'
class _MethodClass:
'''Class representing a salt method per the Modular Crypt Format or the
legacy 2-character crypt method.'''
def __init__(self, name, ident, salt_chars, total_size):
self.name = name
self.ident = ident
self.salt_chars = salt_chars
self.total_size = total_size
def __repr__(self):
return '<crypt.METHOD_%s>' % self.name
# available salting/crypto methods
METHOD_CRYPT = _MethodClass('CRYPT', None, 2, 13)
METHOD_MD5 = _MethodClass('MD5', '1', 8, 34)
METHOD_SHA256 = _MethodClass('SHA256', '5', 16, 63)
METHOD_SHA512 = _MethodClass('SHA512', '6', 16, 106)
def methods():
'''Return a list of methods that are available in the platform ``crypt()``
library, sorted from strongest to weakest. This is guaranteed to always
return at least ``[METHOD_CRYPT]``'''
method_list = [ METHOD_SHA512, METHOD_SHA256, METHOD_MD5 ]
ret = [ method for method in method_list
if len(crypt('', method)) == method.total_size ]
ret.append(METHOD_CRYPT)
return ret
def mksalt(method = None):
'''Generate a salt for the specified method. If not specified, the
strongest available method will be used.'''
import random
if method == None: method = methods()[0]
s = '$%s$' % method.ident if method.ident else ''
s += ''.join([ random.choice(saltchars) for x in range(method.salt_chars) ])
return(s)
def crypt(word, salt = None):
'''Return a string representing the one-way hash of a password, preturbed
by a salt. If ``salt`` is not specified or is ``None``, the strongest
available method will be selected and a salt generated. Otherwise,
``salt`` may be one of the ``crypt.METHOD_*`` values, or a string as
returned by ``crypt.mksalt()``.'''
if salt == None: salt = mksalt()
elif isinstance(salt, _MethodClass): salt = mksalt(salt)
return(_crypt.crypt(word, salt))

View File

@ -10,6 +10,23 @@ class CryptTestCase(unittest.TestCase):
if support.verbose: if support.verbose:
print('Test encryption: ', c) print('Test encryption: ', c)
def test_salt(self):
self.assertEqual(len(crypt.saltchars), 64)
for method in crypt.methods():
salt = crypt.mksalt(method)
self.assertEqual(len(salt),
method.salt_chars + (3 if method.ident else 0))
def test_saltedcrypt(self):
for method in crypt.methods():
pw = crypt.crypt('assword', method)
self.assertEqual(len(pw), method.total_size)
pw = crypt.crypt('assword', crypt.mksalt(method))
self.assertEqual(len(pw), method.total_size)
def test_methods(self):
self.assertTrue(len(crypt.methods()) > 1)
def test_main(): def test_main():
support.run_unittest(CryptTestCase) support.run_unittest(CryptTestCase)

View File

@ -27,6 +27,10 @@ Core and Builtins
Library Library
------- -------
- Issue #10924: Adding salt and Modular Crypt Format to crypt library.
Moved old C wrapper to _crypt, and added a Python wrapper with
enhanced salt generation and simpler API for password generation.
- Issue #11074: Make 'tokenize' so it can be reloaded. - Issue #11074: Make 'tokenize' so it can be reloaded.
- Issue #11085: Moved collections abstract base classes into a separate - Issue #11085: Moved collections abstract base classes into a separate

View File

@ -207,7 +207,7 @@ _symtable symtablemodule.c
# #
# First, look at Setup.config; configure may have set this for you. # First, look at Setup.config; configure may have set this for you.
#crypt cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems #_crypt _cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems
# Some more UNIX dependent modules -- off by default, since these # Some more UNIX dependent modules -- off by default, since these

View File

@ -45,7 +45,7 @@ static PyMethodDef crypt_methods[] = {
static struct PyModuleDef cryptmodule = { static struct PyModuleDef cryptmodule = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
"crypt", "_crypt",
NULL, NULL,
-1, -1,
crypt_methods, crypt_methods,
@ -56,7 +56,7 @@ static struct PyModuleDef cryptmodule = {
}; };
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit_crypt(void) PyInit__crypt(void)
{ {
return PyModule_Create(&cryptmodule); return PyModule_Create(&cryptmodule);
} }

View File

@ -636,7 +636,7 @@ class PyBuildExt(build_ext):
libs = ['crypt'] libs = ['crypt']
else: else:
libs = [] libs = []
exts.append( Extension('crypt', ['cryptmodule.c'], libraries=libs) ) exts.append( Extension('_crypt', ['_cryptmodule.c'], libraries=libs) )
# CSV files # CSV files
exts.append( Extension('_csv', ['_csv.c']) ) exts.append( Extension('_csv', ['_csv.c']) )