core: make spack.util.crypto initialization less expensive.

- This hard-codes the hash lengths rather than computing them on import.

- Also cleans up the code in `spack.util.crypto` to make it easier to
  understand.
This commit is contained in:
Todd Gamblin 2018-07-22 23:40:28 -07:00
parent 674eb00e53
commit 656e935e50
2 changed files with 55 additions and 39 deletions

View File

@ -52,7 +52,7 @@ def test_fetch(
mock_archive.url
mock_archive.path
algo = crypto.hashes[checksum_type]()
algo = crypto.hash_fun_for_algo(checksum_type)()
with open(mock_archive.archive_file, 'rb') as f:
algo.update(f.read())
checksum = algo.hexdigest()
@ -145,7 +145,7 @@ def test_from_list_url(mock_packages, config):
def test_hash_detection(checksum_type):
algo = crypto.hashes[checksum_type]()
algo = crypto.hash_fun_for_algo(checksum_type)()
h = 'f' * (algo.digest_size * 2) # hex -> bytes
checker = crypto.Checker(h)
assert checker.hash_name == checksum_type

View File

@ -28,24 +28,28 @@
import llnl.util.tty as tty
"""Set of acceptable hashes that Spack will use."""
_hash_algorithms = [
'md5',
'sha1',
'sha224',
'sha256',
'sha384',
'sha512']
#: Set of hash algorithms that Spack can use, mapped to digest size in bytes
hashes = {
'md5': 16,
'sha1': 20,
'sha224': 28,
'sha256': 32,
'sha384': 48,
'sha512': 64
}
#: size of hash digests in bytes, mapped to algoritm names
_size_to_hash = dict((v, k) for k, v in hashes.items())
#: List of deprecated hash functions. On some systems, these cannot be
#: used without special options to hashlib.
_deprecated_hash_algorithms = ['md5']
hashes = dict()
"""Index for looking up hasher for a digest."""
_size_to_hash = dict()
#: cache of hash functions generated
_hash_functions = {}
class DeprecatedHash(object):
@ -65,22 +69,41 @@ def __call__(self, disable_alert=False):
return hashlib.new(self.hash_alg)
for h in _hash_algorithms:
try:
if h in _deprecated_hash_algorithms:
hash_gen = DeprecatedHash(
h, tty.debug, disable_security_check=False)
_size_to_hash[hash_gen(disable_alert=True).digest_size] = hash_gen
def hash_fun_for_algo(algo):
"""Get a function that can perform the specified hash algorithm."""
hash_gen = _hash_functions.get(algo)
if hash_gen is None:
if algo in _deprecated_hash_algorithms:
try:
hash_gen = DeprecatedHash(
algo, tty.debug, disable_security_check=False)
# call once to get a ValueError if usedforsecurity is needed
hash_gen(disable_alert=True)
except ValueError:
# Some systems may support the 'usedforsecurity' option
# so try with that (but display a warning when it is used)
hash_gen = DeprecatedHash(
algo, tty.warn, disable_security_check=True)
else:
hash_gen = getattr(hashlib, h)
_size_to_hash[hash_gen().digest_size] = hash_gen
hashes[h] = hash_gen
except ValueError:
# Some systems may support the 'usedforsecurity' option so try with
# that (but display a warning when it is used)
hash_gen = DeprecatedHash(h, tty.warn, disable_security_check=True)
hashes[h] = hash_gen
_size_to_hash[hash_gen(disable_alert=True).digest_size] = hash_gen
hash_gen = getattr(hashlib, algo)
_hash_functions[algo] = hash_gen
return hash_gen
def hash_algo_for_digest(hexdigest):
"""Gets name of the hash algorithm for a hex digest."""
bytes = len(hexdigest) / 2
if bytes not in _size_to_hash:
raise ValueError(
'Spack knows no hash algorithm for this digest: %s' % hexdigest)
return _size_to_hash[bytes]
def hash_fun_for_digest(hexdigest):
"""Gets a hash function corresponding to a hex digest."""
return hash_fun_for_algo(hash_algo_for_digest(hexdigest))
def checksum(hashlib_algo, filename, **kwargs):
@ -123,15 +146,8 @@ class Checker(object):
def __init__(self, hexdigest, **kwargs):
self.block_size = kwargs.get('block_size', 2**20)
self.hexdigest = hexdigest
self.sum = None
bytes = len(hexdigest) / 2
if bytes not in _size_to_hash:
raise ValueError(
'Spack knows no hash algorithm for this digest: %s'
% hexdigest)
self.hash_fun = _size_to_hash[bytes]
self.sum = None
self.hash_fun = hash_fun_for_digest(hexdigest)
@property
def hash_name(self):