Issue #10924: Adding salt and Modular Crypt Format to crypt library.
This commit is contained in:
parent
f3042782af
commit
e2dfefbe85
|
@ -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"
|
||||||
|
|
|
@ -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))
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
2
setup.py
2
setup.py
|
@ -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']) )
|
||||||
|
|
Loading…
Reference in New Issue