Search This Blog

Wednesday, June 9, 2010

Salted Hash Password - OWASP

In very many - not to say almost all - Web applications user data is administered, from Web forum to Web shop. These user data encompass login information of the users which contain the password besides the user name - and this in plain text. A security leak par excellence.

Why is storing the user name and password in plain text a security leak?
Well, imagine a cracker gaining system access through eventual OS or server software errors, and being able to read the user database. As he now knows the user name and password of any arbitrary user he can now log on as a 'real' user and do whatever he wants with the permissions for that user - from ordering in the Web shop to character assassination on the forum. And you are the operator...

How can this security risk be eliminated?
Why should we roam far when a proven method for safe storage of passwords exists since decades: under UNIX, user passwords are stored as so called 'salted hashes'.

What is a Salted Hash?

A hash is a numerical value of fixed length which unequivocally identifies files of arbitrary legth. An example of a hashing algorithm is SHA1, which already figured as the topic of an ASP article (German). The reader might now say that saving the password as a hash would be sufficient, but why is this wrong?

The reason for this is that usually so called 'Dictionary Attacks' are run against hashed passwords - a good example being the MD5 hashed passwords of NT4. This is a Brute Force attack: all entries in a dictionary were hashed using MD5 and those hash values then are compared against the password database. Have a guess how quickly some passwords are found this way.

The intention behind a Salted Hash is to have this type of attack fail by attaching a random value - the so called salt - to each password and only then compute the hash over password and salt. For comparison of the password the salt has to be stored alongside the salted hash, but the only vector of attack is to re-code the dictionary for each individually stored password with the salt - and this takes quite a long time.


Storing the Salted Hash

As previously mentioned, we now need to store three fields instead of user name and password: user name, salt and the salted hash of the password. I also mentioned that when these data get into the hands of a cracker he will have a problem using standard attacks and most probably will look for an easier victim.

One point however must be kept in mind: it is now impossible to send a 'password reminder' email - all that can be done is to generate and send a new password for the user. As a number of mistakes is made in this field, we will begin with the .NET code for generating a truly random password.

Example - Java
**************

SecureAlgorithm.java

package com.product.common;

/**
* @author vijaydr
*/

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class SecureAlgorithm
{
public static String generateRandom () throws Exception
{
SecureRandom sr = SecureRandom.getInstance ( "SHA1PRNG" );
//System.out.println ( "SecureRandom : " + sr.nextInt () ); //if you enable then output return in negative
return String.valueOf ( Math.abs ( sr.nextInt () ) );
}

public static String [] sanitizedData ( String... input )
{
String sanitizedData[] = new String[input.length];
int index = 0;

for ( String i : input )
{
sanitizedData[index++] = i.replaceAll ( "[\'~!@#$%^&*()\";: <>?/,.]", "’" );
}
return sanitizedData;
}

private static String convertToHex ( byte [] data )
{
StringBuffer buf = new StringBuffer ();
for ( int i = 0; i < data.length; i++ ) { int halfbyte = ( data[i] >>> 4 ) & 0x0F;
int two_halfs = 0;
do
{
if ( ( 0 <= halfbyte ) && ( halfbyte <= 9 ) ) buf.append ( (char) ( '0' + halfbyte ) ); else buf.append ( (char) ( 'a' + ( halfbyte - 10 ) ) ); halfbyte = data[i] & 0x0F; } while ( two_halfs++ < 1 ); } return buf.toString (); } public static String SHA1 ( String text ) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md; md = MessageDigest.getInstance ( "SHA-1" ); byte [] sha1hash = new byte[40]; md.update ( text.getBytes ( "iso-8859-1" ), 0, text.length () ); sha1hash = md.digest (); return convertToHex ( sha1hash ); } public static void main ( String args[] ) { String sanitizedData[] = SecureAlgorithm.sanitizedData ( "vijay\'~ !@#$%^&*()\";:<>?/,.", "jessy%" );
System.out.println ( sanitizedData[0] + "\n" + sanitizedData[1] );
}

}

autoGenRandom.jsp

