[Python-checkins] r88500 - in python/branches/py3k: Doc/library/crypt.rst Lib/crypt.py Lib/test/test_crypt.py Misc/NEWS Modules/Setup.dist Modules/_cryptmodule.c Modules/cryptmodule.c setup.py

sean.reifschneider python-checkins at python.org
Tue Feb 22 11:55:44 CET 2011


Author: sean.reifschneider
Date: Tue Feb 22 11:55:44 2011
New Revision: 88500

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


Added:
   python/branches/py3k/Lib/crypt.py
   python/branches/py3k/Modules/_cryptmodule.c
      - copied, changed from r88042, /python/branches/py3k/Modules/cryptmodule.c
Removed:
   python/branches/py3k/Modules/cryptmodule.c
Modified:
   python/branches/py3k/Doc/library/crypt.rst
   python/branches/py3k/Lib/test/test_crypt.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Modules/Setup.dist
   python/branches/py3k/setup.py

Modified: python/branches/py3k/Doc/library/crypt.rst
==============================================================================
--- python/branches/py3k/Doc/library/crypt.rst	(original)
+++ python/branches/py3k/Doc/library/crypt.rst	Tue Feb 22 11:55:44 2011
@@ -15,9 +15,9 @@
 
 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.
+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,15 +26,67 @@
 extensions available on the current implementation will also  be available on
 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
-   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).
+   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,6 +94,34 @@
    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,3 +137,11 @@
        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"

Added: python/branches/py3k/Lib/crypt.py
==============================================================================
--- (empty file)
+++ python/branches/py3k/Lib/crypt.py	Tue Feb 22 11:55:44 2011
@@ -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))

Modified: python/branches/py3k/Lib/test/test_crypt.py
==============================================================================
--- python/branches/py3k/Lib/test/test_crypt.py	(original)
+++ python/branches/py3k/Lib/test/test_crypt.py	Tue Feb 22 11:55:44 2011
@@ -10,6 +10,23 @@
         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)
 

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Tue Feb 22 11:55:44 2011
@@ -27,6 +27,10 @@
 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 #11085: Moved collections abstract base classes into a separate

Modified: python/branches/py3k/Modules/Setup.dist
==============================================================================
--- python/branches/py3k/Modules/Setup.dist	(original)
+++ python/branches/py3k/Modules/Setup.dist	Tue Feb 22 11:55:44 2011
@@ -207,7 +207,7 @@
 #
 # 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

Copied: python/branches/py3k/Modules/_cryptmodule.c (from r88042, /python/branches/py3k/Modules/cryptmodule.c)
==============================================================================
--- /python/branches/py3k/Modules/cryptmodule.c	(original)
+++ python/branches/py3k/Modules/_cryptmodule.c	Tue Feb 22 11:55:44 2011
@@ -45,7 +45,7 @@
 
 static struct PyModuleDef cryptmodule = {
     PyModuleDef_HEAD_INIT,
-    "crypt",
+    "_crypt",
     NULL,
     -1,
     crypt_methods,
@@ -56,7 +56,7 @@
 };
 
 PyMODINIT_FUNC
-PyInit_crypt(void)
+PyInit__crypt(void)
 {
     return PyModule_Create(&cryptmodule);
 }

Deleted: python/branches/py3k/Modules/cryptmodule.c
==============================================================================
--- python/branches/py3k/Modules/cryptmodule.c	Tue Feb 22 11:55:44 2011
+++ (empty file)
@@ -1,62 +0,0 @@
-/* cryptmodule.c - by Steve Majewski
- */
-
-#include "Python.h"
-
-#include <sys/types.h>
-
-#ifdef __VMS
-#include <openssl/des.h>
-#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);
-}

Modified: python/branches/py3k/setup.py
==============================================================================
--- python/branches/py3k/setup.py	(original)
+++ python/branches/py3k/setup.py	Tue Feb 22 11:55:44 2011
@@ -636,7 +636,7 @@
             libs = ['crypt']
         else:
             libs = []
-        exts.append( Extension('crypt', ['cryptmodule.c'], libraries=libs) )
+        exts.append( Extension('_crypt', ['_cryptmodule.c'], libraries=libs) )
 
         # CSV files
         exts.append( Extension('_csv', ['_csv.c']) )


More information about the Python-checkins mailing list