Client Side Password Hashing
This is a continuation of a discussion on MoinMoinBugs/EncryptedPasswordsGiveFalseSenseOfSecurity, but focused on client side hashing of passwords.
The security of almost all web application is focused on trying to keep unauthorized (malicious) users out. Not enough attention is being given to the problem users are having trying to create and remember a unique password for every application they access.
Almost all web applications currently read and process the user's raw password on the server-side. The user's raw password is received regardless of whether the request is sent using http or https. This opens the possibility that a malicious web admin or employee could modify the Moin source code to save the user's raw password, and later try to access one of the user's other accounts -- for example, his email account.
This threat could be reduced if Moin implemented client-side password hashing by using one of several available Javascript SHA-1 libraries. There is one at: http://pajhome.org.uk/crypt/md5/. The example below shows one way for Moin to become more user-friendly -- the users raw password should never leave his PC.
Consider a minimal change-password form (that uses get rather than post).1 Note the form has two hidden fields, one for the user name and one for salt to be used in a hash.
<form action="ChangePassword" method="get" id="ChangePw" name="ChangePw" > <div> New Password: <input id="np1" name="np1" size="40" maxlength="40" type="password"> </div> <div id="remove"> Reenter New Password: <input id="np2" name="np2" size="40" maxlength="40" type="password"> <input type="hidden" id="uid" name="uid" value="JohnDoe"> <input type="hidden" id="salt" name="salt" value="My Photo Gallery Wiki"> </div> <input type="submit" value="Submit" name="Command" onclick="return changePw()"> </form>
The code below is an example of a minimal Javascript function. The changePw function is similar to what Moin is currently doing on the server side before it stores the password in the user record, but includes a fix for the original request documented in MoinMoinBugs/EncryptedPasswordsGiveFalseSenseOfSecurity. It adds the password, user ID, and a bit of application unique salt -- in this example the wiki name (cfg.sitename) sent as a hidden field in the above form. The hex_sha1 function from the pajhome.org.uk web site is not included here for simplicity.
The removeElement function is used to delete fields that the server does not need. This avoids sending the user ID and new password together to the server.
The objective is to generate a new password that is unique to a given user, password, and application instance.
// helper function to delete an unneeded element from the form function removeElement(divNum,eleNum) { var d = document.getElementById(divNum); var olddiv = document.getElementById(eleNum); d.removeChild(olddiv); } function changePw(){ var np1 = hex_sha1(document.forms.ChangePw.np1.value + document.forms.ChangePw.uid.value + document.forms.ChangePw.salt.value); var np2 = hex_sha1(document.forms.ChangePw.np2.value + document.forms.ChangePw.uid.value + document.forms.ChangePw.salt.value); if (np1 != np2) { alert('New passwords do not match! Try again.'); return false; } // overwrite the password field with the hashed value document.forms.ChangePw.np1.value = np1; document.forms.ChangePw.np2.value = np2; // delete unneeded fields to simplify return response // user ID and new password are not sent together removeElement('remove','np2'); removeElement('remove','uid'); removeElement('remove','salt'); }
Now that we understand how a new password is generated, the logon process can be explained. Here is an example logon form. In addition to the hidden salt field supplied in the change password form, a second salt field is sent - here a big random number is used. The get method is used here again, but this time there is little risk because the password being sent to the server is unique2 to this user ID, password, application instance, and logon transaction.
<form action="Logon" method="get" id="Logon" name="Logon" > <div> User Name: <input id="uid" name="uid" size="40" maxlength="40" type="text"> </div> <div> Password: <input id="np1" name="np1" size="40" maxlength="40" type="password"> </div> <div id="remove"> <input type="hidden" id="salt" name="salt" value="My Photo Gallery Wiki"> <input type="hidden" id="salt2" name="salt2" value="0f31b1f54b47fddd681aa682d3b5eee2"> </div> <input type="submit" value="Submit" name="Command" onclick="logonHash()"> </form>
The supporting Javascript function.
function logonHash(){ var s = hex_sha1(document.forms.Logon.pd1.value + document.forms.ChangePw.uid.value + document.forms.ChangePw.salt.value); s = hex_sha1(document.forms.Logon.randomId.value + s); document.forms.Logon.pd1.value = s; // delete unneeded fields to simplify return response removeElement('remove','salt'); removeElement('remove','salt2'); }
To complete the logon process, the server side must have saved the salt2 value sent to the client, and replicate the second hash hex_sha1(document.forms.Logon.randomId.value + s) using the salt2 value and stored user password.
Although I have been using similar code in my application, none of the above code has been tested.
Feedback
I do not sure if this is a wanted feature, because:
- I want a (G)UI for the user witch can run on every browser (mobilephones, lynx e.g. without javascript)
- Also I would like to use different authentication stuff (LDAP, Apache Auth, OpenID), etc..
That's why I think the full authentication stuff should stay in one single place on the MoinMoin Server (and not moved to the client side). In my eyes MoinMoin should be responsibility for saving the password in a secure (one way function).