Saltar al contenido principal

Hashes en Java

Es posible crear hashes en Java usando las clases que vienen con el JDK:

  • Para hashing rápido (checksums e integridad) usa MessageDigest (MD5, SHA-1, SHA-256, etc.).
  • Para hashes de contraseñas usa un derivador de clave seguro como PBKDF2 (disponible en Java estándar vía SecretKeyFactory) y siempre combina con salt y muchas iteraciones.

Hash simple con MessageDigest (SHA-256)

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashUtils {

public static String sha256Hex(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashed = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hashed);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 no disponible", e);
}
}

private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}

// ejemplo de uso
public static void main(String[] args) {
String texto = "hola mundo";
System.out.println("SHA-256: " + sha256Hex(texto));
}
}
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashPassword {
public static String hashPassword(String password) {
try {
// Crear una instancia de MessageDigest para SHA-256
MessageDigest digest = MessageDigest.getInstance("SHA-256");

// Pasar la contraseña como bytes al digest
byte[] hash = digest.digest(password.getBytes());

// Convertir el hash a una cadena hexadecimal
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}

return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}

Consideraciones:

  • Usa SHA-256 (o SHA-512) en vez de MD5 o SHA-1 si buscas integridad; MD5/SHA-1 están rotos frente a colisiones.
  • MessageDigest es parte de java.security. No necesitas librerías externas.

Hash seguro para contraseñas: PBKDF2 (salt + iteraciones)

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class PasswordHash {

private static final String ALGO = "PBKDF2WithHmacSHA256";
private static final int ITERATIONS = 100_000; // ajustar según necesidades
private static final int KEY_LENGTH = 256; // bits
private static final int SALT_LENGTH = 16; // bytes

public static String generateSaltBase64() {
byte[] salt = new byte[SALT_LENGTH];
new SecureRandom().nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}

public static String hashPasswordBase64(char[] password, byte[] salt) {
try {
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory skf = SecretKeyFactory.getInstance(ALGO);
byte[] hash = skf.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Error al hashear password", e);
}
}

public static boolean verifyPassword(char[] attempt, byte[] salt, byte[] expectedHash) {
String attemptHashB64 = hashPasswordBase64(attempt, salt);
String expectedB64 = Base64.getEncoder().encodeToString(expectedHash);
// Comparación resistente a timing attacks:
return MessageDigest.isEqual(attemptHashB64.getBytes(), expectedB64.getBytes());
}
}
  1. Cuando el usuario se registra:
    • Genera salt con generateSaltBase64().
    • Hashea la contraseña con hashPasswordBase64(...).
    • Guarda en DB: salt y hash (ambos en Base64), y también la info de iteraciones/algoritmo si quieres migrar más tarde.
  2. Al verificar login:
    • Recupera salt y hash de la base de datos, calcula hash del intento y compara de forma segura.

Consideraciones:

  • PBKDF2WithHmacSHA256 está disponible en Java 8+ (depende del proveedor, pero suele estar).
  • Ajusta ITERATIONS según rendimiento y seguridad (más iteraciones = más lento para atacantes).
  • Para máxima seguridad se recomiendan algoritmos como Argon2, pero Argon2 requiere bibliotecas externas si tu JDK no lo soporta.

Consejos prácticos y de seguridad

  • Para integridad/identificación: SHA-256 es una buena opción.
  • Para contraseñas: nunca uses SHA-256 directo; usa PBKDF2, scrypt o Argon2 con salt y muchas iteraciones.
  • Nunca guardes contraseñas en texto plano. Guarda salt + hash + algoritmo/iteraciones.
  • Usa SecureRandom para generar salt.
  • Para comparar hashes, usa MessageDigest.isEqual(...) para evitar vulnerabilidades por timing.
  • Codifica bytes en hex o Base64 para almacenamiento legible.
  • Evita reusar salts. Cada contraseña debe tener su salt único.
  • Si necesitas un hash no reversible para tokens, puedes combinar valores con un secreto (HMAC). Java incluye Mac en javax.crypto para HMAC (HmacSHA256) sin librería externa.