35

I'm trying to create a model where I can store usernames and passwords for other applications. How can I set a password field in Django so that it is not in plain text in admin? Thanks in advance.

Ruben Quinones
  • 2,442
  • 8
  • 26
  • 30
  • Thanks all of you for your answers. I'm going for mlissner's and Manoj's suggestion, although a special note goes to rebus since his approach would be suitable for simple applications where you just want simple functionality without much security. – Ruben Quinones Sep 15 '10 at 22:46
  • This might do what you want. http://djangosnippets.org/snippets/1330/ – Paolo Dec 02 '12 at 21:16
  • Ping - Have you implemented this? I'm looking at implementing a similar thing but I'm unsure where to start could you point me in the right direction? – Jack Eccleshall Nov 27 '13 at 17:34
  • Jack - I have, but I made it a bit different. I use the set_password like Manoj stated in his answer, from there I can check hash the password entered by the user to login either by splitting the algo/salt/hash string, leaving only the hash to compare it, or using a static salt and compare them altogether. – Ruben Quinones Nov 27 '13 at 21:44

5 Answers5

32

As @mlissner suggested the auth.User model is a good place to look. If you check the source code you'll see that the password field is a CharField.

password = models.CharField(_('password'), max_length=128, help_text=_("Use 
'[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))

The User model also has a set_password method.

def set_password(self, raw_password):
    import random
    algo = 'sha1'
    salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
    hsh = get_hexdigest(algo, salt, raw_password)
    self.password = '%s$%s$%s' % (algo, salt, hsh)

You can take some clues from this method about creating the password and saving it.

Community
  • 1
  • 1
Manoj Govindan
  • 72,339
  • 21
  • 134
  • 141
  • 1
    So if I were to implement a similar method, how would I then de-hash a password when I needed to use it to authenticate with an API such as Facebook's? – Chris Oct 05 '11 at 01:46
  • 5
    As Marc answered below, you can't. But you can hash the user-supplied password using the same algo and salt, and check that the value equals the one you have stored in your database. – qris Dec 11 '12 at 15:47
  • Hi, the source code link leads to a 404, could you please provide a corrected link – aliasav Feb 05 '16 at 07:20
  • 4
    Django has changed *lots* since this answer was posted —to allow the User model to be overriden and altered— but the gist is still true. The heavy lifting parts for the new `AbstractBaseUser`'s setting and checking of passwords live in [`django.contrib.auth.hashers`](https://github.com/django/django/blob/master/django/contrib/auth/hashers.py). Again, while the prose of this answer is generally true, the code is antiquated. Answer could do with some careful rewriting. – Oli Dec 13 '16 at 14:16
8

I don't think you are ever going to be able to de-hash an encrypted password that was stored in a manner similar to the normal django User passwords. Part of the security is that they are un-de-hashable.

Marc
  • 81
  • 1
  • 1
5

Unfortunately there isn't an easy answer to this question because it depends on the applications you are trying to authenticate against and it also depends on how secure you want the password fields to be.

If your Django application will be using the password to authenticate against another application that requires a plaintext password to be sent, then your options are:

  • Store the password in plain text in your Django model (your question implies you don't want to do this)
  • Capture a master password from the user before they can unlock their stored password for other applications
  • Obfuscate the password in the model so that it can be accessed by anyone with raw datastore permissions but just isn't obvious to human casual viewers

You could use the Django user password as the master password if you are using Django's builtin user model. This means that you will need to keep that master password in memory which may make some operations difficult for you, such as restarting the server or running load-balanced redundant servers.

Alternative to storing passwords

Luckily many modern applications support this in another way using an access token system which is key based rather than password based. Users are guided through the process of setting up a link between the two applications and, behind the scenes, the applications generate keys to authenticate each other either permanently or with a defined expiration date.

Facebook, for example, supports this model and they have extensive documentation about how it works:

Facebook Developers: Access Tokens and Types

Once you have managed to link with Facebook using [OAuth 2.0](http://tools.ietf.org/html/draft-ietf-oauth-v2- 12) you will probably find it easier to add links to other applications using that same protocol.

jwal
  • 140
  • 2
  • 6
5

Your best bet (I'm aware of) is to dig into the code in the django code, and see how it's done there. As I recall, they generate a salted hash so that the plain text values are never stored anywhere, but rather the hash and salt are.

If you go into the django installation, and poke around for words like hash and salt, you should find it pretty quickly. Sorry for the vague answer, but perhaps it will set you on the right path.

mlissner
  • 17,359
  • 18
  • 106
  • 169
3

If you need a reversible password field, you could use something like this:

from django.db import models
from django.core.exceptions import ValidationError
from django.conf import settings

from os import urandom
from base64 import b64encode, b64decode
from Crypto.Cipher import ARC4
from django import forms



PREFIX = u'\u2620'


class EncryptedCharField(models.CharField): 
    __metaclass__ = models.SubfieldBase

    SALT_SIZE = 8

    def __init__(self, *args, **kwargs):
        self.widget = forms.TextInput       
        super(EncryptedCharField, self).__init__(*args, **kwargs)

    def get_internal_type(self):
        return 'TextField'    

    def to_python(self, value):
        if not value:
            return None
        if isinstance(value, basestring):
            if value.startswith(PREFIX):
                return self.decrypt(value)
            else:
                return value
        else:
            raise ValidationError(u'Failed to encrypt %s.' % value)

    def get_db_prep_value(self, value, connection, prepared=False):
        return self.encrypt(value)        

    def value_to_string(self, instance):
        encriptado = getattr(instance, self.name)
        return self.decrypt(encriptado) if encriptado else None

    @staticmethod
    def encrypt(plaintext):
        plaintext = unicode(plaintext)
        salt = urandom(EncryptedCharField.SALT_SIZE)
        arc4 = ARC4.new(salt + settings.SECRET_KEY)
        plaintext = u"%3d%s%s" % (len(plaintext), plaintext, b64encode(urandom(256-len(plaintext))))
        return PREFIX + u"%s$%s" % (b64encode(salt), b64encode(arc4.encrypt(plaintext.encode('utf-8-sig'))))

    @staticmethod
    def decrypt(ciphertext):
        salt, ciphertext = map(b64decode, ciphertext[1:].split('$'))
        arc4 = ARC4.new(salt + settings.SECRET_KEY)
        plaintext = arc4.decrypt(ciphertext).decode('utf-8-sig')
        return plaintext[3:3+int(plaintext[:3].strip())]

The encryption part is based on the snippet on https://djangosnippets.org/snippets/1330/, I just turned it into a field model, added utf-8 support and added a prefix as a workaround for Django's silly use of to_python()

hlongmore
  • 1,603
  • 24
  • 28
Haroldo_OK
  • 6,612
  • 3
  • 43
  • 80
  • 1
    To use the above, you'll probably want to `pip install pycrypto` rather than `pip install Crypto`. Also, there is a package available with many fields now on pypi called `django-encrypted-fields` (or for python 3, `django-encrypted-fields-python3`). – hlongmore Feb 17 '18 at 02:44
  • The whole point of a hash is to be irreversible! There should be NO WAY for ANYONE to get access to the original password. http://plaintextoffenders.com/faq/devs – Solomon Ucko Nov 23 '18 at 16:28
  • 1
    Yes, that's true, and if you can solve a problem using a hash, you should definitely use that. But, sometimes, you may need to retrieve a password: imagine your program needs to log in to a 3rd party server, for example, and the password to access that 3rd party server is in your database. – Haroldo_OK Nov 23 '18 at 16:55