<%@page import="com.abi.searchEngine.ProductConstants"%>
<%
String autoGen = com.product.common.SecureAlgorithm.generateRandom();
session.setAttribute ( ProductConstants.ATTRIBE_AUTO_GEN_NUMBER, autoGen );
%>


index.jsp

<%@ page language="java" pageEncoding="ISO-8859-1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1" />
<META Http-Equiv="Cache-Control" Content="no-cache"/>
<META Http-Equiv="Cache-Control" Content="no-store"/>
<META Http-Equiv="Pragma" Content="no-cache"/>
<META Http-Equiv="Expires" Content="0"/>

<%
response.setHeader ( "Cache-Control", "no-cache" );
response.setHeader ( "Pragma", "no-cache" );
response.setDateHeader ( "Expires", 0 );
response.setHeader ( "Cache-Control", "no-store" );
%>

<%@include file="autoGenRandom.jsp"%>

<title>Secure Engine</title>
<script type="text/javascript" src="js/sha.js"></script>
<script type="text/javascript" src="js/validations.js"></script>
<script type="text/javascript">

var autoGen = "<%=session.getAttribute ( ProductConstants.ATTRIBE_AUTO_GEN_NUMBER ).toString()%>";

var hashObj;
var hmacObj;
function calcHash()
{
var isSuccess = true;
var saltValue = "";
hashObj = new jsSHA(document.getElementById("passwd").value, "ASCII");
try {
saltValue = hashObj.getHash("SHA-1", "HEX");
} catch(e) {
isSuccess = false;
}

hashObj = new jsSHA(saltValue + autoGen, "ASCII");
try {
saltValue = hashObj.getHash("SHA-1", "HEX");
//alert ( saltValue );
} catch(e) {
isSuccess = false;
}

document.getElementById("passwd").value = saltValue;
return isSuccess;

}

function newHMAC()
{
//SHA-1,SHA-256
//ASCII, HEX
var inputTypeSelectBox = document.getElementById("passwd");
hmacObj = new jsSHA(inputTypeSelectBox.value, "ASCII");
var keyTypeSelectBox = "ASCII";
var hashVariantSelectBox = "SHA-1";
var hmac = hmacObj.getHMAC( autoGen, keyTypeSelectBox, hashVariantSelectBox, "HEX" );
try {
document.getElementById("passwd").value = hmac;
alert ( hmac );
} catch(e){
}
}

function msg()
{
document.getElementById( "errors" ).innerHTML = '<%= (request.getAttribute("errors")==null)?"":request.getAttribute("errors")%>';
}

function validate()
{
//var specialChars = "#,+,~,\`,=,\,,.,@,!,~,*,^,\`,&,$,(,),[,],{,},:,;,>,<,%,?,<,>,\",\'";
document.getElementById( "errors" ).innerHTML = '';
if ( trim(document.getElementById("uName").value) == "" || trim(document.getElementById("passwd").value) == "" )
{
document.getElementById( "errors" ).innerHTML = "Login / Password is not empty...";
return false;
}
if( ( !isValidAlphaNumericInput( document.getElementById("uName").value, "" ) ) || ( !isValidAlphaNumericInput( document.getElementById("passwd").value, "" ) ) ) //specialChars ) ) )
{
document.getElementById( "errors" ).innerHTML = "Login / Password is not an Alpha Numeric...";
return false;
}
/* validation for blankspace of userid
else if( isBlankSpace( trim(document.getElementById("uName").value) ) )
{
document.getElementById( "errors" ).innerHTML = "User Name is not be a blank space..";
return false;
}*/
else
{
//newHMAC();
calcHash();
}
}


function noBack(){
window.history.forward()
}

noBack();
window.onload=noBack;
window.onpageshow=function(evt){if(evt.persisted)noBack()}
window.onunload=function(){void(0)}

</script>




<style type="text/css">
<!--
.style1 {
color: #666666
}

.style2 {
color: #333333;
font-size: 10pt;
}

