Skip to content

Commit

Permalink
Simplify implementation of aiida.common.hashing.get_random_string
Browse files Browse the repository at this point in the history
This function is used only to generate a password for the database user
when a new profile is setup through `verdi quicksetup`. Although this
password is not even critical, back in the early days when this was
added, the implementation for the method was taken from Django.

As of Python 3.6 though, there exists a much simpler solution that is
based on the `secrets` built in library. See the following for details:

    https://docs.python.org/3/library/secrets.html#recipes-and-best-practices
  • Loading branch information
sphuber committed Apr 21, 2022
1 parent 8c50bd4 commit da7c611
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 41 deletions.
49 changes: 9 additions & 40 deletions aiida/common/hashing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
from itertools import chain
import numbers
from operator import itemgetter
import random
import time
import secrets
import string
import typing
import uuid

Expand All @@ -29,49 +29,18 @@

from .folders import Folder

# The prefix of the hashed using pbkdf2_sha256 algorithm in Django
HASHING_PREFIX_DJANGO = 'pbkdf2_sha256'
# The prefix of the hashed using pbkdf2_sha256 algorithm in Passlib
HASHING_PREFIX_PBKDF2_SHA256 = '$pbkdf2-sha256'

# This will never be a valid encoded hash
UNUSABLE_PASSWORD_PREFIX = '!' # noqa
# Number of random chars to add after UNUSABLE_PASSWORD_PREFIX
UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40
def get_random_string(length: int = 12) -> str:
"""Return a securely generated random string.
HASHING_KEY = 'HashingKey'
The default length of 12 with the all ASCII letters and digits returns a 71-bit value:
###################################################################
# THE FOLLOWING WAS TAKEN FROM DJANGO BUT IT CAN BE EASILY REPLACED
###################################################################
log_2((26+26+10)^12) =~ 71 bits
# Use the system PRNG if possible
try:
# pylint: disable=invalid-name
random = random.SystemRandom()
using_sysrandom = True
except NotImplementedError:
import warnings
warnings.warn('A secure pseudo-random number generator is not available. Falling back to Mersenne Twister.') # pylint: disable=no-member
using_sysrandom = False # pylint: disable=invalid-name


def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
"""
Returns a securely generated random string.
The default length of 12 with the a-z, A-Z, 0-9 character set returns
a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
:param length: The number of characters to use for the string.
"""
if not using_sysrandom:
# This is ugly, and a hack, but it makes things better than
# the alternative of predictability. This re-seeds the PRNG
# using a value that is hard for an attacker to predict, every
# time a random string is required. This may change the
# properties of the chosen random sequence slightly, but this
# is better than absolute predictability.
random.seed(hashlib.sha256(f'{random.getstate()}{time.time()}{HASHING_KEY}'.encode('utf-8')).digest())
return ''.join(random.choice(allowed_chars) for i in range(length))
alphabet = string.ascii_letters + string.digits
return ''.join(secrets.choice(alphabet) for i in range(length))


BLAKE2B_OPTIONS = {
Expand Down
1 change: 0 additions & 1 deletion open_source_licenses.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ Some files in AiiDA include snippets of code taken from other open-source
projects:

Django:
* aiida/common/hashing.py
* aiida/utils/timezone.py

Python:
Expand Down

0 comments on commit da7c611

Please sign in to comment.