Ciąg Java do SHA1

158

Próbuję zrobić prosty konwerter String na SHA1 w Javie i oto, co mam ...

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));
}

Kiedy go toSHA1("password".getBytes())zdaję [�a�ɹ??�%l�3~��., wiem, że to prawdopodobnie prosta poprawka do kodowania, taka jak UTF-8, ale czy ktoś może mi powiedzieć, co mam zrobić, aby uzyskać to, czego chcę, czyli 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8? A może robię to całkowicie źle?

Brian
źródło
Algorytm jest SHA1bez łącznika, nie wiem, czy to coś zmieni.
The Scrum Meister
Dobrą praktyką jest określenie kodowania znaków podczas dzwonienia getBytes(), na przykład użyjtoSHA1("password".getBytes("UTF-8"))
Qwerky
możliwy duplikat Javy oblicza sha1 łańcucha
Tulains Córdova,

Odpowiedzi:

183

AKTUALIZACJA
Możesz użyć Apache Commons Codec (wersja 1.7+), aby wykonać to zadanie za Ciebie.

DigestUtils.sha1Hex (stringToConvertToSHexRepresentation)

Dzięki @ Jon Onstott za tę sugestię.


Stara odpowiedź
Zamień tablicę bajtów na ciąg szesnastkowy. Real's How To powie Ci, jak to zrobić .

return byteArrayToHexString(md.digest(convertme))

i (skopiowane z 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;
}

BTW, możesz uzyskać bardziej zwartą reprezentację przy użyciu Base64. Apache Commons Codec API 1.4 , ma to przyjemne narzędzie, które usuwa cały ból. patrz tutaj

Nishant
źródło
4
base64 i sha1 są bardzo różne - nie sugeruj ich jako alternatywy.
Ryan A.
13
@RyanA .: Jak rozumiem, sugeruje on base64 jako alternatywę dla kodowania szesnastkowego skrótu SHA1 (nie jako całkowicie alternatywę dla SHA1).
helmbert
Jeszcze tego nie próbowałem, ale czy możesz wyjaśnić, jak to działa?
Jivay,
11
Dlaczego nie skorzystać z biblioteki, takiej jak DigestUtils.sha1Hex("my string")zamiast wymyślać koło na nowo (chociaż warto wiedzieć, jak ręcznie przekonwertować na hex)?
Jon Onstott
3
Ponieważ kiedy napisano tę odpowiedź, DigestUtils (wersja 1.7 została wydana we wrześniu 2012 r.) Nie miała tej funkcji. Dziękuję za zwrócenie uwagi. +1
Nishant
67

To jest moje rozwiązanie polegające na konwersji ciągu znaków na sha1. Działa dobrze w mojej aplikacji na Androida:

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
źródło
7
Można określić, że jest to java.util.Formatter i wymaga na końcu elementu formatującego.close (), aby uniknąć ostrzeżenia.
Eric Chen
Czy nie powinno encryptPassword("test")i echo test|sha1sumw terminalu linux wyświetla ten sam wynik? Oni tego nie robią.
Tulains Córdova,
@ TulainsCórdova Odnośnie wywołania konsoli: Jeśli używasz echo test, dane wyjściowe zawierające podział wiersza zostaną przesłane potokiem do sha1sum. Jeśli chcesz haszować zwykły ciąg bez końca linii, możesz użyć echo -n test | sha1sum. -nParametr sprawia, echopomijając podział wiersza.
MrSnrub
Mniej do pytania, ale bardziej ogólnie: Twoje encryptPassword()dźwięki są takie, jak używane do przechowywania danych uwierzytelniających. Zwróć uwagę, że kodowanie jest podatne na ataki słownikowe, ponieważ nie jest stosowane seeding. Sprawdź swoje środowisko bezpieczeństwa, czy nie stanowi to problemu dla Twojej aplikacji!
EagleRainbow
54

Korzystanie z klasy Guava Hashing :

Hashing.sha1().hashString( "password", Charsets.UTF_8 ).toString()
Jan Schaefer
źródło
1
Ta odpowiedź może wymagać aktualizacji, ponieważ spowoduje to teraz ostrzeżenie, że haszowanie jest niestabilne.
Semir Deljić
32

SHA-1 (i wszystkie inne algorytmy haszujące) zwracają dane binarne. Oznacza to, że (w Javie) tworzą plik byte[]. Ta bytetablica nie reprezentuje żadnych konkretnych znaków, co oznacza, że ​​nie możesz po prostu zmienić jej w Stringtaką, jaką zrobiłeś.

Jeśli potrzebujesz a String, musisz sformatować to byte[]w sposób, który można przedstawić jako a String(w przeciwnym razie po prostu zachowaj byte[]wokół).

Dwa powszechne sposoby przedstawiania dowolnych byte[]znaków drukowalnych to BASE64 lub proste ciągi szesnastkowe (tj. Reprezentowane byteprzez dwie cyfry szesnastkowe). Wygląda na to, że próbujesz utworzyć ciąg szesnastkowy.

Jest też inna pułapka: jeśli chcesz uzyskać SHA-1 w Javie String, musisz przekonwertować to Stringna byte[]pierwszy (ponieważ wejście SHA-1 to również byte[]a). Jeśli po prostu użyjesz myString.getBytes()tak, jak pokazałeś, użyje domyślnego kodowania platformy i jako takie będzie zależne od środowiska, w którym go uruchomisz (na przykład może zwrócić różne dane na podstawie ustawień języka / ustawień regionalnych twojego systemu operacyjnego).

Lepszym rozwiązaniem jest, aby określić kodowanie użyć do String-to- byte[]konwersji tak: myString.getBytes("UTF-8"). Wybór UTF-8 (lub innego kodowania, które może reprezentować każdy znak Unicode) jest tutaj najbezpieczniejszym wyborem.

Joachim Sauer
źródło
27

To proste rozwiązanie, którego można użyć podczas konwersji ciągu znaków na format szesnastkowy:

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);
}
Nikita Koksharov
źródło
Ostrzeżenie: generowanie skrótu jest nieprawidłowe dla skrótów zaczynających się od „0”. Otrzymasz ciąg z 39 znakami.
philn
@philn czy możesz zaproponować rozwiązanie?
Nikita Koksharov
1
Wydaje mi się, że jeśli utworzysz dużą liczbę całkowitą z bajtu [] z wystarczającą liczbą zer wiodących, te zera zostaną utracone. Zatem ciąg szesnastkowy reprezentujący „0” nie będzie tam, co prowadzi do skrótu z 39 lub nawet mniejszą liczbą znaków. Użyłem roztworu petrnohejlsa powyżej i działa dobrze ...
philn
25