.style3 {
font-family: Georgia, "Times New Roman", Times, serif;
font-size: 12px;
}
-->
</style>
</head>
<body onload="msg();">
<table width="60%" height="80%" border="0" align="center">
<tr height="30%">
<td colspan="2">  <img
src="jsp/includes/images/kpisoft_tm.jpg" /></td>
</tr>
<tr class="tableRow" align="center">
<td colspan="2">
<h3>Secure Engine<br />
</h3>
</td>
</tr>

<tr>
<td width="60%">
<p align="justify" class="style1 style3">Business and technology
solutions provider based in Bangalore with development centers in
Madurai and Chennai specializing in services to Small and Medium
Businesses and independent software vendors (ISVs). We deliver
reliable and high quality end-to-end solutions that significantly
enhance the competitiveness of our clients in addition to contributing
to productivity and profitability.</p>
<p align="justify" class="style1 style3"><br />
</p>
<p align="justify" class="style1 style3"><img
src="jsp/includes/images/support.jpg" /></p>
<p align="justify">Company adopts business unit approach towards
specializing in breadth and width of the IT offerings but would
provide its clientele with centralized engagement approach for their
various IT needs. <span class="style2">.</span> <br />
</p>
</td>
<td><br />

<form action="login?action=login" name="frm" focus='uName'
method="post" autocomplete="off"><br />
<table width="388" height="126" border="0">
<tr>
<!-- <th colspan="2" id="infoErrorMsg" width="211" height="31" scope="row">  -->
<th> </th>
<th colspan="2"><font id="errors" class="Mandatory" color="red"></font>
</th>
</tr>
<tr>
<th colspan="2" width="211" height="31" scope="row">
<div align="right">Login Name :</div>
</th>
<td width="161"><input type="text" name="uName" id="uName" styleClass="dropdown" size="30" /> <br />
</td>
</tr>
<tr>
<th colspan="2" height="32" scope="row">
<div align="right">Password   :</div>
</th>
<td><input type="password" name="passwd" id="passwd" styleClass="dropdown" size="30" /> <br />
</td>
</tr>
<tr>
<th colspan="2" height="53" scope="row"><br />
</th>
<td><br />
<div align="center">
<input type="submit" value="Login" onclick="return validate()" />
<input type="reset" /></div>
<br />
</td>
</tr>
</table>
<br />
<br />

</form>
</td>
</tr>
<tr height="50%">
<td colspan="2" align="right"><a href="<%=request.getContextPath()%>/fp.jsp">Forgot Password</a></td>
</tr>

</table>

<p align='center'><%@include file="footer.jsp"%></p>

</body>
</html>


LoginCheck.java

