125

What is the most lightweight way to create a random string of 30 characters like the following?

ufhy3skj5nca0d2dfh9hwd2tbk9sw1

And an hexadecimal number of 30 digits like the followin?

8c6f78ac23b4a7b8c0182d7a89e9b1

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
xRobot
  • 25,579
  • 69
  • 184
  • 304

13 Answers13

142

I got a faster one for the hex output. Using the same t1 and t2 as above:

>>> t1 = timeit.Timer("''.join(random.choice('0123456789abcdef') for n in xrange(30))", "import random")
>>> t2 = timeit.Timer("binascii.b2a_hex(os.urandom(15))", "import os, binascii")
>>> t3 = timeit.Timer("'%030x' % random.randrange(16**30)", "import random")
>>> for t in t1, t2, t3:
...     t.timeit()
... 
28.165037870407104
9.0292739868164062
5.2836320400238037

t3 only makes one call to the random module, doesn't have to build or read a list, and then does the rest with string formatting.

Graham Lea
  • 5,797
  • 3
  • 40
  • 55
jcdyer
  • 18,616
  • 5
  • 42
  • 49
  • 7
    Nice. Just generate a random number 30 hex digits long and print it out. Obvious when pointed out. Nice one. – eemz May 06 '10 at 17:50
  • Interesting, I kind of forgot that Python (and the random module) handles bigints natively. – wump May 06 '10 at 18:49
  • 3
    See yaronf's answer below on using `string.hexdigits`: http://stackoverflow.com/a/15462293/311288 _"`string.hexdigits` returns `0123456789abcdefABCDEF` (both lowercase and uppercase), [...]. Instead, just use `random.choice('0123456789abcdef')`."_ – Thomas BDX Jun 27 '14 at 14:35
  • 5
    Use [`getrandbits`](https://docs.python.org/2/library/random.html#random.getrandbits) instead of `randrange` to make it even faster. – robinst Jun 08 '16 at 03:24
  • 1
    @robinst has a good point. `'%030x' % random.getrandbits(60)` is even faster than `'%030x' % random.randrange(16**30)`, likely because it doesn't haven't have to do any conversion to/from big-ints – Dan Lenski Aug 06 '18 at 02:56
  • t3 is nice. to avoid collision use `uuid.uuid1().hex[:30]` [source](https://stackoverflow.com/questions/2519896/generate-unique-hashes-for-django-models#2520136) – STIKO Mar 03 '19 at 18:04
94

30 digit hex string:

>>> import os,binascii
>>> print binascii.b2a_hex(os.urandom(15))
"c84766ca4a3ce52c3602bbf02ad1f7"

The advantage is that this gets randomness directly from the OS, which might be more secure and/or faster than the random(), and you don't have to seed it.

wump
  • 4,277
  • 24
  • 25
  • that's interesting, and probably a good choice for generating the 30 digit hex number he wants. probably could use urandom and a slice operator to generate the alphanumeric string also. – eemz May 06 '10 at 16:29
  • I did take a look at the other functions in binascii, they do have base64 and uuencode, but no way to generate the first kind of strings he wants (base36). – wump May 06 '10 at 16:49
  • 1
    Is this random/unique enough to be used in, say, session tokens? – moraes Jul 04 '11 at 12:31
  • 1
    A: No. Found the answer: http://stackoverflow.com/questions/817882/unique-session-id-in-python/6092448#6092448 – moraes Jul 04 '11 at 12:48
  • How does one specify the length? – 3kstc Jun 25 '19 at 04:58
93

In Py3.6+, another option is to use the new standard secrets module:

>>> import secrets
>>> secrets.token_hex(15)
'8d9bad5b43259c6ee27d9aadc7b832'
>>> secrets.token_urlsafe(30*3//4) # see notes
'teRq7IqhaRU0S3euX1ji9f58WzUkrg'

Note: token_urlsafe() uses base64 encoding which means you reduce the requested number by 3//4. It may also include _- - unclear if that is acceptable

AChampion
  • 29,683
  • 4
  • 59
  • 75
34
import string
import random
lst = [random.choice(string.ascii_letters + string.digits) for n in xrange(30)]
s = "".join(lst)
print s
ocwbKCiuAJLRJgM1bWNV1TPSH0F2Lb
ggorlen
  • 44,755
  • 7
  • 76
  • 106
eemz
  • 1,183
  • 6
  • 10
28

Dramatically faster solution than those here:

timeit("'%0x' % getrandbits(30 * 4)", "from random import getrandbits")
0.8056681156158447
Kurt Spindler
  • 1,311
  • 1
  • 14
  • 22
  • One user has to try all the methods on the same machine to get an accurate baseline. Hardware specs can make a big difference. >>>> timeit.timeit("'%0x' % getrandbits(30 * 4)", "from random import getrandbits") 0.2471246949999113 – ptay Mar 28 '19 at 01:03
  • Thank you, this much faster than the others above. `%timeit '%030x' % randrange(16**30)` gives 1000000 loops, best of 3: 1.61 µs per loop while `%timeit '%0x' % getrandbits(30 * 4)` gives 1000000 loops, best of 3: 396 ns per loop – frmdstryr Mar 28 '19 at 17:15
16

Note: random.choice(string.hexdigits) is incorrect, because string.hexdigits returns 0123456789abcdefABCDEF (both lowercase and uppercase), so you will get a biased result, with the hex digit 'c' twice as likely to appear as the digit '7'. Instead, just use random.choice('0123456789abcdef').

yaronf
  • 193
  • 2
  • 8
7
In [1]: import random                                    

In [2]: hex(random.getrandbits(16))                      
Out[2]: '0x3b19'
Bob
  • 689
  • 10
  • 11
6

one-line function:

import random
import string

def generate_random_key(length):
    return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))

print generate_random_key(30)
evandrix
  • 6,041
  • 4
  • 27
  • 38
Bacara
  • 6,903
  • 1
  • 20
  • 21
6

Another Method :

from Crypto import Random
import binascii

my_hex_value = binascii.hexlify(Random.get_random_bytes(30))

The point is : byte value is always equal to the value in hex.

dsgdfg
  • 1,492
  • 11
  • 18
4

Incidentally, this is the result of using timeit on the two approaches that have been suggested:

Using random.choice():

>>> t1 = timeit.Timer("''.join(random.choice(string.hexdigits) for n in xrange(30))", "import random, string")
>>> t1.timeit()
69.558588027954102

Using binascii.b2a_hex():

>>> t2 = timeit.Timer("binascii.b2a_hex(os.urandom(15))", "import os, binascii")
>>> t2.timeit()
16.288421154022217
David Narayan
  • 5,338
  • 2
  • 21
  • 6
2

There's a faster one compared to what jcdyer has mentioned. This takes ~50% of his fastest method.

from numpy.random.mtrand import RandomState
import binascii
rand = RandomState()

lo = 1000000000000000
hi = 999999999999999999
binascii.b2a_hex(rand.randint(lo, hi, 2).tostring())[:30]

>>> timeit.Timer("binascii.b2a_hex(rand.randint(lo,hi,2).tostring())[:30]", \
...                 'from __main__ import lo,hi,rand,binascii').timeit()
1.648831844329834         <-- this is on python 2.6.6
2.253110885620117         <-- this on python 2.7.5

If you want in base64:

binascii.b2a_base64(rand.randint(lo, hi, 3).tostring())[:30]

You can change the size parameter passed to randint (last arg) to vary the output length based on your requirement. So, for a 60 char one:

binascii.b2a_hex(rand.randint(lo, hi, 4).tostring())[:60]
Ethan
  • 4,915
  • 1
  • 28
  • 36
  • A slight variation: `binascii.b2a_hex(np.random.rand(np.ceil(N/16)).view(dtype=int))[:N]` where `N=30`. – dan-man Mar 29 '15 at 10:16
  • @dan-man Thanks for this optional method. However, I find that it's consumes atleast 5x more time. Do you notice that as well? – Ethan Mar 29 '15 at 16:31
2

This is for sure not the most lightweight version, but it is random and it's easy to adjust the alphabet / length you want:

import random

def generate(random_chars=12, alphabet="0123456789abcdef"):
    r = random.SystemRandom()
    return ''.join([r.choice(alphabet) for i in range(random_chars)])
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
0

adding one more answer to the mix that performs faster than @eemz solution and is also fully alphanumeric. Note that this does not give you a hexidecimal answer.

import random
import string

LETTERS_AND_DIGITS = string.ascii_letters + string.digits

def random_choice_algo(width):
  return ''.join(random.choice(LETTERS_AND_DIGITS) for i in range(width))

def random_choices_algo(width):
  return ''.join(random.choices(LETTERS_AND_DIGITS, k=width))


print(generate_random_string(10))
# prints "48uTwINW1D"

a quick benchmark yields

from timeit import timeit
from functools import partial

arg_width = 10
print("random_choice_algo", timeit(partial(random_choice_algo, arg_width)))
# random_choice_algo 8.180561417000717
print("random_choices_algo", timeit(partial(random_choices_algo, arg_width)))
# random_choices_algo 3.172438014007639
andykais
  • 996
  • 2
  • 10
  • 27