Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e2dfefb

Browse files
author
Sean Reifscheider
committed
Issue #10924: Adding salt and Modular Crypt Format to crypt library.
1 parent f304278 commit e2dfefb

7 files changed

Lines changed: 183 additions & 13 deletions

File tree

Doc/library/crypt.rst

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
This module implements an interface to the :manpage:`crypt(3)` routine, which is
1717
a one-way hash function based upon a modified DES algorithm; see the Unix man
18-
page for further details. Possible uses include allowing Python scripts to
19-
accept typed passwords from the user, or attempting to crack Unix passwords with
20-
a dictionary.
18+
page for further details. Possible uses include storing hashed passwords
19+
so you can check passwords without storing the actual password, or attempting
20+
to crack Unix passwords with a dictionary.
2121

2222
.. index:: single: crypt(3)
2323

@@ -26,22 +26,102 @@ the :manpage:`crypt(3)` routine in the running system. Therefore, any
2626
extensions available on the current implementation will also be available on
2727
this module.
2828

29+
Hashing Methods
30+
---------------
2931

30-
.. function:: crypt(word, salt)
32+
The :mod:`crypt` module defines the list of hashing methods (not all methods
33+
are available on all platforms):
34+
35+
.. data:: METHOD_SHA512
36+
37+
A Modular Crypt Format method with 16 character salt and 86 character
38+
hash. This is the strongest method.
39+
40+
.. versionadded:: 3.3
41+
42+
.. data:: METHOD_SHA256
43+
44+
Another Modular Crypt Format method with 16 character salt and 43
45+
character hash.
46+
47+
.. versionadded:: 3.3
48+
49+
.. data:: METHOD_MD5
50+
51+
Another Modular Crypt Format method with 8 character salt and 22
52+
character hash.
53+
54+
.. versionadded:: 3.3
55+
56+
.. data:: METHOD_CRYPT
57+
58+
The traditional method with a 2 character salt and 13 characters of
59+
hash. This is the weakest method.
60+
61+
.. versionadded:: 3.3
62+
63+
Module Functions
64+
----------------
65+
66+
The :mod:`crypt` module defines the following functions:
67+
68+
.. function:: crypt(word, salt=None)
3169

