// Copyright (c) 2001 Beyond.com <dustin@beyond.com> package net.spy.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import net.spy.SpyObject; /** * Digest for getting checksums, hashing passwords, stuff like that. */ public class Digest extends SpyObject { private static final String DEFAULTHASH="SHA"; private boolean prefixHash=true; private final String hashAlg; private final int digLen; /** * Get a new Digest object. */ public Digest() { this(DEFAULTHASH); } /** * Get a Digest object with the given hash algorithm. */ public Digest(String alg) { super(); hashAlg=alg; MessageDigest d=getMessageDigest(); digLen=d.getDigestLength(); assert digLen != 0 : "Couldn't calculate digest length"; } /** * Get the hash type to be used by this password digest thing. */ public String getHashAlg() { return hashAlg; } /** * If set to true, hashes will be prefixed with the type of hash used. * (i.e. {SHA}, {SSHA}, etc...). * * @param doit whether or not to prefix the hash */ public void prefixHash(boolean doit) { prefixHash=doit; } // SSHA = 20 bytes of SHA data + a random salt. Perl for checking can // be found here: // http://developer.netscape.com/docs/technote/ldap/pass_sha.html /** * Check a plaintext password against a hashed password. */ public boolean checkPassword(String pw, String hash) { boolean rv=false; String htype=hash.substring(0, hash.indexOf('}')+1).toUpperCase(); if(htype.equals("{" + hashAlg + "}") || htype.equals("{S" + hashAlg + "}")) { String data=hash.substring(hash.indexOf('}')+1); Base64 base64d=new Base64(); byte[] datab=base64d.decode(data); byte[] salt=new byte[datab.length-digLen]; System.arraycopy(datab, digLen, salt, 0, salt.length); String newhash=getHash(pw, salt); rv=hash.equals(newhash); } else { getLogger().warn("Invalid hash type ``%s'' in %s", htype, hash); } return(rv); } private String getPrefix(String p) { String rv=""; if(prefixHash) { rv=p; } return(rv); } /** * Get a hash for a String with a known salt. This should only be used * for verification, don't be stupid and start handing out words with * static salts. */ protected String getHash(String word, byte salt[]) { MessageDigest md = getMessageDigest(); md.update(word.getBytes()); md.update(salt); byte[] pwhash=md.digest(); String hout = getPrefix("{S" + hashAlg + "}") + Base64.getInstance().encode(cat(pwhash, salt)); return(hout.trim()); } private MessageDigest getMessageDigest() { MessageDigest md=null; try { md=MessageDigest.getInstance(hashAlg); } catch(NoSuchAlgorithmException e) { throw new RuntimeException("No such digest: " + hashAlg, e); } return md; } /** * Get the hash for a given string (with no salt). * * @param s the thing to hash * @return the hash */ public byte[] getSaltFreeHashBytes(String s) { MessageDigest md = getMessageDigest(); md.update(s.getBytes()); byte[] hash=md.digest(); return(hash); } /** * Get a hash for a String with no salt. This should only be used for * checksumming, not passwords. */ public String getSaltFreeHash(String s) { String hout = getPrefix("{" + hashAlg + "}") + Base64.getInstance().encode(getSaltFreeHashBytes(s)); return(hout); } /** * Get a hash for a given String. */ public String getHash(String word) { // 8 bytes of salt should be enough sodium for anyone byte[] salt=new byte[8]; SecureRandom sr=new SecureRandom(); sr.nextBytes(salt); return(getHash(word, salt)); } private byte[] cat(byte a[], byte b[]) { byte[] r= new byte [a.length + b.length]; System.arraycopy (a, 0, r, 0, a.length); System.arraycopy (b, 0, r, a.length, b.length); return(r); } }