Is there a function for generating settings.SECRET_KEY in django?

44.8k views Asked by At

I wrote an ansible-role for openwisp2 to ease its deployment, it's a series of django apps. To ease the deployment as much as possible, I wrote a simple (probably trivial) SECRET_KEY generator script:

#!/usr/bin/env python
"""
Pseudo-random django secret key generator
"""
from __future__ import print_function
import random

chars = 'abcdefghijklmnopqrstuvwxyz' \
        'ABCDEFGHIJKLMNOPQRSTUVXYZ' \
        '0123456789' \
        '#()^[]-_*%&=+/'

SECRET_KEY = ''.join([random.SystemRandom().choice(chars) for i in range(50)])

print(SECRET_KEY)

which is called by ansible to generate the secret key the first time the ansible playbook is run.

Now, that works fine BUT I think it defeats the built-in security measures Django has in generating a strong key which is also very hard to guess.

At the time I looked at other ways of doing it but didn't find much, now I wonder: is there a function for generating settings.SECRET_KEY in django?

That would avoid this kind of home baked solutions that even though they work they are not effective when it comes to security.

5

There are 5 answers

4
Daniel Roseman On BEST ANSWER

Indeed, you can use the same function that generates a new key when you call startproject, which is django.core.management.utils.get_random_secret_key().

Note however it's not very different from your version.

4
S Ghosh On

I would like to add that as per the commit Fixed #31757 -- Adjusted system check for SECRET_KEY to warn about autogenerated default keys, the method get_random_secret_key() is considred to be insecure.

If you are using python 3.6+ then you can use the secrets.token_hex([nbytes=None]) function

python3 -c 'import secrets; print(secrets.token_hex(100))'

credit TLDR: Generate Django Secret Key

2
Rik Schoonbeek On

Note that this may not be safe to use for production, as S Ghosh is pointing out in a post below. But copy and paste this after running django-admin shell for example to quickly get a key.

from django.core.management.utils import get_random_secret_key  
get_random_secret_key()
0
hlongmore On

The post referenced by S Ghosh TLDR: Generate Django Secret Key indicates that as of version 3.1.3, Django is actually using the Python secrets module behind the scenes. Looking at this blob for get_random_secret_key and this other blob for get_random_string, I can see it is so:

def get_random_secret_key():
    """
    Return a 50 character random string usable as a SECRET_KEY setting value.
    """
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
    return get_random_string(50, chars)
def get_random_string(length, allowed_chars=RANDOM_STRING_CHARS):
    """
    Return a securely generated random string.
    The bit length of the returned value can be calculated with the formula:
        log_2(len(allowed_chars)^length)
    For example, with default `allowed_chars` (26+26+10), this gives:
      * length: 12, bit length =~ 71 bits
      * length: 22, bit length =~ 131 bits
    """
    return ''.join(secrets.choice(allowed_chars) for i in range(length))

The only issue with the get_random_secret_key function as I see it in the code is that the allowed characters does not include capital letters, so the number of possible bits for the same size key is smaller than it would be if capitals were included, but not by much:

from math import log2
lower_plus_numbers = (list(chr(o) for o in range(0x61, 0x7B))
                      + list(chr(o) for o in range(0x30, 0x3A)))
punctuation = list('!@#$%^&*(-_=+)')
upper_alpha = list(chr(o) for o in range(0x41, 0x5B))
shorter = log2((len(lower_plus_numbers) + len(punctuation)) ** 50)
longer = log2((len(lower_plus_numbers) + len(punctuation) + len(upper_alpha)) ** 50)
print(f'longer: {int(longer + 0.5)}; shorter: {int(shorter + 0.5)} '
      f'difference: {int(longer - shorter + 0.5)}; ratio: {longer/shorter}')

The output of the above code:

longer: 312; shorter: 282; difference: 30; ratio: 1.1070316647619918

So, if you have a recent enough Django and Python, the biggest question is whether you want to generate your SECRET_KEY with a dependency on Dango, or just Python. If you don't mind the Django dependency, but want to include upper case letters, or want to have a longer key, you can easily do something like:

from django.utils.crypto import get_random_string
key_length = 60
get_random_string(
    key_length,
    allowed_chars=lower_plus_numbers + punctuation + upper_alpha,
)

Sample output:

'gW(VDtylhoAuZNcLbIC=ai5=2*tPZ=Gmf4D1^4T!NxX3tB0%_w7pYY2+FgDx'

If you don't want the Django dependency, you could use S Ghosh's answer. Or if you want more than hex characters, you could do something like:

allowed_chars = [chr(i) for i in range(0x21, 0x7F)]
key_length = 60
key = ''.join(secrets.choice(allowed_chars) for i in range(key_length))

Value of key (as a python string):

'DN7tbWid#q6R^=%i"[1AA>$@AZg=XD+p|[aB?:#V`:kKWL77P6dC,~(\\9O\'j'
0
Rizqi N. Assyaufi On

Run this command below on the Terminal.

$ python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'

output:

2x$e%!k_u_0*gq0s4!_u(2(^lpy&gir0hg)q&5nurj0-sseuav