Сам подход хорош;SHA-256 сам по себе является сильной односторонней функцией хеширования.Это не может быть "расшифровано".Но это быстро, что позволяет быстро перебирать пароль с помощью словаря.
Для большей безопасности вы можете замедлить работу, например, с помощью bcrypt или PBKDF2.Некоторые 100 мс не будут заметны для пользователя, но делают грубое принуждение нецелесообразным.
Вот пример с PBKDF2, использующим 100000 итераций SHA-256.Он также использует случайную соль.
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec("my-secret-password".toCharArray(), salt, 100000, 256);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = f.generateSecret(spec).getEncoded();
Base64.Encoder enc = Base64.getEncoder();
System.out.printf("salt: %s%n", enc.encodeToString(salt));
System.out.printf("hash: %s%n", enc.encodeToString(hash));
Примечание: PBKDF2WithHmacSHA256 доступен с Java 8.
Вот более полный пример:
private static final SecureRandom random = new SecureRandom();
/**
* One-way encrypts (hashes) the given password.
*
* @param saltpw the salt (will be generated when null)
* @param pw the password to encrypt
* @return encrypted salted password
*/
public static String encrypt(String saltpw, String pw) throws GeneralSecurityException {
byte[] salt;
if (saltpw == null) {
salt = new byte[16];
random.nextBytes(salt);
} else {
salt = Base64.getDecoder().decode(saltpw.replaceFirst("\\$.*", ""));
}
KeySpec spec = new PBEKeySpec(pw.toCharArray(), salt, 100000, 256);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = f.generateSecret(spec).getEncoded();
Base64.Encoder enc = Base64.getEncoder();
return enc.encodeToString(salt) + "$" + enc.encodeToString(hash);
}
public static void main(String[] args) throws Exception {
String enc = encrypt(null, "my-secret-password");
System.out.printf("enc : %s\n", enc);
String test1 = encrypt(enc, "my-secret-password");
System.out.printf("test 1: %s, valid: %b\n", test1, enc.equals(test1));
String test2 = encrypt(enc, "some-other-password");
System.out.printf("test 2: %s, valid: %b\n", test2, enc.equals(test2));
}
Печать:
enc : B5V6SjkjJpeOxvMAkPf7EA==$NNDA7o+Dpd+M+H99WVxY0B8adqVWJHZ+HIjgPxMljwo=
test 1: B5V6SjkjJpeOxvMAkPf7EA==$NNDA7o+Dpd+M+H99WVxY0B8adqVWJHZ+HIjgPxMljwo=, valid: true
test 2: B5V6SjkjJpeOxvMAkPf7EA==$4H1SpH8N+/jqU40G6RWb+ReHUB3C58iAaU4l39j+TV8=, valid: false
Обратите внимание, что test 1
приводит к точно такой же зашифрованной строке, что и исходный пароль, а test 2
(с неверным паролем) - нет.Таким образом, вы можете проверить, является ли предоставленный пароль действительным или нет, просто сравнив хэши.