7
\$\begingroup\$

This is a program that generates a random hex value.

# How this works:
#   uses random to get a random number
#   checks what hash method to use
#   uses that method to hash the random number
#   returns the value

import hashlib
import random as rand


def hexrandom(minint, maxint, shamode="sha1"):
    x = str(rand.randint(int(minint), int(maxint)))
    reval = None
    if shamode == "sha1":
        reval = hashlib.sha1(x.encode()).hexdigest()
    elif shamode == "sha224":
        reval = hashlib.sha224(x.encode()).hexdigest()
    elif shamode == "sha256":
        reval = hashlib.sha256(x.encode()).hexdigest()
    elif shamode == "sha384":
        reval = hashlib.sha384(x.encode()).hexdigest()
    elif shamode == "sha512":
        reval = hashlib.sha512(x.encode()).hexdigest()

    return reval

How it works is simple.
It creates a random number.
It then hashes that number.
Then, it .hexdigest()s that.
And then returns that.

By the way, I tried being descriptive with explaining it, but it kept saying that there was unformated code. Sorry about that. The big description will be in a .md file on the repository. MarkDown file here

Development of the module this will be here.

\$\endgroup\$
3
  • 5
    \$\begingroup\$ This is a strange way to get random bytes. randint internally gets 4 bytes to make an integer out of. Why not just get however many bytes you need (stackoverflow.com/questions/5495492/…) and then encode as hex if necessary? Additionally Python 3.6 has secrets.token_hex: docs.python.org/3/library/secrets.html#secrets.token_hex \$\endgroup\$ Commented Jan 24, 2018 at 5:52
  • 1
    \$\begingroup\$ @BaileyParker You should make that an answer. The obvious advice to get rid of the if chain has already been given, but the much more helpful advice to use a different algorithm has not. \$\endgroup\$
    – Graipher
    Commented Jan 24, 2018 at 10:24
  • \$\begingroup\$ What is the point of hashing a random number? The hash of a random number will not be any more random than the original random number. \$\endgroup\$
    – zvone
    Commented Jan 24, 2018 at 19:59

2 Answers 2

13
\$\begingroup\$

Anytime you have a stacked if with basically all of the same comparisions, you should consider another method.

Use a dict:

The most common way to do this sort of thing is a dict. The dict makes it easy to map a string to a method like:

sha_methods = dict(
    sha1=hashlib.sha1,
    sha224=hashlib.sha224,
    sha256=hashlib.sha256,
    sha384=hashlib.sha384,
    sha512=hashlib.sha512,
)

def hexrandom(minint, maxint, shamode="sha1"):
    if shamode not in sha_mthods:
        raise ValueError("Unknown hashlib method {}".format(shamode))
    x = str(rand.randint(int(minint), int(maxint)))
    return sha_methods[shamode](x.encode()).hexdigest()

Use a direct method lookup:

In this case, since the shamode string matches the method name hashlib, we can use getattr to directly lookup this method like:

def hexrandom(minint, maxint, shamode="sha1"):
    method = getattr(hashlib, shamode, None)
    if method is None:
        raise ValueError("Unknown hashlib method {}".format(shamode))
    x = str(rand.randint(int(minint), int(maxint)))
    return method(x.encode()).hexdigest() 
\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the advice. Any other advice? \$\endgroup\$
    – Person
    Commented Jan 24, 2018 at 4:37
  • \$\begingroup\$ @Person Add docstring to the function :) \$\endgroup\$
    – hjpotter92
    Commented Jan 24, 2018 at 5:05
  • 1
    \$\begingroup\$ Ah, yes. READIBILITY!. Also I think I followed PEP-8 \$\endgroup\$
    – Person
    Commented Jan 24, 2018 at 5:09
  • 2
    \$\begingroup\$ @Person depending on your needs - it's not obvious why you need to use a hashing algorithm as you don't appear to care that much about being able to reproduce things - since you appear to just be hashing a random integer - you may just want to do something like: binascii.hexlify(os.urandom(16)) and make a string from random bytes to start with... \$\endgroup\$ Commented Jan 24, 2018 at 11:12
2
\$\begingroup\$

There are a few strange things about your code. You are using SHA like a KDF, which is a function that generates randomness from a limited source of entropy. The strange thing is, you have a (virtually) unlimited source of randomness in either random.getrandombits() or os.urandom(). And you are asking for a random hex in a way that to me distinguishes it from random bytes. So let's clear up a few points:

  • Random bytes are random bytes. Hex is a different way of representing bytes (you can use hex to represent any string of bytes). What you are looking to do is generate random bytes (in your code, that number depends on which SHA you use--SHA1 for example is 20 bytes) and then encode that in hex (20 bytes is 40 hex digits)
  • I hope you are not using your code for cryptographic purposes. random is not cryptographically secure! And even if you were using a cryptographically secure source (such as os.urandom), using a hex-encoded hash of a cryptographic source of randomness is bad (every byte in your hex is limited to one of 0123456789abcedf, severely limiting the space)!
  • Instead of trying to stretch the randomness from a small source, you should instead just be getting random bytes of your desired length and then encoding them in hex (if needed)

If you're on Python 3.6, you can use secrets.token_bytes to get cryptographically secure random bytes or secrets.token_hex to get hex-encoded cryptographically secure random bytes. If you aren't on Python 3.6, you can achieve what secrets does with the following:

import binascii
import os

def token_bytes(nbytes):
    """Return a random byte string containing *nbytes* bytes.
    If *nbytes* is ``None`` or not supplied, a reasonable
    default is used.
    >>> token_bytes(16)  #doctest:+SKIP
    b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b'
    """
    return os.urandom(nbytes)

def token_hex(nbytes):
    """Return a random text string, in hexadecimal.
    The string has *nbytes* random bytes, each byte converted to two
    hex digits.  If *nbytes* is ``None`` or not supplied, a reasonable
    default is used.
    >>> token_hex(16)  #doctest:+SKIP
    'f9bf78b9a18ce6d46a0cd2b0b86df9da'
    """
    return binascii.hexlify(token_bytes(nbytes)).decode('ascii')

This is the exact code from the secrets module, so I'd contend it's python-endorsed way to generate the randomness you're looking for.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.