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;
|
||
|
}
|
||
|
}
|