Ticket #1534: secure_login.diff

File secure_login.diff, 13.9 KB (added by Chris Beaven, 18 years ago)
  • django/contrib/admin/media/js/login_encrypt.js

     
     1var login_form = document.getElementById('id_login_form');
     2if (login_form) {
     3        var salt = document.getElementById('id_login_salt');
     4        if (salt && salt.value) {
     5                // hide "sending plain text" message
     6                var no_encryption = document.getElementById('id_no_encryption');
     7                if (no_encryption) {
     8                        no_encryption.style.display = 'none';
     9                }
     10                // add encryption method
     11                addEvent(login_form, 'submit', encrypt_password);
     12        }
     13}
     14
     15function encrypt_password() {
     16        // encrypt with login salt
     17        var login_salt = document.getElementById('id_login_salt');
     18        var username = document.getElementById('id_username');
     19        var password = document.getElementById('id_password');
     20        var salt = hex_sha1(username.value).substring(0,5);
     21        var hsh = hex_sha1(password.value+salt);
     22        // make random salt
     23        var salt = hex_sha1(Math.random().toString()).substring(0,5);
     24        // put the hashed hash in password & new salt value
     25        password.value = '';
     26        login_salt.value = salt+'$'+hex_sha1(hsh+login_salt.value+salt);
     27}
     28
     29
     30// cut down version of sha1.js from http://pajhome.org.uk/crypt/md5/sha1src.html
     31
     32/*
     33 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
     34 * in FIPS PUB 180-1
     35 * Version 2.1a Copyright Paul Johnston 2000 - 2002.
     36 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
     37 * Distributed under the BSD License
     38 * See http://pajhome.org.uk/crypt/md5 for details.
     39 */
     40
     41/*
     42 * Configurable variables. You may need to tweak these to be compatible with
     43 * the server-side, but the defaults work in most cases.
     44 */
     45var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
     46
     47/*
     48 * These are the functions you'll usually want to call
     49 */
     50function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
     51
     52/*
     53 * Calculate the SHA-1 of an array of big-endian words, and a bit length
     54 */
     55function core_sha1(x, len)
     56{
     57  /* append padding */
     58  x[len >> 5] |= 0x80 << (24 - len % 32);
     59  x[((len + 64 >> 9) << 4) + 15] = len;
     60
     61  var w = Array(80);
     62  var a =  1732584193;
     63  var b = -271733879;
     64  var c = -1732584194;
     65  var d =  271733878;
     66  var e = -1009589776;
     67
     68  for(var i = 0; i < x.length; i += 16)
     69  {
     70    var olda = a;
     71    var oldb = b;
     72    var oldc = c;
     73    var oldd = d;
     74    var olde = e;
     75
     76    for(var j = 0; j < 80; j++)
     77    {
     78      if(j < 16) w[j] = x[i + j];
     79      else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
     80      var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
     81                       safe_add(safe_add(e, w[j]), sha1_kt(j)));
     82      e = d;
     83      d = c;
     84      c = rol(b, 30);
     85      b = a;
     86      a = t;
     87    }
     88
     89    a = safe_add(a, olda);
     90    b = safe_add(b, oldb);
     91    c = safe_add(c, oldc);
     92    d = safe_add(d, oldd);
     93    e = safe_add(e, olde);
     94  }
     95  return Array(a, b, c, d, e);
     96
     97}
     98
     99/*
     100 * Perform the appropriate triplet combination function for the current
     101 * iteration
     102 */
     103function sha1_ft(t, b, c, d)
     104{
     105  if(t < 20) return (b & c) | ((~b) & d);
     106  if(t < 40) return b ^ c ^ d;
     107  if(t < 60) return (b & c) | (b & d) | (c & d);
     108  return b ^ c ^ d;
     109}
     110
     111/*
     112 * Determine the appropriate additive constant for the current iteration
     113 */
     114function sha1_kt(t)
     115{
     116  return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
     117         (t < 60) ? -1894007588 : -899497514;
     118}
     119
     120/*
     121 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     122 * to work around bugs in some JS interpreters.
     123 */
     124function safe_add(x, y)
     125{
     126  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
     127  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
     128  return (msw << 16) | (lsw & 0xFFFF);
     129}
     130
     131/*
     132 * Bitwise rotate a 32-bit number to the left.
     133 */
     134function rol(num, cnt)
     135{
     136  return (num << cnt) | (num >>> (32 - cnt));
     137}
     138
     139/*
     140 * Convert an 8-bit or 16-bit string to an array of big-endian words
     141 * In 8-bit function, characters >255 have their hi-byte silently ignored.
     142 */
     143function str2binb(str)
     144{
     145  var bin = Array();
     146  var mask = (1 << chrsz) - 1;
     147  for(var i = 0; i < str.length * chrsz; i += chrsz)
     148    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
     149  return bin;
     150}
     151
     152/*
     153 * Convert an array of big-endian words to a hex string.
     154 */
     155function binb2hex(binarray)
     156{
     157  var hex_tab = "0123456789abcdef";
     158  var str = "";
     159  for(var i = 0; i < binarray.length * 4; i++)
     160  {
     161    str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
     162           hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8  )) & 0xF);
     163  }
     164  return str;
     165}
  • django/contrib/admin/templates/admin/login.html

     
    99<p class="errornote">{{ error_message }}</p>
    1010{% endif %}
    1111<div id="content-main">
    12 <form action="{{ app_path }}" method="post">
     12<form action="{{ app_path }}" method="post" id="id_login_form">
    1313
    1414<p class="aligned">
    1515<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
     
    1717<p class="aligned">
    1818<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
    1919<input type="hidden" name="this_is_the_login_form" value="1" />
     20{% if login_salt %}<input type="hidden" name="login_salt" value="{{ login_salt }}" id="id_login_salt" />{% endif %}
    2021<input type="hidden" name="post_data" value="{{ post_data }}" />{% comment %} <span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
    2122</p>
     23<p class="help" id="id_no_encryption">{% trans 'Your password will be sent without encryption' %}<noscript> ({% trans 'Javascript is turned off' %})</noscript></p>
    2224
    2325<div class="aligned ">
    2426<label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}" />
     
    2830<script type="text/javascript">
    2931document.getElementById('id_username').focus()
    3032</script>
     33<script type="text/javascript" src="{% load adminmedia %}{% admin_media_prefix %}js/core.js"></script>
     34<script type="text/javascript" src="{% admin_media_prefix %}js/login_encrypt.js"></script>
    3135</div>
    3236{% endblock %}
  • django/contrib/admin/views/decorators.py

     
    11from django.core.extensions import DjangoContext, render_to_response
    22from django.conf.settings import SECRET_KEY
    3 from django.models.auth import users
     3from django.models.auth import users, CURRENT_ALGORITHM
    44from django.utils import httpwrappers
    55from django.utils.translation import gettext_lazy
    66import base64, datetime, md5
    77import cPickle as pickle
     8import random
    89
    910ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
    1011LOGIN_FORM_KEY = 'this_is_the_login_form'
     12LOGIN_SALT_KEY = '_session_salt'
    1113
    1214def _display_login_form(request, error_message=''):
    1315    request.session.set_test_cookie()
     
    2325        'title': _('Log in'),
    2426        'app_path': request.path,
    2527        'post_data': post_data,
    26         'error_message': error_message
     28        'error_message': error_message,
     29        'login_salt': request.session.get(LOGIN_SALT_KEY)
    2730    }, context_instance=DjangoContext(request))
    2831
    2932def _encode_post_data(post_data):
     
    6871            message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
    6972            return _display_login_form(request, message)
    7073
     74        # Get the previous login salt in case this is a login attempt
     75        # then generate a new login salt.
     76        last_login_salt = request.session.get(LOGIN_SALT_KEY, '')
     77        request.session[LOGIN_SALT_KEY] = users.do_hash(CURRENT_ALGORITHM, str(random.random()))[:5]
     78
    7179        # Check the password.
    7280        username = request.POST.get('username', '')
    7381        try:
     
    8694
    8795        # The user data is correct; log in the user in and continue.
    8896        else:
    89             if user.check_password(request.POST.get('password', '')):
     97            is_correct = False
     98           
     99            incoming_salt = request.POST.get('login_salt', '')
     100            if '$' in incoming_salt:
     101                # Javascript encrypted hash and changed salt.
     102                incoming_salt, incoming_hash = incoming_salt.split('$',1)
     103                is_correct = user.check_password_hash(incoming_hash, last_login_salt+incoming_salt)
     104                algo, nothing = user.password.split('$', 1)
     105                if algo != CURRENT_ALGORITHM:
     106                    message = _("Your password needs to be updated to a more secure format, please type your password again.")
     107                    # Set empty salt so next password won't be encrypted.
     108                    request.session[LOGIN_SALT_KEY] = ''
     109                    return _display_login_form(request, message)
     110
     111            if not is_correct:
     112                is_correct = user.check_password(request.POST.get('password', ''))
     113
     114            if is_correct:
    90115                request.session[users.SESSION_KEY] = user.id
    91116                user.last_login = datetime.datetime.now()
    92117                user.save()
  • django/models/auth.py

     
    22from django.models import core
    33from django.utils.translation import gettext_lazy as _
    44
     5CURRENT_ALGORITHM = 'sha1un'   #user name
     6
    57class Permission(meta.Model):
    68    name = meta.CharField(_('name'), maxlength=50)
    79    package = meta.ForeignKey(core.Package, db_column='package')
     
    7880        return full_name.strip()
    7981
    8082    def set_password(self, raw_password):
    81         import sha, random
    82         algo = 'sha1'
    83         salt = sha.new(str(random.random())).hexdigest()[:5]
    84         hsh = sha.new(salt+raw_password).hexdigest()
     83        import random
     84        from django.models.auth import users, CURRENT_ALGORITHM
     85        algo = CURRENT_ALGORITHM
     86        #salt = do_hash(algo, str(random.random()))[:5]
     87        salt = users.do_hash(algo, self.username)[:5]
     88        hsh = users.do_hash(algo, raw_password, salt)
    8589        self.password = '%s$%s$%s' % (algo, salt, hsh)
    8690
    8791    def check_password(self, raw_password):
     
    8993        Returns a boolean of whether the raw_password was correct. Handles
    9094        encryption formats behind the scenes.
    9195        """
    92         # Backwards-compatibility check. Older passwords won't include the
     96        from django.models.auth import users, CURRENT_ALGORITHM
     97        # Backwards-compatibility check. Original MD5 passwords won't include the
    9398        # algorithm or salt.
    94         if '$' not in self.password:
    95             import md5
    96             is_correct = (self.password == md5.new(raw_password).hexdigest())
    97             if is_correct:
    98                 # Convert the password to the new, more secure format.
    99                 self.set_password(raw_password)
    100                 self.save()
    101             return is_correct
    102         algo, salt, hsh = self.password.split('$')
    103         if algo == 'md5':
    104             import md5
    105             return hsh == md5.new(salt+raw_password).hexdigest()
    106         elif algo == 'sha1':
    107             import sha
    108             return hsh == sha.new(salt+raw_password).hexdigest()
    109         raise ValueError, "Got unknown password algorithm type in password."
     99        algo = 'md5'
     100        salt = ''
     101        hsh = self.password
     102        if '$' in self.password:
     103            algo, salt, hsh = self.password.split('$', 2)
     104        is_correct = (hsh == users.do_hash(algo, raw_password, salt))
     105        # Make sure we are using the current algorithm. Otherwise upgrade.
     106        if algo != CURRENT_ALGORITHM and is_correct:
     107            # Convert the password to the new, more secure format.
     108            self.set_password(raw_password)
     109            self.save()
     110        return is_correct
     111   
     112    def check_password_hash(self, incoming_hsh, incoming_salt):
     113        """
     114        Returns a boolean of whether the hash (done by javascript in the login
     115        form) matches.
     116        The javascript uses the login_salt + password to get the database hash.
     117        Then it hashes this result with it's own random salt.
     118        """
     119        from django.models.auth.users import do_hash
     120        algo, salt, hsh = self.password.split('$', 2)
     121        hashed_hash = do_hash(algo, hsh, incoming_salt)
     122        return incoming_hsh == hashed_hash
    110123
    111124    def get_group_permissions(self):
    112125        "Returns a list of permission strings that this user has through his/her groups."
     
    211224        from random import choice
    212225        return ''.join([choice(allowed_chars) for i in range(length)])
    213226
     227    def _module_do_hash(algo, value, salt=''):
     228        "Hash a value using the given algorithm."
     229        if algo == 'md5':
     230            import md5
     231            return md5.new(salt+value).hexdigest()
     232        elif algo == 'sha1':
     233            import sha
     234            return sha.new(salt+value).hexdigest()
     235        # Psuedorandom username salted hash (different algo so existing names
     236        # get updated).
     237        elif algo == 'sha1un':
     238            import sha
     239            return sha.new(value+salt).hexdigest()  # note: salt last
     240        raise ValueError, "Got unknown algorithm type."
     241
    214242class Message(meta.Model):
    215243    user = meta.ForeignKey(User)
    216244    message = meta.TextField(_('Message'))
Back to Top