 4a39525809
			
		
	
	
		4a39525809
		
			
		
	
	
	
	
		
			
			Support for bcrypt, sha256, pbkdf2 and plain text ofc. This makes password-based auth actually secure.
		
			
				
	
	
		
			160 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package com.ryanmichela.sshd;
 | |
| 
 | |
| import java.security.spec.InvalidKeySpecException;
 | |
| import java.util.Arrays;
 | |
| import java.security.MessageDigest;
 | |
| import java.security.NoSuchAlgorithmException;
 | |
| import java.security.SecureRandom;
 | |
| 
 | |
| import javax.crypto.SecretKeyFactory;
 | |
| import javax.crypto.spec.PBEKeySpec;
 | |
| 
 | |
| import com.ryanmichela.sshd.BCrypt;
 | |
| 
 | |
| import java.math.BigInteger;
 | |
| 
 | |
| // You should run `openssl speed` to see which parts of these algorithms may need
 | |
| // tweaking in the future as CPUs and GPUs get faster to crack these hashing algos.
 | |
| 
 | |
| 
 | |
| class Cryptography 
 | |
| {
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 	// BCrypt-based password hashing algorithm
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| 	public static String BCrypt_HashPassword(String password) throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		// This algo handles the salt itself.
 | |
| 		return BCrypt.hashpw(password, BCrypt.gensalt());
 | |
| 	}
 | |
| 
 | |
| 	public static Boolean BCrypt_ValidatePassword(String password, String ConfigPassword) throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		// Unfortunately, the BCrypt library uses String.compareTo which is not
 | |
| 		// hardened against timing attacks so we have to compare the password
 | |
| 		// ourselves otherwise it doesn't work well.
 | |
| 		String test = BCrypt.hashpw(password, ConfigPassword);
 | |
| 		return TimingSafeCmp(test.getBytes(), ConfigPassword.getBytes());
 | |
| 	}
 | |
| 
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 	// SHA256-based password hashing algorithm
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| 	public static String SHA256_HashPassword(String password) throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		MessageDigest md = MessageDigest.getInstance("SHA-256");
 | |
| 		byte[] salt		 = GetSalt();
 | |
| 		int iterations   = 500000; // sha256 is a fast algo to make lots of hashes for, 
 | |
| 		                           // try and make it kinda computationally expensive.
 | |
| 		md.update(salt);
 | |
| 		byte[] bytes = md.digest(password.getBytes());
 | |
| 
 | |
| 		// Hash it a few thousand times.
 | |
| 		for (int i = 0; i < iterations; i++)
 | |
| 			bytes = md.digest(bytes);
 | |
| 		
 | |
| 		StringBuilder sb = new StringBuilder();
 | |
| 		for (int i = 0; i < bytes.length; i++)
 | |
| 			sb.append(Integer.toString((bytes[i] & 0xFF) + 0x100, 16).substring(1));
 | |
| 
 | |
| 		return iterations + "$" + ToHex(salt) + "$" + sb.toString();
 | |
| 	}
 | |
| 
 | |
| 	public static Boolean SHA256_ValidatePassword(String password, String ConfigPassword) throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		String[] hparts = ConfigPassword.split("\\$");
 | |
| 		int iterations = Integer.parseInt(hparts[0]);
 | |
| 		byte[] salt = FromHex(hparts[1]);
 | |
| 		String hash = hparts[2];
 | |
| 
 | |
| 		MessageDigest md = MessageDigest.getInstance("SHA-256");
 | |
| 
 | |
| 		md.update(salt);
 | |
| 		byte[] bytes = md.digest(password.getBytes());
 | |
| 
 | |
| 		// Hash it a few thousand times.
 | |
| 		for (int i = 0; i < iterations; i++)
 | |
| 			bytes = md.digest(bytes);
 | |
| 
 | |
| 		StringBuilder sb = new StringBuilder();
 | |
| 		for (int i = 0; i < bytes.length; i++)
 | |
| 			sb.append(Integer.toString((bytes[i] & 0xFF) + 0x100, 16).substring(1));
 | |
| 
 | |
| 		return TimingSafeCmp(hash.getBytes(), sb.toString().getBytes());
 | |
| 	}
 | |
| 
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 	// PBKDF2-based password hashing algoritm
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 	public static String PBKDF2_HashPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException
 | |
| 	{
 | |
| 		char[] passwdchars = password.toCharArray();
 | |
| 		int iterations	 = 20000; // NOTE: Change this as CPUs get faster
 | |
| 		// First: Start getting 16 bytes of guaranteed random data to use for our salt
 | |
| 		byte[] salt = GetSalt();
 | |
| 
 | |
| 		PBEKeySpec spec = new PBEKeySpec(passwdchars, salt, iterations, 64*8);
 | |
| 		SecretKeyFactory skf  = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
 | |
| 		byte[] hash = skf.generateSecret(spec).getEncoded();
 | |
| 		return iterations + "$" + ToHex(salt) + "$" + ToHex(hash);
 | |
| 	}
 | |
| 
 | |
| 	public static Boolean PBKDF2_ValidateHash(String password, String ConfigPassword) throws NoSuchAlgorithmException, InvalidKeySpecException
 | |
| 	{
 | |
| 		String[] hparts = ConfigPassword.split("\\$");
 | |
| 		int iterations = Integer.parseInt(hparts[0]);
 | |
| 		byte[] salt = FromHex(hparts[1]);
 | |
| 		byte[] hash = FromHex(hparts[2]);
 | |
| 
 | |
| 		PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, hash.length * 8);
 | |
| 		SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
 | |
| 		byte[] cmphash = skf.generateSecret(spec).getEncoded();
 | |
| 
 | |
| 		return TimingSafeCmp(cmphash, hash);
 | |
| 	}
 | |
| 
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 	// Utility Functions
 | |
| 	///////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| 	public static byte[] GetSalt() throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
 | |
| 		byte[] salt		= new byte[16];
 | |
| 		sr.nextBytes(salt);
 | |
| 		return salt;
 | |
| 	}
 | |
| 
 | |
| 	// This is a string comparitor function safe against timing attacks.
 | |
| 	public static boolean TimingSafeCmp(byte[] str1, byte[] str2)
 | |
| 	{
 | |
| 		int diff = str1.length ^ str2.length;
 | |
| 		for (int i = 0; i < str1.length && i < str2.length; i++)
 | |
| 			diff |= str1[i] ^ str2[i];
 | |
| 
 | |
| 		return diff == 0;
 | |
| 	}
 | |
| 
 | |
|   	private static byte[] FromHex(String hex) throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		byte[] bytes = new byte[hex.length() / 2];
 | |
| 		for (int i = 0; i < bytes.length; i++)
 | |
| 		{
 | |
| 			bytes[i] = (byte)Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
 | |
| 		}
 | |
| 		return bytes;
 | |
| 	}
 | |
| 
 | |
| 	private static String ToHex(byte[] array) throws NoSuchAlgorithmException
 | |
| 	{
 | |
| 		BigInteger bi			 = new BigInteger(1, array);
 | |
| 		String	 hex			 = bi.toString(16);
 | |
| 		int		   paddingLength = (array.length * 2) - hex.length();
 | |
| 		if (paddingLength > 0)
 | |
| 			return String.format("%0" + paddingLength + "d", 0) + hex;
 | |
| 		else
 | |
| 			return hex;
 | |
| 	}
 | |
| } |