3270
*word* will usually be a user's password as typed at a prompt or in a graphical
33-
interface. *salt* is usually a random two-character string which will be used
34-
to perturb the DES algorithm in one of 4096 ways. The characters in *salt* must
35-
be in the set ``[./a-zA-Z0-9]``. Returns the hashed password as a string, which
36-
will be composed of characters from the same alphabet as the salt (the first two
37-
characters represent the salt itself).
71+
interface. The optional *salt* is either a string as returned from
72+
:func:`mksalt`, one of the ``crypt.METHOD_*`` values (though not all
73+
may be available on all platforms), or a full encrypted password
74+
including salt, as returned by this function. If *salt* is not
75+
provided, the strongest method will be used (as returned by
76+
:func:`methods`.
77+
78+
Checking a password is usually done by passing the plain-text password
79+
as *word* and the full results of a previous :func:`crypt` call,
80+
which should be the same as the results of this call.
81+
82+
*salt* (either a random 2 or 16 character string, possibly prefixed with
83+
``$digit$`` to indicate the method) which will be used to perturb the
84+
encryption algorithm. The characters in *salt* must be in the set
85+
``[./a-zA-Z0-9]``, with the exception of Modular Crypt Format which
86+
prefixes a ``$digit$``.
87+
88+
Returns the hashed password as a string, which will be composed of
89+
characters from the same alphabet as the salt.
3890

3991
.. index:: single: crypt(3)
4092

4193
Since a few :manpage:`crypt(3)` extensions allow different values, with
4294
different sizes in the *salt*, it is recommended to use the full crypted
4395
password as salt when checking for a password.
4496

97+
.. versionchanged:: 3.3
98+
Before version 3.3, *salt* must be specified as a string and cannot
99+
accept ``crypt.METHOD_*`` values (which don't exist anyway).
100+
101+
.. function:: methods()
102+
103+
Return a list of available password hashing algorithms, as
104+
``crypt.METHOD_*`` objects. This list is sorted from strongest to
105+
weakest, and is guaranteed to have at least ``crypt.METHOD_CRYPT``.
106+
107+
.. versionadded:: 3.3
108+
109+
.. function:: mksalt(method=None)
110+
111+
Return a randomly generated salt of the specified method. If no
112+
*method* is given, the strongest method available as returned by
113+
:func:`methods` is used.
114+
115+
The return value is a string either of 2 characters in length for
116+
``crypt.METHOD_CRYPT``, or 19 characters starting with ``$digit$`` and
117+
16 random characters from the set ``[./a-zA-Z0-9]``, suitable for
118+
passing as the *salt* argument to :func:`crypt`.
119+
120+
.. versionadded:: 3.3
121+
122+
Examples
123+
--------
124+
45125
A simple example illustrating typical use::
46126

47127
import crypt, getpass, pwd
@@ -57,3 +137,11 @@ A simple example illustrating typical use::
57137
else:
58138
return 1
59139

140+
To generate a hash of a password using the strongest available method and
141+
check it against the original::
142+
143+
import crypt
144+
145+
hashed = crypt.crypt(plaintext)
146+
if hashed != crypt.crypt(plaintext, hashed):
147+
raise "Hashed version doesn't validate against original"

Lib/crypt.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'''Wrapper to the POSIX crypt library call and associated functionality.
2+
'''
3+
4+
import _crypt
5+
6+
saltchars = 'abcdefghijklmnopqrstuvwxyz'
7+
saltchars += saltchars.upper()
8+
saltchars += '0123456789./'
9+
10+
11+
class _MethodClass:
12+
'''Class representing a salt method per the Modular Crypt Format or the
13+
legacy 2-character crypt method.'''
14+
def __init__(self, name, ident, salt_chars, total_size):
15+
self.name = name
16+
self.ident = ident
17+
self.salt_chars = salt_chars
18+
self.total_size = total_size
19+
20+
def __repr__(self):
21+
return '<crypt.METHOD_%s>' % self.name
22+
23+
24+
# available salting/crypto methods
25+
METHOD_CRYPT = _MethodClass('CRYPT', None, 2, 13)
26+
METHOD_MD5 = _MethodClass('MD5', '1', 8, 34)
27+
METHOD_SHA256 = _MethodClass('SHA256', '5', 16, 63)
28+
METHOD_SHA512 = _MethodClass('SHA512', '6', 16, 106)
29+
30+
31+
def methods():
32+
'''Return a list of methods that are available in the platform ``crypt()``
33+
library, sorted from strongest to weakest. This is guaranteed to always
34+
return at least ``[METHOD_CRYPT]``'''
35+
method_list = [ METHOD_SHA512, METHOD_SHA256, METHOD_MD5 ]
36+
ret = [ method for method in method_list
37+
if len(crypt('', method)) == method.total_size ]
38+
ret.append(METHOD_CRYPT)
39+
return ret
40+
41+
42+
def mksalt(method = None):
43+
'''Generate a salt for the specified method. If not specified, the
44+
strongest available method will be used.'''
45+
import random
46+
47+
if method == None: method = methods()[0]
48+
s = '$%s$' % method.ident if method.ident else ''
49+
s += ''.join([ random.choice(saltchars) for x in range(method.salt_chars) ])
50+
return(s)
51+
52+
53+
def crypt(word, salt = None):
54+
'''Return a string representing the one-way hash of a password, preturbed
55+
by a salt. If ``salt`` is not specified or is ``None``, the strongest
56+
available method will be selected and a salt generated. Otherwise,
57+
``salt`` may be one of the ``crypt.METHOD_*`` values, or a string as
58+
returned by ``crypt.mksalt()``.'''
59+
if salt == None: salt = mksalt()
60+
elif isinstance(salt, _MethodClass): salt = mksalt(salt)
61+
return(_crypt.crypt(word, salt))

Lib/test/test_crypt.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ def test_crypt(self):
1010
if support.verbose:
1111
print('Test encryption: ', c)
1212

13+
def test_salt(self):
14+
self.assertEqual(len(crypt.saltchars), 64)
15+
for method in crypt.methods():
16+
salt = crypt.mksalt(method)
17+
self.assertEqual(len(salt),
18+
method.salt_chars + (3 if method.ident else 0))
19+
20+
def test_saltedcrypt(self):
21+
for method in crypt.methods():
22+
pw = crypt.crypt('assword', method)
23+
self.assertEqual(len(pw), method.total_size)
24+
pw = crypt.crypt('assword', crypt.mksalt(method))
25+
self.assertEqual(len(pw), method.total_size)
26+
27+
def test_methods(self):
28+
self.assertTrue(len(crypt.methods()) > 1)
29+
1330
def test_main():
1431
support.run_unittest(CryptTestCase)
1532

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ Core and Builtins
2727
Library
2828
-------
2929

30+
- Issue #10924: Adding salt and Modular Crypt Format to crypt library.
31+
Moved old C wrapper to _crypt, and added a Python wrapper with
32+
enhanced salt generation and simpler API for password generation.
33+
3034
- Issue #11074: Make 'tokenize' so it can be reloaded.
3135

3236
- Issue #11085: Moved collections abstract base classes into a separate

Modules/Setup.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ _symtable symtablemodule.c
207207
#
208208
# First, look at Setup.config; configure may have set this for you.
209209

210-
#crypt cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems
210+
#_crypt _cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems
211211

212212

213213
# Some more UNIX dependent modules -- off by default, since these
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ static PyMethodDef crypt_methods[] = {
4545

4646
static struct PyModuleDef cryptmodule = {
4747
PyModuleDef_HEAD_INIT,
48-
"crypt",
48+
"_crypt",
4949
NULL,
5050
-1,
5151
crypt_methods,
@@ -56,7 +56,7 @@ static struct PyModuleDef cryptmodule = {
5656
};
5757

5858
PyMODINIT_FUNC
59-
PyInit_crypt(void)
59+
PyInit__crypt(void)
6060
{
6161
return PyModule_Create(&cryptmodule);
6262
}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ def detect_modules(self):
636636
libs = ['crypt']
637637
else:
638638
libs = []
639-
exts.append( Extension('crypt', ['cryptmodule.c'], libraries=libs) )
639+
exts.append( Extension('_crypt', ['_cryptmodule.c'], libraries=libs) )
640640

641641
# CSV files
642642
exts.append( Extension('_csv', ['_csv.c']) )

0 commit comments

Comments
 (0)