diff -crN python3-trunk/Doc/library/crypt.rst py3/Doc/library/crypt.rst *** python3-trunk/Doc/library/crypt.rst Thu Jul 23 18:35:12 2009 --- py3/Doc/library/crypt.rst Mon Jan 17 14:09:50 2011 *************** *** 15,23 **** 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 ! page for further details. Possible uses include allowing Python scripts to ! accept typed passwords from the user, or attempting to crack Unix passwords with ! a dictionary. .. index:: single: crypt(3) --- 15,23 ---- 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 ! page for further details. Possible uses include storing hashed passwords ! so you can check passwords without storing the actual password, or attempting ! to crack Unix passwords with a dictionary. .. index:: single: crypt(3) *************** *** 26,40 **** extensions available on the current implementation will also be available on this module. ! .. function:: crypt(word, salt) *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 ! to perturb the DES algorithm in one of 4096 ways. The characters in *salt* must ! be in the set ``[./a-zA-Z0-9]``. Returns the hashed password as a string, which ! will be composed of characters from the same alphabet as the salt (the first two ! characters represent the salt itself). .. index:: single: crypt(3) --- 26,92 ---- extensions available on the current implementation will also be available on this module. + Hashing Methods + --------------- ! 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 ! interface. The optional *salt* is either a string as returned from ! :func:`mksalt`, one of the ``crypt.METHOD_*`` values (though not all ! may be available on all platforms), or a full encrypted password ! including salt, as returned by this function. If *salt* is not ! 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) *************** *** 42,47 **** --- 94,127 ---- different sizes in the *salt*, it is recommended to use the full crypted 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:: import crypt, getpass, pwd *************** *** 57,59 **** --- 137,147 ---- else: 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" diff -crN python3-trunk/Lib/crypt.py py3/Lib/crypt.py *** python3-trunk/Lib/crypt.py Wed Dec 31 17:00:00 1969 --- py3/Lib/crypt.py Mon Jan 17 14:16:35 2011 *************** *** 0 **** --- 1,122 ---- + '''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 '' % 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)) + '''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 '' % 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)) diff -crN python3-trunk/Lib/test/test_crypt.py py3/Lib/test/test_crypt.py *** python3-trunk/Lib/test/test_crypt.py Fri Mar 19 17:06:52 2010 --- py3/Lib/test/test_crypt.py Mon Jan 17 14:16:13 2011 *************** *** 10,15 **** --- 10,32 ---- if support.verbose: 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(): support.run_unittest(CryptTestCase) diff -crN python3-trunk/Modules/Setup.dist py3/Modules/Setup.dist *** python3-trunk/Modules/Setup.dist Wed Sep 8 19:01:39 2010 --- py3/Modules/Setup.dist Mon Jan 17 14:10:29 2011 *************** *** 207,213 **** # # First, look at Setup.config; configure may have set this for you. ! #crypt cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems # Some more UNIX dependent modules -- off by default, since these --- 207,213 ---- # # First, look at Setup.config; configure may have set this for you. ! #_crypt _cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems # Some more UNIX dependent modules -- off by default, since these diff -crN python3-trunk/Modules/_cryptmodule.c py3/Modules/_cryptmodule.c *** python3-trunk/Modules/_cryptmodule.c Wed Dec 31 17:00:00 1969 --- py3/Modules/_cryptmodule.c Mon Jan 17 14:15:56 2011 *************** *** 0 **** --- 1,62 ---- + /* cryptmodule.c - by Steve Majewski + */ + + #include "Python.h" + + #include + + #ifdef __VMS + #include + #endif + + /* Module crypt */ + + + static PyObject *crypt_crypt(PyObject *self, PyObject *args) + { + char *word, *salt; + #ifndef __VMS + extern char * crypt(const char *, const char *); + #endif + + if (!PyArg_ParseTuple(args, "ss:crypt", &word, &salt)) { + return NULL; + } + /* On some platforms (AtheOS) crypt returns NULL for an invalid + salt. Return None in that case. XXX Maybe raise an exception? */ + return Py_BuildValue("s", crypt(word, salt)); + + } + + PyDoc_STRVAR(crypt_crypt__doc__, + "crypt(word, salt) -> string\n\ + word will usually be a user's password. salt is a 2-character string\n\ + which will be used to select one of 4096 variations of DES. The characters\n\ + in salt must be either \".\", \"/\", or an alphanumeric character. Returns\n\ + the hashed password as a string, which will be composed of characters from\n\ + the same alphabet as the salt."); + + + static PyMethodDef crypt_methods[] = { + {"crypt", crypt_crypt, METH_VARARGS, crypt_crypt__doc__}, + {NULL, NULL} /* sentinel */ + }; + + + static struct PyModuleDef cryptmodule = { + PyModuleDef_HEAD_INIT, + "_crypt", + NULL, + -1, + crypt_methods, + NULL, + NULL, + NULL, + NULL + }; + + PyMODINIT_FUNC + PyInit__crypt(void) + { + return PyModule_Create(&cryptmodule); + } diff -crN python3-trunk/setup.py py3/setup.py *** python3-trunk/setup.py Sun Jan 2 16:29:39 2011 --- py3/setup.py Mon Jan 17 14:01:05 2011 *************** *** 636,642 **** libs = ['crypt'] else: libs = [] ! exts.append( Extension('crypt', ['cryptmodule.c'], libraries=libs) ) # CSV files exts.append( Extension('_csv', ['_csv.c']) ) --- 636,642 ---- libs = ['crypt'] else: libs = [] ! exts.append( Extension('_crypt', ['_cryptmodule.c'], libraries=libs) ) # CSV files exts.append( Extension('_csv', ['_csv.c']) )