Po prostu użyj biblioteki kodeków Apache commons. Mają klasę narzędzi o nazwie DigestUtils

Nie ma potrzeby wchodzenia w szczegóły.

DaTroop
źródło
51
Nie zgadzam się, wchodzenie w szczegóły jest w pewnym sensie
sednem sprawy
12
Pytanie brzmi, czy masz czas, aby zagłębić się w szczegóły, czy nie. Zwykle chodzi o to, aby zrobić to na czas, nie każdy jest studentem lub ma luksus, aby poznać wszystkie szczegóły.
DaTroop
DigestUtils zwraca tablicę bajtów, więc aby uzyskać reprezentację w postaci ciągu, musisz uruchomić ją przez Hex.encodeHexString. Java: jest rok 2014 i nadal nie mamy metody jednoetapowej sha
ryber
5
Jednoetapowa metoda SHA-1 String result = DigestUtils.sha1Hex("An input string")
:;
18

Jak wspomniano wcześniej, użyj kodeka Apache commons. Jest również polecany przez Springa (zobacz DigestUtils w dokumencie Spring). Na przykład:

DigestUtils.sha1Hex(b);

Zdecydowanie nie użyłbym tutaj najwyżej ocenionej odpowiedzi.

kazuar
źródło
6

Nie drukuje poprawnie, ponieważ musisz użyć kodowania Base64. W Javie 8 możesz kodować za pomocą klasy kodera Base64 .

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

