113

I'm looking for a very quick way to generate an alphanumeric unique id for a primary key in a table.

Would something like this work?

def genKey():
    hash = hashlib.md5(RANDOM_NUMBER).digest().encode("base64")
    alnum_hash = re.sub(r'[^a-zA-Z0-9]', "", hash)
    return alnum_hash[:16]

What would be a good way to generate random numbers? If I base it on microtime, I have to account for the possibility of several calls of genKey() at the same time from different instances.

Or is there a better way to do all this?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
ensnare
  • 40,069
  • 64
  • 158
  • 224
  • 1
    possible duplicate of [Random string generation with upper case letters and digits in Python](http://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python) – Amir Ali Akbari Dec 26 '14 at 17:43

13 Answers13

190

As none of the answers provide you with a random string consisting of characters 0-9, a-z, A-Z: Here is a working solution which will give you one of approx. 62^16 = 4.76724 e+28 keys:

import random, string
x = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(16))
print(x)

It is also very readable without knowing ASCII codes by heart.

There is an even shorter version since python 3.6.2:

import random, string
x = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
print(x)
David Schumann
  • 13,380
  • 9
  • 75
  • 96
64

You can use this:

>>> import random
>>> ''.join(random.choice('0123456789ABCDEF') for i in range(16))
'E2C6B2E19E4A7777'

There is no guarantee that the keys generated will be unique so you should be ready to retry with a new key in the case the original insert fails. Also, you might want to consider using a deterministic algorithm to generate a string from an auto-incremented id instead of using random values, as this will guarantee you uniqueness (but it will also give predictable keys).

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 1
    random is not random but pseudo-random according to the documentation. Please use os.urandom instead. – nikola Mar 26 '10 at 13:13
  • 8
    @prometheus. is `os.urandom` not psuedo-random? – aaronasterling Sep 28 '10 at 22:04
  • 1
    I was responding to Mark Byers loose usage of the term "random values". ``os.urandom`` is still pseudo-random, but cryptographically secure pseudo-random, which makes it much more suitable for a wide range of use cases compared to ``random``. – nikola Feb 10 '13 at 19:25
  • 1
    @nikola its doesnt really matter if the keys are only pseudo random, they are used for indexing. – yamm May 07 '15 at 11:35
  • 3
    Perhaps obvious, but 'deterministic' doesn't mean unique, you have to actually check that the algorithm has a very long repetition period. `get_key = lambda n: n % 10` is deterministic, but not unique for long. – Mark Oct 09 '15 at 18:42
  • 1
    This answer only uses characters A-F whereas the question strongly suggests a-z and A-Z are desired. – Hugh W Mar 13 '17 at 17:27
44

In Python 3.6, released in December 2016, the secrets module was introduced.

You can now generate a random token this way :

import secrets

secrets.token_hex(16)

From the Python docs :

The secrets module is used for generating cryptographically strong random numbers suitable for managing data such as passwords, account authentication, security tokens, and related secrets.

In particularly, secrets should be used in preference to the default pseudo-random number generator in the random module, which is designed for modelling and simulation, not security or cryptography.

https://docs.python.org/3/library/secrets.html

Brachamul
  • 1,886
  • 2
  • 21
  • 34
  • 3
    Docstring: "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." Meaning `32 == len(secrets.token_hex(16))`. – Edward Corrigall May 09 '21 at 20:13
43

Have a look at the uuid module (Python 2.5+).

A quick example:

import uuid
uid = uuid.uuid4()
print(uid.hex)
df008b2e24f947b1b873c94d8a3f2201

Note that the OP asked for a 16-character alphanumeric string, but UUID4 strings are 32 characters long. You should not truncate this string, instead, use the complete 32 characters.

Jurakin
  • 832
  • 1
  • 5
  • 19
ChristopheD
  • 112,638
  • 29
  • 165
  • 179
  • 7
    This is 32 characters, and truncation of Guids is unsafe. – Brian Mar 24 '10 at 21:03
  • True (about the truncation). On the other hand: I'd just store 32 characters (unless you have a very specific reason to only store 16). – ChristopheD Mar 24 '10 at 21:09
  • 1
    @Brian Hi, I need to know why guids is not safe? do you have some reference? – Adiyat Mubarak Nov 30 '18 at 01:19
  • 1
    @AdiyatMubarak: Fundamentally, you don't need a reference. Guids are documented as being unique. Half of a Guid is not documented as being unique. That said, https://blogs.msdn.microsoft.com/oldnewthing/20080627-00/?p=21823 runs through what happens when you truncate one particular GUID algorithm . – Brian Nov 30 '18 at 14:12
  • this returns hexadecimal with letters only from a - f. – kta May 04 '23 at 01:48
10

There's an official recipe:

import string
import secrets
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(16))
print(password)

