Created
August 14, 2010 14:40
-
-
Save opensource21/524356 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.ppi.util; | |
import java.io.UnsupportedEncodingException; | |
import java.security.SecureRandom; | |
import java.util.Arrays; | |
import org.apache.commons.codec.DecoderException; | |
import org.apache.commons.codec.binary.Base64; | |
import org.apache.commons.codec.binary.Hex; | |
import org.apache.commons.codec.digest.DigestUtils; | |
/** | |
* Encapsulates several password-related utility functions | |
* Some functions came originally from http://www.securitydocs.com/library/3439 | |
*/ | |
public class PasswordTools { | |
private static String allowedCharacters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@,;.-=+"; | |
private static String loginCharacters = "0123456789abcdefghijklmnopqrstuvwxyz."; | |
private static Hex codec = new Hex(); | |
public static String generateRandomPassword() { | |
return generateRandomPassword(10, allowedCharacters); | |
} | |
/** | |
* Generates a random password | |
* @param size Password size | |
* @returns Random password | |
*/ | |
static String generateRandomPassword(int size) { | |
return generateRandomPassword(size, allowedCharacters); | |
} | |
/** | |
* Generates a random password out of a alowed characters | |
* @param size Password size | |
* @param allowed String comprising the allowed characters | |
* @returns Random password | |
*/ | |
static String generateRandomPassword(int size, String allowed) { | |
SecureRandom random = new SecureRandom(); | |
StringBuilder sb = new StringBuilder(); | |
for(int i = 0; i < size; i++) { | |
int rand = random.nextInt(allowed.length()); | |
sb.append(allowed.substring(rand, rand+1)); | |
} | |
return sb.toString(); | |
} | |
static String generateRandomLogin() { | |
return generateRandomLogin(10); | |
} | |
/** | |
* Returns a random string made up only of numbers, letters and the | |
* period character | |
*/ | |
static String generateRandomLogin(int size) { | |
return generateRandomPassword(size, loginCharacters); | |
} | |
static byte[] generateSalt() { | |
return generateSalt(4); | |
} | |
/** | |
* Generates a random salt of a certain size | |
* @param size How many bytes should be in the salt | |
*/ | |
static byte[] generateSalt(int size) { | |
SecureRandom random = new SecureRandom(); | |
byte[] list = new byte[size]; | |
for(int i = 0; i < list.length; i++) { | |
list[i] = (byte)random.nextInt(); | |
} | |
return list; | |
} | |
/** | |
* Combine two byte arrays | |
* | |
* @param l | |
* first byte array | |
* @param r | |
* second byte array | |
* @return byte[] combined byte array | |
*/ | |
private static byte[] concatenate(byte[] left, byte[] right) { | |
byte[] b = new byte[left.length + right.length]; | |
System.arraycopy(left, 0, b, 0, left.length); | |
System.arraycopy(right, 0, b, left.length, right.length); | |
return b; | |
} | |
/** | |
* split a byte array in two | |
* | |
* @param src | |
* byte array to be split | |
* @param n | |
* element at which to split the byte array | |
* @return byte[][] two byte arrays that have been split | |
*/ | |
private static byte[][] split(byte[] src, int n) { | |
byte[] l, r; | |
if (src == null || src.length <= n) { | |
l = src; | |
r = new byte[0]; | |
} else { | |
l = new byte[n]; | |
r = new byte[src.length - n]; | |
System.arraycopy(src, 0, l, 0, n); | |
System.arraycopy(src, n, r, 0, r.length); | |
} | |
byte[][] lr = {l , r}; | |
return lr; | |
} | |
/** | |
* SHA password and a random salt. | |
* | |
* @param password | |
* String to hash | |
* @return String Base64-encoded byte array concatenating the 32-byte hash and the salt | |
* | |
*/ | |
static byte[] saltPassword(String password) { | |
final byte[] salt = generateSalt(4); | |
final byte[] pwdBytes = password.getBytes(); | |
final byte[] hash = DigestUtils.sha(concatenate(pwdBytes, salt)); | |
return concatenate(hash, salt); | |
} | |
/** | |
* Returns a salted password base64 encoded. | |
* @see PasswordTools#saltPassword(String) | |
*/ | |
static String saltPasswordBase64(String password) { | |
String encoded = new String(Base64.encodeBase64(saltPassword(password))); | |
return encoded; | |
} | |
/** | |
* Returns a salted password hex-encoded | |
* @see PasswordTools#saltPassword(String) | |
*/ | |
static String saltPasswordHex(String password) { | |
String encoded = new String(codec.encode(saltPassword(password))); | |
return encoded; | |
} | |
/** | |
* Verifies a password against a hex-encoded digest | |
* @throws DecoderException | |
* @see PasswordTools#checkDigest(String,byte[]) | |
*/ | |
static boolean checkDigestHex(String password, String digestHex) throws DecoderException { | |
byte[] digest = codec.decode(digestHex.getBytes()); | |
return checkDigest(password, digest); | |
} | |
/** | |
* Verifies a password against a base64-encoded digest | |
* @throws UnsupportedEncodingException | |
* @see PasswordTools#checkDigest(String,byte[]) | |
*/ | |
static boolean checkDigestBase64(String password, String digestBase64) throws UnsupportedEncodingException { | |
byte[] digest = Base64.decodeBase64(digestBase64.getBytes("utf-8")); | |
return checkDigest(password, digest); | |
} | |
/** | |
* Verifies a password against a digest | |
* | |
* @param password | |
* Value to verify | |
* @param digest | |
* byte array concatenating the 32-byte hash and the salt | |
* @return boolean True if successful | |
* | |
*/ | |
static boolean checkDigest(String password, byte[] digest) { | |
// First section will contain the original hash, the next the salt | |
// SHA-1 hashes are 20-bytes in length | |
// SHA-256 hashes are 32-bytes in length | |
final int byteLength = 20; | |
byte[][] hs = split(digest, byteLength); | |
byte[] hash = hs[0]; | |
byte[] salt = hs[1]; | |
// Update digest object with byte array of clear text string and salt | |
final byte[] concat = concatenate(password.getBytes(), salt); | |
// Complete hash computation, this is now binary data | |
final byte[] pwhash; | |
pwhash = DigestUtils.sha(concat); | |
final boolean valid = Arrays.equals(hash, pwhash); | |
return valid; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.ppi.util; | |
import java.io.UnsupportedEncodingException; | |
import java.util.ArrayList; | |
import java.util.List; | |
import org.apache.commons.codec.DecoderException; | |
import junit.framework.TestCase; | |
public class PasswordToolsTest extends TestCase { | |
public void testSalt() { | |
int size = 4; | |
List<String> salts = new ArrayList<String>(); | |
for (int i = 0; i < 100; i++) { | |
String salt = PasswordTools.generateRandomPassword(size); | |
assertEquals(-1, salts.indexOf(salt)); | |
salts.add(salt); | |
} | |
assertEquals(100, salts.size()); | |
for (String salt : salts) { | |
assertEquals(size, salt.length()); | |
} | |
size = 20; | |
List<byte[]> bsalts = new ArrayList<byte[]>(); | |
for (int i = 0; i < 10; i++) { | |
bsalts.add(PasswordTools.generateSalt(size)); | |
} | |
assertEquals(10, bsalts.size()); | |
for (byte[] bs : bsalts) { | |
assertEquals(size, bs.length); | |
} | |
} | |
public void testSaltedPasswordHex() throws DecoderException { | |
String password = "superSecret"; | |
String salted = PasswordTools.saltPasswordHex(password); | |
String again = PasswordTools.saltPasswordHex(password); | |
assertFalse(salted.equals(again)); | |
assertTrue(PasswordTools.checkDigestHex(password, salted)); | |
assertTrue(PasswordTools.checkDigestHex(password, again)); | |
assertFalse(PasswordTools.checkDigestHex("SuperSecret", salted)); | |
List<String> hashes = new ArrayList<String>(); | |
for (int i = 0; i < 100; i++) { | |
// Verify we don't get duplicated hashes | |
salted = PasswordTools.saltPasswordHex(password); | |
assertEquals(-1, hashes.indexOf(salted)); | |
hashes.add(salted); | |
} | |
for (String hash : hashes) { | |
assertTrue(PasswordTools.checkDigestHex(password, hash)); | |
assertFalse(PasswordTools.checkDigestHex("SuperSecret", hash)); | |
} | |
} | |
public void testSaltedPasswordBase64() throws UnsupportedEncodingException { | |
String password = "superSecret"; | |
String salted = PasswordTools.saltPasswordBase64(password); | |
String again = PasswordTools.saltPasswordBase64(password); | |
assertFalse(salted.equals(again)); | |
assertTrue(PasswordTools.checkDigestBase64(password, salted)); | |
assertTrue(PasswordTools.checkDigestBase64(password, again)); | |
assertFalse(PasswordTools.checkDigestBase64("SuperSecret", salted)); | |
List<String> hashes = new ArrayList<String>(); | |
for (int i = 0; i < 100; i++) { | |
// Verify we don't get duplicated hashes | |
salted = PasswordTools.saltPasswordBase64(password); | |
assertEquals(-1, hashes.indexOf(salted)); | |
hashes.add(salted); | |
} | |
for (String hash : hashes) { | |
assertTrue(PasswordTools.checkDigestBase64(password, hash)); | |
assertFalse(PasswordTools.checkDigestBase64("SuperSecret", hash)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment