"Fossies" - the Fresh Open Source Software Archive 
Member "salt-3002.2/salt/utils/pycrypto.py" (18 Nov 2020, 4684 Bytes) of package /linux/misc/salt-3002.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "pycrypto.py" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
3002.1_vs_3002.2.
1 """
2 Use pycrypto to generate random passwords on the fly.
3 """
4
5 # Import python libraries
6
7 import logging
8 import random
9 import re
10 import string
11
12 import salt.utils.stringutils
13 from salt.exceptions import CommandExecutionError, SaltInvocationError
14
15 try:
16 try:
17 from M2Crypto.Rand import rand_bytes as get_random_bytes
18 except ImportError:
19 try:
20 from Cryptodome.Random import get_random_bytes # pylint: disable=E0611
21 except ImportError:
22 from Crypto.Random import get_random_bytes # pylint: disable=E0611
23 HAS_RANDOM = True
24 except ImportError:
25 HAS_RANDOM = False
26
27 try:
28 import crypt
29
30 HAS_CRYPT = True
31 except ImportError:
32 HAS_CRYPT = False
33
34 try:
35 import passlib.context
36
37 HAS_PASSLIB = True
38 except ImportError:
39 HAS_PASSLIB = False
40
41 log = logging.getLogger(__name__)
42
43
44 def secure_password(length=20, use_random=True):
45 """
46 Generate a secure password.
47 """
48 try:
49 length = int(length)
50 pw = ""
51 while len(pw) < length:
52 if HAS_RANDOM and use_random:
53 while True:
54 try:
55 char = salt.utils.stringutils.to_str(get_random_bytes(1))
56 break
57 except UnicodeDecodeError:
58 continue
59 pw += re.sub(
60 salt.utils.stringutils.to_str(r"[\W_]"),
61 "", # future lint: disable=blacklisted-function
62 char,
63 )
64 else:
65 pw += random.SystemRandom().choice(string.ascii_letters + string.digits)
66 return pw
67 except Exception as exc: # pylint: disable=broad-except
68 log.exception("Failed to generate secure passsword")
69 raise CommandExecutionError(str(exc))
70
71
72 if HAS_CRYPT:
73 methods = {m.name.lower(): m for m in crypt.methods}
74 else:
75 methods = {}
76 known_methods = ["sha512", "sha256", "blowfish", "md5", "crypt"]
77
78
79 def _gen_hash_passlib(crypt_salt=None, password=None, algorithm=None):
80 """
81 Generate a /etc/shadow-compatible hash for a non-local system
82 """
83 # these are the passlib equivalents to the 'known_methods' defined in crypt
84 schemes = ["sha512_crypt", "sha256_crypt", "bcrypt", "md5_crypt", "des_crypt"]
85
86 ctx = passlib.context.CryptContext(schemes=schemes)
87
88 kwargs = {"secret": password, "scheme": schemes[known_methods.index(algorithm)]}
89 if crypt_salt and "$" in crypt_salt:
90 # this salt has a rounds specifier.
91 # passlib takes it as a separate parameter, split it out
92 roundsstr, split_salt = crypt_salt.split("$")
93 rounds = int(roundsstr.split("=")[-1])
94 kwargs.update({"salt": split_salt, "rounds": rounds})
95 else:
96 # relaxed = allow salts that are too long
97 kwargs.update({"salt": crypt_salt, "relaxed": True})
98 return ctx.hash(**kwargs)
99
100
101 def _gen_hash_crypt(crypt_salt=None, password=None, algorithm=None):
102 """
103 Generate /etc/shadow hash using the native crypt module
104 """
105 if crypt_salt is None:
106 # setting crypt_salt to the algorithm makes crypt generate
107 # a salt compatible with the specified algorithm.
108 crypt_salt = methods[algorithm]
109 else:
110 if algorithm != "crypt":
111 # all non-crypt algorithms are specified as part of the salt
112 crypt_salt = "${}${}".format(methods[algorithm].ident, crypt_salt)
113
114 try:
115 ret = crypt.crypt(password, crypt_salt)
116 except OSError:
117 ret = None
118 return ret
119
120
121 def gen_hash(crypt_salt=None, password=None, algorithm=None):
122 """
123 Generate /etc/shadow hash
124 """
125 if password is None:
126 password = secure_password()
127
128 if algorithm is None:
129 # prefer the most secure natively supported method
130 algorithm = crypt.methods[0].name.lower() if HAS_CRYPT else known_methods[0]
131
132 if algorithm == "crypt" and crypt_salt and len(crypt_salt) != 2:
133 log.warning("Hash salt is too long for 'crypt' hash.")
134
135 if HAS_CRYPT and algorithm in methods:
136 return _gen_hash_crypt(
137 crypt_salt=crypt_salt, password=password, algorithm=algorithm
138 )
139 elif HAS_PASSLIB and algorithm in known_methods:
140 return _gen_hash_passlib(
141 crypt_salt=crypt_salt, password=password, algorithm=algorithm
142 )
143 else:
144 raise SaltInvocationError(
145 "Cannot hash using '{}' hash algorithm. Natively supported "
146 "algorithms are: {}. If passlib is installed ({}), the supported "
147 "algorithms are: {}.".format(
148 algorithm, list(methods), HAS_PASSLIB, known_methods
149 )
150 )