public void doPost ( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException
{
response.setContentType ( "text/html" );
PrintWriter out = response.getWriter ();
HttpSession session = request.getSession ();
String autoGen = session.getAttribute ( ProductConstants.ATTRIBE_AUTO_GEN_NUMBER ).toString ();
PreparedStatement st;
boolean success = false;
String nextJSP = "/index.jsp";
RequestDispatcher dispatcher = null;

try
{
if ( session.getAttribute ( ProductConstants.SESSION_USER ) == null )
{
String uName = request.getParameter ( "uName" );
String passwd = request.getParameter ( "passwd" );

uName = (SecureAlgorithm.sanitizedData ( uName ) )[0];

dbConnection = DBConnection.getDBConnection ();
String query = rb.getString ( "user.login" );
query += " and sha1(concat(pwd,'" + autoGen + "')) = ? ";
System.out.println ( "query " + query );

st = dbConnection.prepareStatement ( query );
st.setString ( 1, uName );
st.setString ( 2, passwd );

ResultSet rs = st.executeQuery ();
if ( rs.next () )
{
success = true;
Login login = new Login ();
login.setId ( rs.getInt ( "id" ) );
login.setLoginid ( uName );
login.setDeptId ( rs.getInt ("deptid") );
login.setUserDetailsId ( rs.getString ( "userid" ) );
CommonUtils.generateNewJSessionId ( request, false );
session = request.getSession ();
session.setAttribute ( ProductConstants.SESSION_USER, login );
CommonUtils.saveAuditLog ( dbConnection, new AuditLogger ( -1, String.valueOf ( login.getId () ), rb.getString ( "action.login.type" ), rb.getString ( "action.login.type.message" ), new Date (), ProductConstants.STATUS_SUCCESS ) );
}
else
{
CommonUtils.saveAuditLog ( dbConnection, new AuditLogger ( -1, uName, rb.getString ( "action.login.type" ), rb.getString ( "action.login.type.message" ), new Date (), ProductConstants.STATUS_FAILURE ) );
}
}
if ( success )
{
nextJSP = "redirect.jsp";
request.removeAttribute ( ProductConstants.ATTRIB_ERRORS );
// dispatcher = getServletContext ().getRequestDispatcher ( nextJSP );
// dispatcher.forward ( request, response );
response.sendRedirect ( nextJSP );
}
else
{
nextJSP = "/" + ProductConstants.URL_INDEX; // dispatcher call, so put /index.jsp instead index.jsp
request.setAttribute ( ProductConstants.ATTRIB_ERRORS, "Login error" );
dispatcher = getServletContext ().getRequestDispatcher ( nextJSP );
dispatcher.forward ( request, response );
// response.sendRedirect ( nextJSP );
}

}
catch ( Exception e )
{
System.out.println ( e );
}
finally
{
try
{
DBConnection.closeConnection ();

}
catch ( Exception e )
{
e.printStackTrace ();
}
}
}



ProductConstants

package com.abi.searchEngine;
public class ProductConstants
{
   public static String CONFIG_FILE = "myapp";
   public static String SESSION_USER = "login";
   public static String ATTRIB_ERRORS = "errors";

   //url
   public static String URL_INDEX = "index.jsp";
   public static String ATTRIBE_AUTO_GEN_NUMBER = "autoGen";

   public static String STATUS_SUCCESS = "SUCCESS";
   public static String STATUS_FAILURE = "FAILURE";
   public static String STATUS_ONGOING = "ONGOING";

   public static String ITEM_ID = "itemId";
   public static String ID = "id";

   public static String PURCHASE_LIST = "purchaseList";
   public static String PURCHASE = "purchase";
   public static int ADMIN_ID = 1;

   public static String TRUE = "t";
   public static String FALSE = "f";

   public static String EDIT_PURCHASE = "editPurchase";
   public static String LOGOUT = "lt";

}

//user.login=SELECT l.id, l.pwd, l.userid, d.id as deptid FROM login l JOIN userdetails ud ON ud.id = l.id JOIN department d ON d.id = ud.deptid WHERE loginid = ?

5 comments:

Anonymous said...

Hi Vijay, loveed your article, but I cant find a few packages and files namely:
ProductConstants in the com.abi.searchEngine

Would be grateful if you can help me, since Im trying to achieve encryption of passwords etc.

Regards
Kiki

drvijayy2k2 said...

Thanks,

i updated the java file, where i missed

ProductConstants.java

Anonymous said...

Thanks Vijay, let me try.
Also, some references mentioned that the password must be appended with the salt value before submission. Will try and improve on your article.
Thanks... wonderful article for me.

Kiki (gulrez_sohliya@yahoo.com)

Anonymous said...

Hi Vijay, I tried on the article with MYSQL. Can you please help me, do i store the password as the sha of the password+salted values or simply the sha value of the password.

Ever tried in Postgres, it does not have the SHA function.

Kiki (gulrez_sohliya@yahoo.com)

drvijayy2k2 said...

DB
*. Simply sha value to be store in DB

APP
1. send the random value to login page

2. after submit, then from your JS function(),
2.1 Do sha with password value
2.2 Do sha value[pwd] with random value and again sha
2.3 send that to your login check

3. in login check
3.1 get the double sha value,
3.2 take a sha value from DB
3.3 concat DB sha value to random value and do again double sha
3.4 match with parameter double sha value with db double sha value.

if yes
then redirect to middle page and then redirect to home page [owasp]
else
then redirect to login page with new random value.


thanks
vijay.dr

Hit Counter


View My Stats