IMMREX7
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function
import sys
import hashlib
from datetime import datetime
from . import backend
from .util import rand_bytes
from ._types import type_name, byte_cls, int_types
from ._errors import pretty_message
from ._ffi import new, deref
_backend = backend()
if _backend == 'mac':
from ._mac.util import pbkdf2, pkcs12_kdf
elif _backend == 'win' or _backend == 'winlegacy':
from ._win.util import pbkdf2, pkcs12_kdf
from ._win._kernel32 import kernel32, handle_error
else:
from ._openssl.util import pbkdf2, pkcs12_kdf
__all__ = [
'pbkdf1',
'pbkdf2',
'pbkdf2_iteration_calculator',
'pkcs12_kdf',
]
if sys.platform == 'win32':
def _get_start():
number = new(kernel32, 'LARGE_INTEGER *')
res = kernel32.QueryPerformanceCounter(number)
handle_error(res)
return deref(number)
def _get_elapsed(start):
length = _get_start() - start
return int(length / 1000.0)
else:
def _get_start():
return datetime.now()
def _get_elapsed(start):
length = datetime.now() - start
seconds = length.seconds + (length.days * 24 * 3600)
milliseconds = (length.microseconds / 10 ** 3)
return int(milliseconds + (seconds * 10 ** 3))
def pbkdf2_iteration_calculator(hash_algorithm, key_length, target_ms=100, quiet=False):
"""
Runs pbkdf2() twice to determine the approximate number of iterations to
use to hit a desired time per run. Use this on a production machine to
dynamically adjust the number of iterations as high as you can.
:param hash_algorithm:
The string name of the hash algorithm to use: "md5", "sha1", "sha224",
"sha256", "sha384", "sha512"
:param key_length:
The length of the desired key in bytes
:param target_ms:
The number of milliseconds the derivation should take
:param quiet:
If no output should be printed as attempts are made
:return:
An integer number of iterations of PBKDF2 using the specified hash
that will take at least target_ms
"""
if hash_algorithm not in set(['sha1', 'sha224', 'sha256', 'sha384', 'sha512']):
raise ValueError(pretty_message(
'''
hash_algorithm must be one of "sha1", "sha224", "sha256", "sha384",
"sha512", not %s
''',
repr(hash_algorithm)
))
if not isinstance(key_length, int_types):
raise TypeError(pretty_message(
'''
key_length must be an integer, not %s
''',
type_name(key_length)
))
if key_length < 1:
raise ValueError(pretty_message(
'''
key_length must be greater than 0 - is %s
''',
repr(key_length)
))
if not isinstance(target_ms, int_types):
raise TypeError(pretty_message(
'''
target_ms must be an integer, not %s
''',
type_name(target_ms)
))
if target_ms < 1:
raise ValueError(pretty_message(
'''
target_ms must be greater than 0 - is %s
''',
repr(target_ms)
))
if pbkdf2.pure_python:
raise OSError(pretty_message(
'''
Only a very slow, pure-python version of PBKDF2 is available,
making this function useless
'''
))
iterations = 10000
password = 'this is a test'.encode('utf-8')
salt = rand_bytes(key_length)
def _measure():
start = _get_start()
pbkdf2(hash_algorithm, password, salt, iterations, key_length)
observed_ms = _get_elapsed(start)
if not quiet:
print('%s iterations in %sms' % (iterations, observed_ms))
return 1.0 / target_ms * observed_ms
# Measure the initial guess, then estimate how many iterations it would
# take to reach 1/2 of the target ms and try it to get a good final number
fraction = _measure()
iterations = int(iterations / fraction / 2.0)
fraction = _measure()
iterations = iterations / fraction
# < 20,000 round to 1000
# 20,000-100,000 round to 5,000
# > 100,000 round to 10,000
round_factor = -3 if iterations < 100000 else -4
result = int(round(iterations, round_factor))
if result > 20000:
result = (result // 5000) * 5000
return result
def pbkdf1(hash_algorithm, password, salt, iterations, key_length):
"""
An implementation of PBKDF1 - should only be used for interop with legacy
systems, not new architectures
:param hash_algorithm:
The string name of the hash algorithm to use: "md2", "md5", "sha1"
:param password:
A byte string of the password to use an input to the KDF
:param salt:
A cryptographic random byte string
:param iterations:
The numbers of iterations to use when deriving the key
:param key_length:
The length of the desired key in bytes
:return:
The derived key as a byte string
"""
if not isinstance(password, byte_cls):
raise TypeError(pretty_message(
'''
password must be a byte string, not %s
''',
(type_name(password))
))
if not isinstance(salt, byte_cls):
raise TypeError(pretty_message(
'''
salt must be a byte string, not %s
''',
(type_name(salt))
))
if not isinstance(iterations, int_types):
raise TypeError(pretty_message(
'''
iterations must be an integer, not %s
''',
(type_name(iterations))
))
if iterations < 1:
raise ValueError(pretty_message(
'''
iterations must be greater than 0 - is %s
''',
repr(iterations)
))
if not isinstance(key_length, int_types):
raise TypeError(pretty_message(
'''
key_length must be an integer, not %s
''',
(type_name(key_length))
))
if key_length < 1:
raise ValueError(pretty_message(
'''
key_length must be greater than 0 - is %s
''',
repr(key_length)
))
if hash_algorithm not in set(['md2', 'md5', 'sha1']):
raise ValueError(pretty_message(
'''
hash_algorithm must be one of "md2", "md5", "sha1", not %s
''',
repr(hash_algorithm)
))
if key_length > 16 and hash_algorithm in set(['md2', 'md5']):
raise ValueError(pretty_message(
'''
key_length can not be longer than 16 for %s - is %s
''',
(hash_algorithm, repr(key_length))
))
if key_length > 20 and hash_algorithm == 'sha1':
raise ValueError(pretty_message(
'''
key_length can not be longer than 20 for sha1 - is %s
''',
repr(key_length)
))
algo = getattr(hashlib, hash_algorithm)
output = algo(password + salt).digest()
for _ in range(2, iterations + 1):
output = algo(output).digest()
return output[:key_length]
Copyright © 2021 -