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

View File

@ -28,24 +28,28 @@
import llnl.util.tty as tty import llnl.util.tty as tty
"""Set of acceptable hashes that Spack will use.""" #: Set of hash algorithms that Spack can use, mapped to digest size in bytes
_hash_algorithms = [ hashes = {
'md5', 'md5': 16,
'sha1', 'sha1': 20,
'sha224', 'sha224': 28,
'sha256', 'sha256': 32,
'sha384', 'sha384': 48,
'sha512'] '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'] _deprecated_hash_algorithms = ['md5']
hashes = dict() #: cache of hash functions generated
_hash_functions = {}
"""Index for looking up hasher for a digest."""
_size_to_hash = dict()
class DeprecatedHash(object): class DeprecatedHash(object):
@ -65,22 +69,41 @@ def __call__(self, disable_alert=False):
return hashlib.new(self.hash_alg) return hashlib.new(self.hash_alg)
for h in _hash_algorithms: def hash_fun_for_algo(algo):
try: """Get a function that can perform the specified hash algorithm."""
if h in _deprecated_hash_algorithms: hash_gen = _hash_functions.get(algo)
hash_gen = DeprecatedHash( if hash_gen is None:
h, tty.debug, disable_security_check=False) if algo in _deprecated_hash_algorithms:
_size_to_hash[hash_gen(disable_alert=True).digest_size] = hash_gen 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: else:
hash_gen = getattr(hashlib, h) hash_gen = getattr(hashlib, algo)
_size_to_hash[hash_gen().digest_size] = hash_gen _hash_functions[algo] = hash_gen
hashes[h] = hash_gen
except ValueError: return hash_gen
# 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) def hash_algo_for_digest(hexdigest):
hashes[h] = hash_gen """Gets name of the hash algorithm for a hex digest."""
_size_to_hash[hash_gen(disable_alert=True).digest_size] = hash_gen 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): def checksum(hashlib_algo, filename, **kwargs):
@ -123,15 +146,8 @@ class Checker(object):
def __init__(self, hexdigest, **kwargs): def __init__(self, hexdigest, **kwargs):
self.block_size = kwargs.get('block_size', 2**20) self.block_size = kwargs.get('block_size', 2**20)
self.hexdigest = hexdigest self.hexdigest = hexdigest
self.sum = None self.sum = None
self.hash_fun = hash_fun_for_digest(hexdigest)
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]
@property @property
def hash_name(self): def hash_name(self):