This will create output similar to 'STCT3jdDUkppph03'.

mathandy
  • 1,892
  • 25
  • 32
5

For random numbers a good source is os.urandom:

import os
import hashlib
random_data = os.urandom(128)
hashlib.md5(random_data).hexdigest()[:16]
Jurakin
  • 832
  • 1
  • 5
  • 19
rlotun
  • 7,897
  • 4
  • 28
  • 23
  • I forgot the so much great urandom function :V and that's nice, better than adding charsets into a string then loop then. Builtin ;) – m3nda Jan 17 '16 at 20:26
  • 1
    this has been mentioned in other answers too, you should not truncate the md5 hash. – bman Nov 04 '16 at 06:22
  • @bman: I'm aware that there a serious issues truncating vertan UUIDs because the randomness is not linearly distributed. vor MD5 this should not be an issue. – max Jun 24 '17 at 19:33
  • @rlotun You can't just cut out the hash and say it's random. If you do that, the hash function won't be guaranteed to be random. – Jurakin Feb 11 '23 at 17:33
3
import random
''.join(random.sample(map(chr, range(48, 57) + range(65, 90) + range(97, 122)), 16))

Outputs something like:

'CDh0geq3NpKtcXfP'
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Jan Matějka
  • 1,880
  • 1
  • 14
  • 31
  • 4
    Your solution would omit characters 9, Z and z. Also, sample() chooses every character only once. So it would give you a lot less permutations. This would give you a string of 16 random digits and upper/lower case letters: `''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(6666))` – David Schumann Jun 11 '15 at 11:13
2

This value is incremented by 1 on each call (it wraps around). Deciding where the best place to store the value will depend on how you are using it. You may find this explanation of interest, as it discusses not only how Guids work but also how to make a smaller one.

The short answer is this: Use some of those characters as a timestamp and the other characters as a "uniquifier," a value increments by 1 on each call to your uid generator.

Brian
  • 25,523
  • 18
  • 82
  • 173
2

I would prefer urandom over secrets.token_hex, as it samples from a richer character set and hence needs a smaller length to achieve the same entropy.

os.urandom, which reads from urandom, is considered secure (see the relevant answer in a question if urandom is secure). You can then read as much as you like from urandom and produce a random alphanummeric as follows:

import math
import os
def random_alphanumeric(str_len: int) -> str:
  rand_len = 3 * (math.ceil(str_len / 3) + 1)
  return base64.b64encode(os.urandom(rand_len), altchars=b'aA').decode('ascii')[:str_len]

NOTE: The above function is not secure. Since you need a "very quick way to generate an alphanumeric", this function sacrifices performance over security, since the frequencies of a and A (or whatever characters you choose to replace + and / with) will be increased compared to what urandom would give you otherwise.

If you put randomness above performance, you could do something like:

def secure_random_alphanumeric(str_len: int) -> str:
  ret = ''
  while len(ret) < str_len:
    rand_len = 3 * (math.ceil((str_len - len(ret)) / 3) + 2)
    ret += base64.b64encode(os.urandom(rand_len)).decode('ascii').replace('+', '').replace('/', '').replace('=', '')
  return ret[:str_len]

Note that chaining replace turns out to be faster than sequntially calling it, as per this answer.

Also, in the above, +1 is replaced by +2 when determining rand_lento reduce the number of iterations needed to achieve the requested length. You could even replace by +3 or more to reduce even more the possibility for an iteration, but then you would loose in performance at the chained replace calls.

pac
  • 83
  • 8
0
import math
import secrets


def random_alphanum(length: int) -> str:
    if length == 0:
        return ''
    elif length < 0:
        raise ValueError('negative argument not allowed')
    else:
        text = secrets.token_hex(nbytes=math.ceil(length / 2))
        is_length_even = length % 2 == 0
        return text if is_length_even else text[1:]
  • uuid method is inefficient and limited because uuid only returns 36 characters, and is then truncated.
  • default psuedo-random number generator is not suitable for security or cryptographic applications, a standard module secrets is available and is designed for these applications.
-3

Simply use python builtin uuid:

If UUIDs are okay for your purposes use the built in uuid package.

One Line Solution:

import uuid
str(uuid.uuid4().get_hex().upper()[0:16])

Outputs something like:

'40003A9B8C3045CA'
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Jaykumar Patel
  • 26,836
  • 12
  • 74
  • 76
-3

simply use python inbuilt uuid :

import uuid
print uuid.uuid4().hex[:16].upper()
Chirag Maliwal
  • 442
  • 11
  • 25
-3

You could use the choice function in np.random which chooses the number of characters specified from a list of characters:

import numpy as np
chars = np.array(list('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'))
np_codes = np.random.choice(chars,16)
print(''.join([val for val in np_codes]))

this outputs something like the following: 591FXwW61F4Q57av