Wynik

To da ci oczekiwaną wydajność 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8

Eduardo Dennis
źródło
1
@Devenv to SHA-1 trzy kropki oznaczają, że zachowa swój oryginalny kod, który zostanie przekonwertowany na sha1. Pierwotny problem OP dotyczył prawidłowego drukowania ciągu.
Eduardo Dennis
4

Message Digest (hash) to bajt [] w bajcie []

Podsumowanie wiadomości jest zdefiniowane jako funkcja, która pobiera nieprzetworzoną tablicę bajtów i zwraca surową tablicę bajtów (aka byte[]). Na przykład SHA-1 (Secure Hash Algorithm 1) ma rozmiar skrótu 160 bitów lub 20 bajtów. Tablice surowych bajtów zwykle nie mogą być interpretowane jako kodowanie znaków, takie jak UTF-8 , ponieważ nie każdy bajt w każdej kolejności jest legalnym kodowaniem. Więc konwertuj je na a Stringz:

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

może tworzyć niedozwolone sekwencje lub ma wskaźniki kodu do niezdefiniowanych mapowań Unicode :

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

Kodowanie plików binarnych na tekst

W tym celu używane jest kodowanie binarne na tekst . W przypadku skrótów najczęściej używanym kodowaniem jest kodowanie HEX lub Base16 . Zasadniczo bajt może mieć wartość od 0do 255(lub -128do ze znakiem 127), która jest równoważna reprezentacji szesnastkowej 0x00- 0xFF. Dlatego hex podwoi wymaganą długość wyjścia, co oznacza, że ​​20-bajtowe wyjście utworzy ciąg o długości 40 znaków, np .:

2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

Zauważ, że nie jest wymagane stosowanie kodowania szesnastkowego. Możesz też użyć czegoś takiego jak base64 . Hex jest często preferowany, ponieważ jest łatwiejszy do odczytania przez ludzi i ma określoną długość wyjściową bez potrzeby wypełniania.

Możesz przekonwertować tablicę bajtów na szesnastkową za pomocą samej funkcjonalności JDK:

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

Zauważ jednak, że BigIntegerzinterpretuje podaną tablicę bajtów jako liczbę a nie jako ciąg bajtów. Oznacza to, że wiodące zera nie zostaną wyprowadzone, a wynikowy ciąg może być krótszy niż 40 znaków.

Używanie bibliotek do kodowania w formacie HEX

Możesz teraz skopiować i wkleić nieprzetestowaną metodę bajt-szesnastkowy ze Stack Overflow lub użyć ogromnych zależności, takich jak Guava .

Aby znaleźć rozwiązanie większości problemów związanych z bajtami, zaimplementowałem narzędzie do obsługi następujących przypadków: bytes-java (Github)

Aby przekonwertować tablicę bajtów skrótu wiadomości, możesz po prostu zrobić

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

lub możesz po prostu użyć wbudowanej funkcji skrótu

String hex =  Bytes.from(subject).hashSha1().encodeHex();
Patrick Favre
źródło
2

Podstawowa reprezentacja SHA1skrótu 64 :

String hashedVal = Base64.getEncoder().encodeToString(DigestUtils.sha1(stringValue.getBytes(Charset.forName("UTF-8"))));
MOPS
źródło
1

Powodem, dla którego to nie działa, jest to, że dzwoniąc String(md.digest(convertme)), mówisz Javie, aby zinterpretował sekwencję zaszyfrowanych bajtów jako ciąg. To, czego chcesz, to konwersja bajtów na znaki szesnastkowe.

Zarkonnen
źródło
0

Konwertuj tablicę bajtów na ciąg szesnastkowy.

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
źródło