Строка Java для SHA1

158

Я пытаюсь сделать простой конвертер строки в SHA1 в Java, и это то, что у меня есть ...

public static String toSHA1(byte[] convertme) {
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA-1");
    }
    catch(NoSuchAlgorithmException e) {
        e.printStackTrace();
    } 
    return new String(md.digest(convertme));
}

Когда я передаю его toSHA1("password".getBytes()), я понимаю, [�a�ɹ??�%l�3~��.что это, вероятно, простое исправление кодировки, такое как UTF-8, но кто-то может сказать мне, что я должен сделать, чтобы получить то, что я хочу 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8? Или я делаю это совершенно неправильно?

Брайан
источник
Алгоритм SHA1без дефиса, не знаю, будет ли это иметь значение.
Scrum Meister
Хорошей практикой является указание кодировки символов при вызове getBytes(), например usetoSHA1("password".getBytes("UTF-8"))
Qwerky
возможный дубликат Java вычисляет sha1 строки
Tulains Córdova

Ответы:

183

ОБНОВЛЕНИЕ
Вы можете использовать кодек Apache Commons (версия 1.7+), чтобы сделать эту работу за вас.

DigestUtils.sha1Hex (stringToConvertToSHexRepresentation)

Спасибо @ Jon Onstott за это предложение.


Старый ответ
Преобразование массива байтов в шестнадцатеричную строку. Real's How To рассказывает вам, как .

return byteArrayToHexString(md.digest(convertme))

и (скопировано из Real's How To)

public static String byteArrayToHexString(byte[] b) {
  String result = "";
  for (int i=0; i < b.length; i++) {
    result +=
          Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
  }
  return result;
}

Кстати, вы можете получить более компактное представление с помощью Base64. Apache Commons Codec API 1.4 , имеет эту полезную утилиту, чтобы убрать всю боль. обратитесь сюда

Nishant
источник
4
base64 и sha1 очень разные - не предлагайте их в качестве альтернативы.
Райан А.
13
@RyanA .: Насколько я понимаю, он предлагает base64 в качестве альтернативы шестнадцатеричному кодированию хеша SHA1 (не как альтернативу SHA1 полностью).
helmbert
Я еще не пробовал, но не могли бы вы объяснить, как это работает?
Дживай
11
Почему бы не использовать библиотеку, например, DigestUtils.sha1Hex("my string")вместо того, чтобы заново изобретать колесо (хотя интересно знать, как конвертировать в гекс вручную)?
Джон Онстотт
3
Потому что, когда этот ответ был написан, DigestUtils (1.7 был выпущен в сентябре 2012 года) не имел этой функции. Спасибо за указание на это. +1
Нишант
67

Это мое решение для преобразования строки в sha1. Это хорошо работает в моем приложении для Android:

private static String encryptPassword(String password)
{
    String sha1 = "";
    try
    {
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(password.getBytes("UTF-8"));
        sha1 = byteToHex(crypt.digest());
    }
    catch(NoSuchAlgorithmException e)
    {
        e.printStackTrace();
    }
    catch(UnsupportedEncodingException e)
    {
        e.printStackTrace();
    }
    return sha1;
}

private static String byteToHex(final byte[] hash)
{
    Formatter formatter = new Formatter();
    for (byte b : hash)
    {
        formatter.format("%02x", b);
    }
    String result = formatter.toString();
    formatter.close();
    return result;
}
petrnohejl
источник
7
Возможно, вам захочется указать, что это java.util.Formatter, и в конце ему нужен formatter.close (), чтобы избежать предупреждения.
Эрик Чен
Не должно ли encryptPassword("test")и echo test|sha1sumв терминале Linux вывести тот же результат? Они не
Тулаинс Кордова
@ TulainsCórdova Относительно вызова консоли: Если вы используете echo test, вывод, включая разрыв строки, будет передан по конвейеру sha1sum. Если вы хотите хэшировать простую строку без завершающего переноса строки, вы можете использовать echo -n test | sha1sum. -nПараметр позволяет echoопустить разрыв строки.
MrSnrub
Меньше вопроса, но больше в целом: ваши encryptPassword()звуки похожи на те, которые используются для хранения аутентификационных данных. Обратите внимание, что ваше кодирование уязвимо для атак по словарю, так как не применяется заполнение. Проверьте вашу среду безопасности, является ли это проблемой для вашего приложения!
EagleRainbow
54

Использование класса Guava Hashing :

Hashing.sha1().hashString( "password", Charsets.UTF_8 ).toString()
Ян Шефер
источник
1
Этот ответ может нуждаться в обновлении, так как теперь он выдаст предупреждение о том, что хеширование нестабильно.
Семир Делич
32

SHA-1 (и все другие алгоритмы хеширования) возвращают двоичные данные. Это означает, что (на Java) они производят byte[]. Этот byteмассив не представляет никаких конкретных символов, а это значит, что вы не можете просто превратить его в то, Stringчто делали.

Если вам нужно String, то вы должны отформатировать byte[]это так, чтобы это можно было представить как String(иначе, просто оставьте все как есть byte[]).

Два обычных способа представления произвольных byte[]символов для печати - это BASE64 или простые шестнадцатеричные строки (т.е.byte из двух шестнадцатеричных цифр). Похоже, вы пытаетесь создать шестнадцатеричную строку.

Есть и еще один подводный камень: если вы хотите получить SHA-1 Java String, то вам нужно преобразовать Stringего в byte[]первое (поскольку вход SHA-1 также является byte[]). Если вы просто используете, myString.getBytes()как вы показали, то он будет использовать кодировку платформы по умолчанию и, как таковой, будет зависеть от среды, в которой вы его запускаете (например, он может возвращать разные данные в зависимости от настроек языка / локали вашей ОС).

Лучшее решение указать кодировку для использования для String-До- byte[]преобразования , как это: myString.getBytes("UTF-8"). Выбор UTF-8 (или другой кодировки, которая может представлять каждый символ Unicode) - самый безопасный выбор здесь.

Йоахим Зауэр
источник
27

Это простое решение, которое можно использовать при преобразовании строки в шестнадцатеричный формат:

private static String encryptPassword(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {

    MessageDigest crypt = MessageDigest.getInstance("SHA-1");
    crypt.reset();
    crypt.update(password.getBytes("UTF-8"));

    return new BigInteger(1, crypt.digest()).toString(16);
}
Никита Кокшаров
источник
Предупреждение: генерация хэша неверна для хэшей, начинающихся с '0'. Вы получите строку с 39 символами.
Philn
@philn не могли бы вы предложить решение?
Никита Кокшаров
1
Я предполагаю, что если вы создадите большое число из байта [] с достаточным количеством нулей, эти 0 будут потеряны. Таким образом, шестнадцатеричное строковое представление "0" там не будет, что приведет к хешу с 39 или даже менее символами. Я использовал решение Petrnohejls выше, и он отлично работает ...
philn
25

Просто используйте библиотеку кодеков Apache Commons. У них есть служебный класс, называемый DigestUtils

Не нужно вдаваться в подробности.

DaTroop
источник
51
Я не согласен,
вникание
12
Вопрос в том, успеете ли вы вникнуть в подробности или нет. Весь смысл обычно состоит в том, чтобы сделать это вовремя. Не каждый студент или может позволить себе узнать все детали.
DaTroop
DigestUtils возвращает байтовый массив, поэтому для получения строкового представления вам необходимо запустить его через Hex.encodeHexString. Java: сейчас 2014 год, и у нас до сих пор нет метода шага ша
Рибер
5
String result = DigestUtils.sha1Hex("An input string")
Одношаговый
18

Как упоминалось ранее, используйте кодек Apache Commons. Это также рекомендуется парнями из Spring (см. DigestUtils в Spring doc). Например:

DigestUtils.sha1Hex(b);

Определенно не буду использовать здесь самый лучший ответ.

kazuar
источник
7

Печатается неправильно, потому что вам нужно использовать кодировку Base64. С Java 8 вы можете кодировать, используя класс кодировщика Base64 .

public static String toSHA1(byte[] convertme) {
    md = MessageDigest.getInstance("SHA-1");
    return Base64.getEncoder().encodeToString((md.digest(convertme));
}

результат

Это даст вам ожидаемый результат 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8

Эдуардо Деннис
источник
1
@Devenv - это SHA-1, три точки означают, что он сохранит свой исходный код, который преобразуется в sha1. Оригинальная проблема OP была при правильной печати строки.
Эдуардо Деннис
4

Дайджест сообщения (хеш) - это байт [] в байте [] из

Дайджест сообщения определяется как функция, которая принимает необработанный байтовый массив и возвращает необработанный байтовый массив (он же byte[]). Например, SHA-1 (Secure Hash Algorithm 1) имеет размер дайджеста 160 бит или 20 байт. Необработанные байтовые массивы обычно не могут быть интерпретированы как кодировки символов, такие как UTF-8 , потому что не каждый байт в каждом порядке является законным кодированием. Так что конвертируем их в String:

new String(md.digest(subject), StandardCharsets.UTF_8)

может создавать недопустимые последовательности или иметь указатели кода на неопределенные отображения Unicode :

[�a�ɹ??�%l3~��.

Двоичное в текстовое кодирование

Для этого используется двоичное кодирование текста . Для хэшей чаще всего используется кодировка HEX или Base16 . По существу, байт может иметь значение от 0до 255(или -128со 127знаком), которое эквивалентно шестнадцатеричному представлению 0x00- 0xFF. Поэтому hex будет удваивать требуемую длину вывода, что означает, что 20-байтовый вывод создаст строку длиной в 40 символов, например:

2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

Обратите внимание, что не требуется использовать шестнадцатеричное кодирование. Вы также можете использовать что-то вроде base64 . Шестнадцатеричный кодекс часто предпочитается, потому что он легче читается людьми и имеет определенную выходную длину без необходимости заполнения.

Вы можете преобразовать байтовый массив в шестнадцатеричный код только с помощью функции JDK:

new BigInteger(1, token).toString(16)

Однако обратите внимание, что этот BigIntegerмассив байтов будет интерпретироваться как число, а не как строка байтов. Это означает, что начальные нули не будут выводиться, а результирующая строка может быть короче 40 символов.

Использование библиотек для кодирования в HEX

Теперь вы можете скопировать и вставить непроверенный метод преобразования байтов в шестнадцатеричный код из Stack Overflow или использовать массивные зависимости, такие как Guava .

Чтобы получить решение для большинства проблем, связанных с байтами, я реализовал утилиту для обработки этих случаев: bytes-java (Github)

Для преобразования вашего байта массива дайджеста сообщения вы можете просто сделать

String hex = Bytes.wrap(md.digest(subject)).encodeHex();

или вы можете просто использовать встроенную функцию хеширования

String hex =  Bytes.from(subject).hashSha1().encodeHex();
Патрик Фавр
источник
2

Base 64 Представление SHA1хэша:

String hashedVal = Base64.getEncoder().encodeToString(DigestUtils.sha1(stringValue.getBytes(Charset.forName("UTF-8"))));
мОПС
источник
1

Причина, по которой это не работает, заключается в том, что когда вы вызываете String(md.digest(convertme)), вы говорите Java интерпретировать последовательность зашифрованных байтов как строку. Вам нужно преобразовать байты в шестнадцатеричные символы.

Zarkonnen
источник
0

Преобразовать байтовый массив в шестнадцатеричную строку.

public static String toSHA1(byte[] convertme) {
    final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA-1");
    }
    catch(NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    byte[] buf = md.digest(convertme);
    char[] chars = new char[2 * buf.length];
    for (int i = 0; i < buf.length; ++i) {
        chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
        chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
    }
    return new String(chars);
}
abhihere
источник