package io.vertx.ext.auth.mongo.impl;
import io.vertx.core.VertxException;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.mongo.HashAlgorithm;
import io.vertx.ext.auth.mongo.HashSaltStyle;
import io.vertx.ext.auth.mongo.HashStrategy;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Random;
@Deprecated
public class DefaultHashStrategy implements HashStrategy {
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
private static final int WORK_FACTOR = 10000;
private HashSaltStyle saltStyle;
private String externalSalt;
private HashAlgorithm algorithm;
private SecretKeyFactory skf;
private MessageDigest md;
public DefaultHashStrategy() {
saltStyle = HashSaltStyle.COLUMN;
}
public DefaultHashStrategy(String externalSalt) {
saltStyle = HashSaltStyle.EXTERNAL;
this.externalSalt = externalSalt;
}
private synchronized void initMessageDigest() {
try {
if (md == null) {
md = MessageDigest.getInstance("SHA-512");
}
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException("PBKDF2 is not available", nsae);
}
}
private synchronized void initKeyFactory() {
try {
if (skf == null) {
skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
}
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException("PBKDF2 is not available", nsae);
}
}
@Override
public String computeHash(String password, User user) {
switch (saltStyle) {
case NO_SALT:
return password;
case COLUMN:
case EXTERNAL:
String salt = getSalt(user);
return computeHash(password, salt);
default:
throw new UnsupportedOperationException("Not existing, saltstyle " + saltStyle);
}
}
@Override
public String getStoredPwd(User user) {
String fieldPassword = user.principal().getString(MongoAuthImpl.PROPERTY_FIELD_PASSWORD);
return fieldPassword != null ? user.principal().getString(fieldPassword) : null;
}
@Override
public String getSalt(User user) {
switch (saltStyle) {
case NO_SALT:
return null;
case COLUMN:
String fieldSalt = user.principal().getString(MongoAuthImpl.PROPERTY_FIELD_SALT);
return fieldSalt != null ? user.principal().getString(fieldSalt) : null;
case EXTERNAL:
return externalSalt;
default:
throw new UnsupportedOperationException("Not existing, saltstyle " + saltStyle);
}
}
@Override
public void setSaltStyle(HashSaltStyle saltStyle) {
this.saltStyle = saltStyle;
}
@Override
public HashSaltStyle getSaltStyle() {
return saltStyle;
}
@Override
public void setAlgorithm(HashAlgorithm algorithm) {
switch (algorithm) {
case SHA512:
initMessageDigest();
this.algorithm = algorithm;
break;
case PBKDF2:
initKeyFactory();
this.algorithm = algorithm;
break;
default:
throw new VertxException("Algorithm " + algorithm + " not supported");
}
}
private String computeHash(String password, String salt) {
if (algorithm == null) {
setAlgorithm(HashAlgorithm.SHA512);
}
try {
switch (algorithm) {
case SHA512:
String concat = (salt == null ? "" : salt) + password;
return bytesToHex(md.digest(concat.getBytes(StandardCharsets.UTF_8)));
case PBKDF2:
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(),
salt == null ? new byte[]{} : salt.getBytes(StandardCharsets.UTF_8),
WORK_FACTOR,
64 * 8);
return bytesToHex(skf.generateSecret(spec).getEncoded());
default:
throw new VertxException("Can't compute hash for algorithm: " + algorithm);
}
} catch (InvalidKeySpecException e) {
throw new VertxException(e);
}
}
public static String generateSalt() {
final Random r = new SecureRandom();
byte[] salt = new byte[32];
r.nextBytes(salt);
return bytesToHex(salt);
}
private static String bytesToHex(byte[] bytes) {
char[] chars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
int x = 0xFF & bytes[i];
chars[i * 2] = HEX_CHARS[x >>> 4];
chars[1 + i * 2] = HEX_CHARS[0x0F & x];
}
return new String(chars);
}
@Override
public void setExternalSalt(String externalSalt) {
this.externalSalt = externalSalt;
}
}