Практически каждый раз, когда речь идет о профилях пользователей, необходимо управлять учетными данными пользователей и, таким образом, иметь возможность создавать и хранить пароли пользователей. Общепринятой практикой является использование хешированных и соленых паролей для подготовки к раскрытию базы данных и обращению хэшей с помощью радужных таблиц .
Тем не менее, (к сожалению) весьма обычно находить пароли, хранящиеся в открытом тексте (мы пропустим список крупных игроков, которым пришлось научиться делать это правильно на своем сложном пути). Последствия утечки базы данных с незашифрованными, не хэшированными и несолеными паролями должны быть очевидны… .. Второй худший способ сделать это — использовать хешированные, но несоленые пароли. В этом случае радужные таблицы или сервисы обращения к хешу в сети, такие как эта или эта, имеют огромную помощь. И, наконец, третий худший способ — полагаться только на зашифрованные записи — когда ключ или расшифрованная база данных просочились, игра окончена!
Так как это сделать правильно? Простой ответ: используйте PBKDF2WithHmacSHA1 вместе с солт-числом. Пример того, как его использовать, можно найти здесь . Эта реализация выглядит зрелой, но сложной. Если вы просто хотите почувствовать концепцию посола паролей, вы можете взглянуть на следующий демонстрационный код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
public static final String HASH_ALGORITHM = "SHA-256" ; public static final Charset DEFAULT_CHARSET = Charset.forName( "UTF-8" ); private static final char [] PASSWORD_CHARS = new char []{ '!' , '@' , '#' , '$' , '%' , '&' , '*' , '(' , ')' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' }; public String getRandomString( final int length) { /* * Don't try to seed SecureRandom yourself unless you know * what you are doing! * @see Randomly failed! Weaknesses in Java Pseudo Random Number Generators (PRNGs). */ SecureRandom secureRandom = new SecureRandom(); StringBuilder sb = new StringBuilder(length); int position = 0 ; // create a random string of the requested length from a set of allowed chars for ( int i = 0 ; i < length; i++ ) { position = secureRandom.nextInt(PASSWORD_CHARS.length); sb.append(PASSWORD_CHARS[position]); } return sb.toString(); } public static byte [] createPasswordHash( final String password, final String salt) { byte [] result = null ; try { MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); digest.update(salt.getBytes(DEFAULT_CHARSET)); digest.update(password.getBytes(DEFAULT_CHARSET)); result = digest.digest(); } catch (NoSuchAlgorithmException e) { // TODO Logging } return result; } public static boolean checkPassword( final User user, final String password) { boolean result = false ; String storedPasswordHash = user.getPwHash(); String salt = user.getSalt(); byte [] checkPasswordHashBytes = createPasswordHash(password, salt); String checkPasswordHash = encodeBase64(checkPasswordHashBytes); // for simplicity let's say we use Base64 if (checkPasswordHash != null && storedPasswordHash != null && checkPasswordHash.equals(storedPasswordHash)) { result = true ; } return result; } |
Код ожидает некоторый объект User с полями pwHash и salt (оба поля не чувствительны!) Для хранения необходимой информации. Этот пользовательский объект может (в случае отсутствия других конфиденциальных данных, связанных с объектом) безопасно сохраняться. Даже если база данных утечка, злоумышленник должен либо грубо форсировать комбинацию пароль-соль, либо вычислить радужную таблицу для соленого пароля. Обратите внимание, что эту радужную таблицу нельзя использовать для одного и того же пароля с другой солью! Это означает, что для случайно выбранной соли злоумышленнику понадобятся радужные таблицы, чтобы изменить хеш-функцию для каждой соли — даже если пароль остается прежним!
Приведенный выше код максимально прост. Например, getRandomString можно использовать повторно для создания значения соли и, возможно, для генерации временного пароля во время процесса регистрации. Но помните, этот код далеко не пригоден для использования в производственных средах!
Несколько заключительных замечаний: обязательно используйте соль, которая достаточно длинна, предотвратите повторное использование солей и используйте сильные алгоритмы для хеширования!