. Введение
Генерация хеш-значения пароля представляет собой зашифрованную последовательность символов, полученную путем вычисления пароля, предоставленного пользователем, по определенному алгоритму. Существует множество реализаций хеш-алгоритмов в Java, которые доказали свою эффективность в обеспечении безопасности паролей, и я расскажу о некоторых из них в этом посте.
Следует отметить, что после того, как хэш пароля будет сгенерирован и сохранен в базе данных, вы не сможете преобразовать его обратно в открытый текст пароля. Только каждый раз, когда пользователь входит в приложение, хэш-значение должно быть перегенерировано, чтобы соответствовать хэш-значению в базе данных, чтобы завершить проверку пароля.
2. Простая реализация защиты паролем с использованием алгоритма MD5
Алгоритм MD5 Message-Digest (алгоритм MD5 Message-Digest) — это широко используемая хеш-функция шифрования, в основном используемая для генерации 128-битного (16-байтового) хеш-значения. Его идея реализации очень проста и легка для понимания.Его основная идея заключается в отображении наборов данных переменной длины в наборы данных фиксированной длины. Для этого он разбивает входящее сообщение на 512-битные куски. Убедитесь, что его длину можно разделить на 512, дополнив сообщение до конца. Теперь эти блоки будут обрабатываться алгоритмом MD5 и результатом будет 128-битное хэш-значение. После использования MD5 результирующее хеш-значение обычно представляет собой 32-битное шестнадцатеричное число.
В этой статье зашифрованный открытый текст называется «сообщением», а хэш-значение, сгенерированное после шифрования, называется «дайджестом сообщения» или, для краткости, «дайджестом». Ниже приведен пример кода для MD5 для создания хеш-значения:
public class SimpleMD5Example
{
public static void main(String[] args)
{
String passwordToHash = “password”;
String generatedPassword = null;
try {
// Create MessageDigest instance for MD5
MessageDigest md = MessageDigest.getInstance(“MD5”);
//Add password bytes to digest
md.update(passwordToHash.getBytes());
//Get the hash’s bytes
byte[] bytes = md.digest();
//This bytes[] has bytes in decimal format;
//Convert it to hexadecimal format
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++)
{
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
//Get complete hashed password in hex format
generatedPassword = sb.toString();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
System.out.println(generatedPassword);
}
}
Хотя MD5 является широко распространенным хеш-алгоритмом, он небезопасен, а сгенерированное хэш-значение довольно слабое. Его главное преимущество заключается в том, что он быстро генерируется и легко реализуется. Однако это также означает, что он уязвим для перебора и атак по словарю. Например, с помощью радужной таблицы, сгенерированной открытым текстом и хэшем, можно быстро найти исходные данные, соответствующие известному хэшу.
Кроме того, MD5 не избегает коллизий хэшей: это означает, что разные пароли приведут к одному и тому же значению хеша.
Однако, если вам все еще нужно использовать MD5, рассмотрите возможность добавления соли, чтобы еще больше обеспечить его безопасность.
Используйте соль, чтобы сделать сгенерированный MD5 более безопасным
Здесь следует отметить, что добавление соли не уникально для MD5, вы также можете применить его к другим алгоритмам. Итак, здесь вы просто фокусируетесь на том, как он применяется, а не на его связи с MD5.
Определение соли в Википедии — это добавление дополнительных данных к паролю или фразе-паролю путем получения случайных данных через одностороннюю функцию. Проще говоря, хэш генерируется путем генерации некоторого случайного текста и добавления его к паролю.
Основная цель добавления соли в Hash — предотвратить атаки предварительно вычисленных радужных таблиц. Преимущество добавления slat сейчас состоит в том, чтобы изменить исходное одно сравнение на несколько сравнений, чтобы замедлить угадывание значения Hash пароля, в противном случае эффективность взлома библиотеки паролей Hash будет очень высокой.
Важно: в Java нам всегда нужно использовать SecureRandom для генерации хорошего солт-значения, поэтому мы можем использовать алгоритм «SHA1PRNG», предоставляемый классом SecureRandom, для генерации псевдослучайных чисел. Код выглядит следующим образом:
private static String getSalt() throws NoSuchAlgorithmException
{
//Always use a SecureRandom generator
SecureRandom sr = SecureRandom.getInstance(“SHA1PRNG”);
//Create array for salt
byte[] salt = new byte[16];
//Get a random salt
sr.nextBytes(salt);
//return salt
return salt.toString();
}
Алгоритм SHA1PRNG — это генератор псевдослучайных чисел с высокой степенью конфиденциальности, основанный на алгоритме SHA-1. Обратите внимание, что если вы не предоставите ему случайное начальное число, оно сгенерирует начальное число (TRNG) из истинного случайного числа. Аннотация: О TRGN и PRGN.
Далее посмотрите наХэш MD5 плюс планкапример кода:
public class SaltedMD5Example
{
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException
{
String passwordToHash = “password”;
String salt = getSalt();
String securePassword = getSecurePassword(passwordToHash, salt);
System.out.println(securePassword); //Prints 83ee5baeea20b6c21635e4ea67847f66
String regeneratedPassowrdToVerify = getSecurePassword(passwordToHash, salt);
System.out.println(regeneratedPassowrdToVerify); //Prints 83ee5baeea20b6c21635e4ea67847f66
}
private static String getSecurePassword(String passwordToHash, String salt)
{
String generatedPassword = null;
try {
// Create MessageDigest instance for MD5
MessageDigest md = MessageDigest.getInstance(“MD5”);
//Add password bytes to digest
md.update(salt.getBytes());
//Get the hash’s bytes
byte[] bytes = md.digest(passwordToHash.getBytes());
//This bytes[] has bytes in decimal format;
//Convert it to hexadecimal format
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++)
{
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
//Get complete hashed password in hex format
generatedPassword = sb.toString();
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return generatedPassword;
}
//Add salt
private static String getSalt() throws NoSuchAlgorithmException, NoSuchProviderException
{
//Always use a SecureRandom generator
SecureRandom sr = SecureRandom.getInstance(“SHA1PRNG”, “SUN”);
//Create array for salt
byte[] salt = new byte[16];
//Get a random salt
sr.nextBytes(salt);
//return salt
return salt.toString();
}
}
ВАЖНО: обратите внимание, что теперь вам нужно хранить значение slat для каждого хэша пароля. Потому что, когда пользователь входит в систему, вы должны использовать первоначально сгенерированную планку для повторной генерации хэша, чтобы он соответствовал сохраненному хэшу. Если вы используете другую планку (создаете случайную планку), то сгенерированный хэш будет другим.
Кроме того, вы, возможно, много раз слышали о перемешивании и солении. Обычно это означает создание пользовательских комбинаций, таких как:
salt+password+salt => hash
На самом деле вам не нужно этого делать, потому что это не поможет вам еще больше укрепить безопасность Hash. Если вам нужна более высокая безопасность, правильно будет выбрать лучший алгоритм.
Умеренная криптографическая безопасность реализована с использованием алгоритма SHA.
SHA (алгоритм безопасного хеширования) также является членом семейства криптографических хеш-функций. Он очень похож на MD5, за исключением того, что генерируемый им хэш более безопасен, чем MD5. Однако генерируемые ими хэши не всегда уникальны, а это значит, что при вводе двух разных значений будет получен один и тот же хэш. Обычно, когда это происходит, мы называем это «столкновением». Однако вероятность коллизии SHA меньше, чем у MD5. Вам даже не нужно беспокоиться о столкновениях, так как это очень редко.
В Java существует четыре реализации алгоритмов SHA, и по сравнению с MD5 (128-битный хэш) он обеспечивает хеш следующей длины:
- SHA-1 (простая реализация — 160-битный хеш)
- SHA-256 (надежнее, чем SHA-1 — 256-битный хеш)
- SHA-384 (надежнее, чем SHA-256 — 384-битный хэш)
- SHA-512 (надежнее, чем SHA-384 — 512-битный хэш)
Обычно чем длиннее хэш, тем сложнее его взломать, это основная идея.
Чтобы получить реализацию соответствующего алгоритма, вы можете передать параметр MessageDigest для получения экземпляра. Следующее:
MessageDigest md = MessageDigest.getInstance(“SHA-1”);
//OR
MessageDigest md = MessageDigest.getInstance(“SHA-256”);
Давайте посмотрим на применение SHA через тестовую программу:
package com.howtodoinjava.hashing.password.demo.sha;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class SHAExample {
public static void main(String[] args) throws NoSuchAlgorithmException {
String passwordToHash = “password”;
String salt = getSalt();
String securePassword = get_SHA_1_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
securePassword = get_SHA_256_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
securePassword = get_SHA_384_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
securePassword = get_SHA_512_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
}
private static String get_SHA_1_SecurePassword(String passwordToHash, String salt)
{
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance(“SHA-1”);
md.update(salt.getBytes());
byte[] bytes = md.digest(passwordToHash.getBytes());
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++)
{
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return generatedPassword;
}
private static String get_SHA_256_SecurePassword(String passwordToHash, String salt)
{
//Use MessageDigest md = MessageDigest.getInstance(“SHA-256”);
}
private static String get_SHA_384_SecurePassword(String passwordToHash, String salt)
{
//Use MessageDigest md = MessageDigest.getInstance(“SHA-384”);
}
private static String get_SHA_512_SecurePassword(String passwordToHash, String salt)
{
//Use MessageDigest md = MessageDigest.getInstance(“SHA-512”);
}
//Add salt
private static String getSalt() throws NoSuchAlgorithmException
{
SecureRandom sr = SecureRandom.getInstance(“SHA1PRNG”);
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt.toString();
}
}
Как показано в приведенном выше коде, при использовании SHA вы также можете добавить к нему соль, чтобы усилить его безопасность.
Более высокая криптографическая безопасность реализована с использованием алгоритма PBKDF2WithHmacSHA1.
До сих пор мы видели, как создать безопасный хэш для пароля и сделать его более безопасным, используя соль. Но проблема сегодня в том, что аппаратное обеспечение намного быстрее, чем любая атака методом перебора с использованием словаря или радужной таблицы, и любой пароль может быть взломан, вопрос лишь в том, сколько времени это займет.
Чтобы решить эту проблему, основная идея состоит в том, чтобы максимально замедлить скорость атаки грубой силы, чтобы минимизировать потери. Наш следующий алгоритм также основан на этой концепции. Цель состоит в том, чтобы сделать хеш-функцию достаточно медленной, чтобы препятствовать атаке, но при этом быть очень быстрой для пользователя и не испытывать заметной задержки.
Обычно это достигается с помощью некоторых алгоритмов с интенсивным использованием ЦП, таких как PBKDF2, Bcrypt или Scrypt. Эти алгоритмы принимают коэффициент работы (также называемый коэффициентом безопасности) или количество итераций в качестве параметра, чтобы определить, насколько медленной станет хеш-функция, и по мере увеличения вычислительной мощности в будущем коэффициент работы можно постепенно увеличивать, чтобы соответствовать вычислительной мощности Достичь баланса.
Алгоритм "PBKDF2" может быть реализован на Java через "PBKDF2WithHmacSHA1". Следующий код является примером использования:
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String originalPassword = “password”;
String generatedSecuredPasswordHash = generateStorngPasswordHash(originalPassword);
System.out.println(generatedSecuredPasswordHash);
}
private static String generateStorngPasswordHash(String password) throws NoSuchAlgorithmException, InvalidKeySpecException
{
int iterations = 1000;
char[] chars = password.toCharArray();
byte[] salt = getSalt().getBytes();
PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 64 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);
byte[] hash = skf.generateSecret(spec).getEncoded();
return iterations + “:” + toHex(salt) + “:” + toHex(hash);
}
private static String getSalt() throws NoSuchAlgorithmException
{
SecureRandom sr = SecureRandom.getInstance(“SHA1PRNG”);
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt.toString();
}
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;
}
}
Следующим шагом, когда вы вернетесь, вам нужно предоставить реализацию метода проверки логина и пароля:
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String originalPassword = “password”;
String generatedSecuredPasswordHash = generateStorngPasswordHash(originalPassword);
System.out.println(generatedSecuredPasswordHash);
boolean matched = validatePassword(“password”, generatedSecuredPasswordHash);
System.out.println(matched);
matched = validatePassword(“password1”, generatedSecuredPasswordHash);
System.out.println(matched);
}
private static boolean validatePassword(String originalPassword, String storedPassword) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String[] parts = storedPassword.split(“:”);
int iterations = Integer.parseInt(parts[0]);
byte[] salt = fromHex(parts[1]);
byte[] hash = fromHex(parts[2]);
PBEKeySpec spec = new PBEKeySpec(originalPassword.toCharArray(), salt, iterations, hash.length 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);
byte[] testHash = skf.generateSecret(spec).getEncoded();
int diff = hash.length ^ testHash.length;
for(int i = 0; i < hash.length && i < testHash.length; i++)
{
diff |= hash[i] ^ testHash[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;
}
Будьте осторожны при обращении к приведенному выше коду, если вы обнаружите какие-либо проблемы, загрузите код, прикрепленный в конце этой статьи.
, Три, более безопасная реализация пароля с использованием алгоритмов bcrypt и scrypt.
Идея bcrypt аналогична PBKDF2. Просто в Java нет встроенной поддержки реализации алгоритма bcrypt, который замедляет атакующих, но вы все равно можете найти и скачать его исходный код.
Ниже приведен пример использования кода bcrypt (где Bcrypt.java был предоставлен в исходном коде):
public class BcryptHashingExample
{
public static void main(String[] args) throws NoSuchAlgorithmException
{
String originalPassword = “password”;
String generatedSecuredPasswordHash = BCrypt.hashpw(originalPassword, BCrypt.gensalt(12));
System.out.println(generatedSecuredPasswordHash);
boolean matched = BCrypt.checkpw(originalPassword, generatedSecuredPasswordHash);
System.out.println(matched);
}
}
Укажите адрес загрузки файла bcrypt.java:скачать
Подобно алгоритму bcrypt, я скачал исходный код алгоритма scrypt с github и добавил его в загрузку исходного кода в последнем разделе. Посмотрите, как он используется:
public class ScryptPasswordHashingDemo
{
public static void main(String[] args) {
String originalPassword = “password”;
String generatedSecuredPasswordHash = SCryptUtil.scrypt(originalPassword, 16, 16, 16);
System.out.println(generatedSecuredPasswordHash);
boolean matched = SCryptUtil.check(“password”, generatedSecuredPasswordHash);
System.out.println(matched);
matched = SCryptUtil.check(“passwordno”, generatedSecuredPasswordHash);
System.out.println(matched);
}
}
4. Заключительные замечания
Хранение паролей в открытом виде в приложении чрезвычайно опасно.
MD5 обеспечивает наиболее базовое безопасное генерирование хэша, и к нему следует добавить планку для дальнейшего повышения его безопасности.
MD5 генерирует 128-битный хэш. Чтобы сделать его более безопасным, 160-битный или 512-битный хэш должен быть сгенерирован с использованием алгоритма SHA, причем 512-битный является самым надежным.
Хотя пароли, использующие SHA Hash, также могут быть взломаны современным быстрым оборудованием, чтобы избежать этого, вам нужен алгоритм, который делает атаки грубой силы максимально медленными и минимально эффективными. В это время вы можете использовать PBKDF2,
Алгоритм BCrypt или SCrypt.
После тщательного рассмотрения выберите подходящий алгоритм безопасности.
Перепечатано с:Блог Woohoo.cn на .com/inter's RP/afraid/…
В статье есть что-то неуместное, пожалуйста, поправьте меня, вы также можете обратить внимание на мой паблик WeChat:好好学java, для получения качественных ресурсов.