diff options
author | Sean Reifscheider <jafo@tummy.com> | 2011-02-22 10:55:44 (GMT) |
---|---|---|
committer | Sean Reifscheider <jafo@tummy.com> | 2011-02-22 10:55:44 (GMT) |
commit | e2dfefbe85e0471c35062146a218aea2270ea600 (patch) | |
tree | 779b690c9e097108530f75b9bf443f8566bd93d7 /Lib | |
parent | f3042782af65fbf68ca7e343357144c676b3fd54 (diff) | |
download | cpython-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.py | 61 | ||||
-rw-r--r-- | Lib/test/test_crypt.py | 17 |
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) |