summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorSean Reifscheider <jafo@tummy.com>2011-02-22 10:55:44 (GMT)
committerSean Reifscheider <jafo@tummy.com>2011-02-22 10:55:44 (GMT)
commite2dfefbe85e0471c35062146a218aea2270ea600 (patch)
tree779b690c9e097108530f75b9bf443f8566bd93d7 /Lib
parentf3042782af65fbf68ca7e343357144c676b3fd54 (diff)
downloadcpython-e2dfefbe85e0471c35062146a218aea2270ea600.zip
cpython-e2dfefbe85e0471c35062146a218aea2270ea600.tar.gz
cpython-e2dfefbe85e0471c35062146a218aea2270ea600.tar.bz2
Issue #10924: Adding salt and Modular Crypt Format to crypt library.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/crypt.py61
-rw-r--r--Lib/test/test_crypt.py17
2 files changed, 78 insertions, 0 deletions
diff --git a/Lib/crypt.py b/Lib/crypt.py
new file mode 100644
index 0000000..dc62dba
--- /dev/null
+++ b/Lib/crypt.py
@@ -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))
diff --git a/Lib/test/test_crypt.py b/Lib/test/test_crypt.py
index 2adb28d..cb4234b 100644
--- a/Lib/test/test_crypt.py
+++ b/Lib/test/test_crypt.py
@@ -10,6 +10,23 @@ class CryptTestCase(unittest.TestCase):
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)