8

I'm using Hibernate/Java to persist an entity to a database. The entity has a password field which is a String. When registring a user in my application, I hash the password using SHA-1 (I acknowledge this is a little weak). This produces a byte[] which I then convert to String using new String(byte[] arr); Whenever I want to log a user in, I simply retrieve the hashed password from the database (as String) and compare it with the digest of the input password at login using hashedPasswordFromDatabase.equals(SHA1_HASH(inputPassword));

This worked perfectly on my development system (Windows 7, JDK 1.6.0_23 / JDK 1.7, MySQL 5.5, Tomcat 6.0.26) but upon deploying it on our server (running JDK 1.6 on Linux), the equals method never evaluates to TRUE even for equal passwords. I quickly setup a new development system (Ubuntu 12.04, MySQL 5.5, JDK 1.7.0_03, Tomcat 7.0.22) and it doesn't work there too.

I'm aware of the possible encoding issues stated in the Java API documentation for the String class and also stated in several places here on SO. I've tried a couple of encodings suggested on this forum (e.g Base64, Latin-1) and I ended up with UnsupportedEncodingException. I think I'll be better off avoiding the String conversion. So how do I design my database such that the Hibernate-generated entity class comes up with byte[] for the password field instead of String?

erickson
  • 265,237
  • 58
  • 395
  • 493
Sayo Oladeji
  • 741
  • 4
  • 15
  • 28
  • 1
    +1, very good question. As an aside that is not an answer to your question, I've had very good luck using some of the Commons Base64 utilities going into and out of the database. – Chris Thompson May 14 '12 at 21:07
  • 2
    Why would you store a string representing a number instead of storing the number itself? – m0skit0 May 14 '12 at 21:13
  • Don't use one round of hashing to protect passwords. Use something like PBKDF2 or bcrypt with 10s of thousands of rounds---even 100k is not unreasonable. Storing a fixed-length `byte[]` directly should be easy for more database, but you can always create a `BigInteger` from a byte array and store that as a numeric type. – erickson May 14 '12 at 21:20
  • @erickson yeah good one there. I intend to upgrade the strength of the password protection (scraping out SHA-1, salting, multiple hashing, etc) after getting around this problem. – Sayo Oladeji May 14 '12 at 21:35
  • What is the data type in the database? – Steve Ebersole Jun 05 '12 at 19:30

3 Answers3

5

Yes, the problem is most likely in byte[] to String conversion. You must know that SHA produces raw byte array and there is no guarantee that arbitrary byte[] will produce valid String, irrespective to encoding. Thus your code was only working by accident.

Avoid the problem altogether by:

  • storing raw byte[] in BLOB - the safest and most storage-effective way. In Hibernate just use byte[] property on your POJO.

  • encode byte[] using (check out Decode Base64 data in Java) and store it as a string.

BTW remember about salting!

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
1

In my case, the bad database design push me to use a Blob in case of Clob. The solution was Map in hibernate the property with the Lob annotation and put another property in String type.

In other level of the code, when i call the get or set, i use the String property and this one, get or set the byte array value.

@Entity
@Table(name = "CMUN_TAGS")
@SequenceGenerator(name = "idSeqTag", sequenceName = "SEQ_CMUN_TAGS")
public class CmunTagsDO implements java.io.Serializable {
  private BigDecimal lngIdTag;
  private byte[] blobValTag;
  private String strValTag;

  @Id
  @Column(name = "LNG_ID_TAG", unique = true, nullable = false, precision = 20, scale = 0)
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idSeqTag")
  public BigDecimal getLngIdTag() {
    return this.lngIdTag;
  }

  public void setLngIdTag(BigDecimal lngIdTag) {
    this.lngIdTag = lngIdTag;
  }

  @Column(name = "BLOB_VAL_TAG", nullable = false)
  @Lob
  public byte[] getBlobValTag() {
    return this.blobValTag;
  }

  public void setBlobValTag(byte[] blobValTag) {
    this.blobValorTag = blobValorTag;
  }

  @Transient
  public String getStrValTag() {
    strValTag = new String(getBlobValTag());
    return strValTag;
  }

  public void setStrValTag(String strValTag) {
    setBlobValTag(strValTag.getBytes());
    this.strValTag = strValTag;
  }
}
0

You can convert your byte to an hexadecimal representation like this:

public String encryptPassword(String passwordInClear) {
            // Salt all you want here.
            MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
    byte[] digest = sha256.digest(passwordInClear.getBytes());
    return digestToString(digest);
}

private String digestToString(byte[] digest) {
    StringBuilder hashString = new StringBuilder();
    for (int i = 0; i < digest.length; i++) {
        String hex = Integer.toHexString(digest[i]);
        if (hex.length() == 1) {
            hashString.append('0');
            hashString.append(hex.charAt(hex.length() - 1));
        } else {
            hashString.append(hex.substring(hex.length() - 2));
        }
    }
    return hashString.toString();
}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • How does this make affect the strength of the hashed password? Does this approach weaken it? – Sayo Oladeji May 14 '12 at 21:27
  • @SayoStealth-virusOladeji No, it does not weaken it or strengthen it. This is a simple thing that maps a byte onto its "equivalent" string value by representing each byte by its numeric value (0 becomes "00", 16 becomes "10", 255 becomes "FF", etc...). Passing from one to the other is bidirectional but the real "strength" or "weakness" is in your digesting of the password. – Guillaume Polet May 14 '12 at 21:32
  • Great! Thanks a lot. I think I'll employ this approach since I won't have to touch the database and any other thing close to it :) – Sayo Oladeji May 14 '12 at 21:56