Ticket #1534: secure_login.diff
File secure_login.diff, 13.9 KB (added by , 19 years ago) |
---|
-
django/contrib/admin/media/js/login_encrypt.js
1 var login_form = document.getElementById('id_login_form'); 2 if (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 15 function 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 */ 45 var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 46 47 /* 48 * These are the functions you'll usually want to call 49 */ 50 function 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 */ 55 function 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 */ 103 function 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 */ 114 function 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 */ 124 function 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 */ 134 function 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 */ 143 function 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 */ 155 function 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
9 9 <p class="errornote">{{ error_message }}</p> 10 10 {% endif %} 11 11 <div id="content-main"> 12 <form action="{{ app_path }}" method="post" >12 <form action="{{ app_path }}" method="post" id="id_login_form"> 13 13 14 14 <p class="aligned"> 15 15 <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" /> … … 17 17 <p class="aligned"> 18 18 <label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" /> 19 19 <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 %} 20 21 <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 %} 21 22 </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> 22 24 23 25 <div class="aligned "> 24 26 <label> </label><input type="submit" value="{% trans 'Log in' %}" /> … … 28 30 <script type="text/javascript"> 29 31 document.getElementById('id_username').focus() 30 32 </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> 31 35 </div> 32 36 {% endblock %} -
django/contrib/admin/views/decorators.py
1 1 from django.core.extensions import DjangoContext, render_to_response 2 2 from django.conf.settings import SECRET_KEY 3 from django.models.auth import users 3 from django.models.auth import users, CURRENT_ALGORITHM 4 4 from django.utils import httpwrappers 5 5 from django.utils.translation import gettext_lazy 6 6 import base64, datetime, md5 7 7 import cPickle as pickle 8 import random 8 9 9 10 ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") 10 11 LOGIN_FORM_KEY = 'this_is_the_login_form' 12 LOGIN_SALT_KEY = '_session_salt' 11 13 12 14 def _display_login_form(request, error_message=''): 13 15 request.session.set_test_cookie() … … 23 25 'title': _('Log in'), 24 26 'app_path': request.path, 25 27 'post_data': post_data, 26 'error_message': error_message 28 'error_message': error_message, 29 'login_salt': request.session.get(LOGIN_SALT_KEY) 27 30 }, context_instance=DjangoContext(request)) 28 31 29 32 def _encode_post_data(post_data): … … 68 71 message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") 69 72 return _display_login_form(request, message) 70 73 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 71 79 # Check the password. 72 80 username = request.POST.get('username', '') 73 81 try: … … 86 94 87 95 # The user data is correct; log in the user in and continue. 88 96 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: 90 115 request.session[users.SESSION_KEY] = user.id 91 116 user.last_login = datetime.datetime.now() 92 117 user.save() -
django/models/auth.py
2 2 from django.models import core 3 3 from django.utils.translation import gettext_lazy as _ 4 4 5 CURRENT_ALGORITHM = 'sha1un' #user name 6 5 7 class Permission(meta.Model): 6 8 name = meta.CharField(_('name'), maxlength=50) 7 9 package = meta.ForeignKey(core.Package, db_column='package') … … 78 80 return full_name.strip() 79 81 80 82 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) 85 89 self.password = '%s$%s$%s' % (algo, salt, hsh) 86 90 87 91 def check_password(self, raw_password): … … 89 93 Returns a boolean of whether the raw_password was correct. Handles 90 94 encryption formats behind the scenes. 91 95 """ 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 93 98 # 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 110 123 111 124 def get_group_permissions(self): 112 125 "Returns a list of permission strings that this user has through his/her groups." … … 211 224 from random import choice 212 225 return ''.join([choice(allowed_chars) for i in range(length)]) 213 226 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 214 242 class Message(meta.Model): 215 243 user = meta.ForeignKey(User) 216 244 message = meta.TextField(_('Message'))