Can someone share the best practices for creating a nonce for an OAuth request in Python?
Here's what rauth does. There's not really hard and fast rules here. The spec doesn't seem too opinionated. Your constraints are that the value, being a nonce, should be unique. Other than that, assuming the provider doesn't complain, you can use whatever method you like.
While this probably does not exist at the time of this question creation, Python 3.6 introduced the secrets module which is meant for generating cryptographically strong random numbers suitable for managing data such as passwords, account authentication, security tokens, and related secrets.
In this case, generating a nonce can be generated easily (here a base64 encoded string):
nonce = secrets.token_urlsafe()
Alternatives are token_bytes to get a binary token or token_hex to get an hexadecimal string.
For most practical purposes this gives very good nonce:
import uuid
uuid.uuid4().hex
# 'b46290528cd949498ce4cc86ca854173'
uuid4()
uses os.urandom()
which is best random you can get in python.
Nonce should be used only once and hard to predict. Note that uuid4()
is harder to predict than uuid1()
whereas later is more globally unique. So you can achieve even more strength by combining them:
uuid.uuid4().hex + uuid.uuid1().hex
# 'a6d68f4d81ec440fb3d5ef6416079305f7a44a0c9e9011e684e2c42c0319303d'
Here's how python-oauth2 does it:
def generate_nonce(length=8):
"""Generate pseudorandom number."""
return ''.join([str(random.randint(0, 9)) for i in range(length)])
They also have:
@classmethod
def make_nonce(cls):
"""Generate pseudorandom number."""
return str(random.randint(0, 100000000))
Additionally there is this issue entitled: "make_nonce is not random enough", which proposes:
def gen_nonce(length):
""" Generates a random string of bytes, base64 encoded """
if length < 1:
return ''
string=base64.b64encode(os.urandom(length),altchars=b'-_')
b64len=4*floor(length,3)
if length%3 == 1:
b64len+=2
elif length%3 == 2:
b64len+=3
return string[0:b64len].decode()
And also references CVE-2013-4347. TL;DR version, use os.urandom
or the abstracted interface to it (SystemRandom).
I like my lambda
s—and didn't want non-alphanumeric characters—so I used this:
lambda length: filter(lambda s: s.isalpha(), b64encode(urandom(length * 2)))[:length]
Here are a few ideas I got for emailage. The generate_nonce comes from their code, but I use generate_nonce_timestamp which I used uuid for. It gives me a random alpha-numeric string and a time stamp in seconds:
import random
import time
import uuid
def generate_nonce(length=8):
"""Generate pseudo-random number."""
return ''.join([str(random.randint(0, 9)) for i in range(length)])
def generate_timestamp():
"""Get seconds since epoch (UTC)."""
return str(int(time.time()))
def generate_nonce_timestamp():
"""Generate pseudo-random number and seconds since epoch (UTC)."""
nonce = uuid.uuid1()
oauth_timestamp, oauth_nonce = str(nonce.time), nonce.hex
return oauth_nonce, oauth_timestamp
I like using uuid1, since it generates the uuid based on current host and time and has the time property that you can extract if you need both. For emailage, you need both the timestamp and the nonce.
Here is what you get:
>>> generate_nonce_timestamp()
('a89faa84-6c35-11e5-8a36-080027c336f0', '136634341422770820')
If you want to remove the -
, use nonce.get_hex()
.
uuid1 - Generate a UUID from a host ID, sequence number, and the current time. More on uuid.