179

I'm trying to make a simple String to SHA1 converter in Java and this is what I've got...

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

When I pass it toSHA1("password".getBytes()), I get [�a�ɹ??�%l�3~��. I know it's probably a simple encoding fix like UTF-8, but could someone tell me what I should do to get what I want which is 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8? Or am I doing this completely wrong?

Patrick
  • 33,984
  • 10
  • 106
  • 126
Brian
  • 7,955
  • 16
  • 66
  • 107
  • It's good practice to specify the character encoding when you call `getBytes()`, for example use `toSHA1("password".getBytes("UTF-8"))` – Qwerky Feb 04 '11 at 10:57
  • possible duplicate of [Java calculate a sha1 of a String](http://stackoverflow.com/questions/4400774/java-calculate-a-sha1-of-a-string) – Tulains Córdova Dec 17 '14 at 13:31
  • 1
    @TheScrumMeister The standard name for this algorithm is _SHA-1_ **with hyphen**. See https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest – John McClane Sep 03 '20 at 08:06

13 Answers13

202

UPDATE
You can use Apache Commons Codec (version 1.7+) to do this job for you.

DigestUtils.sha1Hex(stringToConvertToSHexRepresentation)

Thanks to @Jon Onstott for this suggestion.


Old Answer
Convert your Byte Array to Hex String. Real's How To tells you how.

return byteArrayToHexString(md.digest(convertme))

and (copied from 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, you may get more compact representation using Base64. Apache Commons Codec API 1.4, has this nice utility to take away all the pain. refer here

Community
  • 1
  • 1
Nishant
  • 54,584
  • 13
  • 112
  • 127
  • 5
    base64 and sha1 are very different -- do not suggest them as alternatives. – Ryan A. Aug 22 '13 at 19:15
  • 14
    @RyanA.: As I understand it, he suggests base64 as an alternative to hex-encoding the SHA1 hash (not as an alternative to SHA1 entirely). – helmbert Aug 30 '13 at 10:22
  • I haven't tried it yet, but would you care to explain how this works ? – Jivay Sep 03 '13 at 21:24
  • 12
    Why not use a library like `DigestUtils.sha1Hex("my string")` instead of reinventing the wheel (though it's interesting to know how to convert to hex by hand)? – Jon Onstott Jun 23 '15 at 15:34
  • 4
    Because when this answer was written DigestUtils (1.7 was released in Sep 2012) did not have that feature. Thanks for pointing this out. +1 – Nishant Jun 24 '15 at 05:29
  • Do not use Apache Commons Codec. Its an unfortunately an awfully messy library. – Patrick Nov 23 '18 at 00:06
72

This is my solution of converting string to sha1. It works well in my Android app:

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,581
  • 3
  • 51
  • 63
  • 8
    Might want to specify that this is java.util.Formatter and needs a formatter.close() at the end to avoid warning. – Eric Chen Sep 13 '12 at 19:45
  • Shoudln't `encryptPassword("test")` and `echo test|sha1sum` in linux terminal output the same result ? They don't. – Tulains Córdova Dec 17 '14 at 13:14
  • 2
    @TulainsCórdova Concerning the console invocation: If you use `echo test`, the output including a line break will be piped to `sha1sum`. If you want to hash a plain string without trailing line break, you can use `echo -n test | sha1sum`. The `-n` parameter makes `echo` omitting the line break. – MrSnrub Jul 27 '18 at 00:04
  • 1
    Less to the question, but more in general: Your `encryptPassword()` sounds like that being used for storing authentication data. Note that your coding is vulnerable to dictionary attacks, as no seeding is applied. Check your security environment whether this is a problem for your application! – EagleRainbow Jan 26 '20 at 09:36
61

Using Guava Hashing class:

Hashing.sha1().hashString( "password", Charsets.UTF_8 ).toString()
dumbPotato21
  • 5,669
  • 5
  • 21
  • 34
Jan Schaefer
  • 1,882
  • 1
  • 18
  • 23
36

SHA-1 (and all other hashing algorithms) return binary data. That means that (in Java) they produce a byte[]. That byte array does not represent any specific characters, which means you can't simply turn it into a String like you did.

If you need a String, then you have to format that byte[] in a way that can be represented as a String (otherwise, just keep the byte[] around).

Two common ways of representing arbitrary byte[] as printable characters are BASE64 or simple hex-Strings (i.e. representing each byte by two hexadecimal digits). It looks like you're trying to produce a hex-String.

There's also another pitfall: if you want to get the SHA-1 of a Java String, then you need to convert that String to a byte[] first (as the input of SHA-1 is a byte[] as well). If you simply use myString.getBytes() as you showed, then it will use the platform default encoding and as such will be dependent on the environment you run it in (for example it could return different data based on the language/locale setting of your OS).

A better solution is to specify the encoding to use for the String-to-byte[] conversion like this: myString.getBytes("UTF-8"). Choosing UTF-8 (or another encoding that can represent every Unicode character) is the safest choice here.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
27

This is a simple solution that can be used when converting a string to a hex format:

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);
}
Patrick W. McMahon
  • 3,488
  • 1
  • 20
  • 31
Nikita Koksharov
  • 10,283
  • 1
  • 62
  • 71
  • 4
    Warning: Hash generation is incorrect for hashes starting with '0'. You will receive a String with 39 characters. – philn Nov 02 '16 at 12:54
  • @philn could you suggest solution? – Nikita Koksharov Nov 02 '16 at 12:54
  • 3
    I guess that if you create a big integer from a byte[] with enough leading zeros, these 0s are lost. Thus, the hex string representation "0" will not be there, leading to a hash with 39 or even less chars. I used petrnohejls solution above and it works fine... – philn Nov 02 '16 at 21:45
25

Just use the apache commons codec library. They have a utility class called DigestUtils

No need to get into details.

Timmo
  • 3,142
  • 4
  • 26
  • 43
  • 57
    I disagree, getting into the details is sort of the whole point – ninesided Feb 04 '11 at 08:55
  • 12
    The question is whether you have time to get into details or not. The whole point is usually getting it done on time.Not everyone is a student or has the luxury to learn all details. – Timmo Aug 13 '12 at 10:50
  • DigestUtils returns a byte array so to get the string representation you need to run it through Hex.encodeHexString. Java: it's 2014 and we still don't have a one step sha method – ryber Oct 07 '14 at 20:55
  • 5
    One step SHA-1 method: `String result = DigestUtils.sha1Hex("An input string")` ;o) – Jon Onstott Jun 23 '15 at 05:58
19

As mentioned before use apache commons codec. It's recommended by Spring guys as well (see DigestUtils in Spring doc). E.g.:

DigestUtils.sha1Hex(b);

Definitely wouldn't use the top rated answer here.

kazuar
  • 1,094
  • 1
  • 12
  • 14
13

It is not printing correctly because you need to use Base64 encoding. With Java 8 you can encode using Base64 encoder class.

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

Result

This will give you your expected output of 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8

Jmini
  • 9,189
  • 2
  • 55
  • 77
Eddie Martinez
  • 13,582
  • 13
  • 81
  • 106
  • 1
    @Devenv it is SHA-1 the three dots means it will maintain his original code which converts to sha1. OP's original issue was when printing the string correctly. – Eddie Martinez May 16 '17 at 15:04
  • 2
    With Base64 encodeToString(..) I get `W6ph5Mm5Pz8GgiULbPgzG37mj9g=` instead of `5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8` – Jmini Dec 22 '20 at 14:16
5

Message Digest (hash) is byte[] in byte[] out

A message digest is defined as a function that takes a raw byte array and returns a raw byte array (aka byte[]). For example SHA-1 (Secure Hash Algorithm 1) has a digest size of 160 bit or 20 bytes. Raw byte arrays cannot usually be interpreted as character encodings like UTF-8, because not every byte in every order is a legal that encoding. So converting them to a String with:

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

might create some illegal sequences or has code-pointers to undefined Unicode mappings:

[�a�ɹ??�%l�3~��.

Binary-to-text Encoding

For that binary-to-text encoding is used. With hashes, the one that is used most is the HEX encoding or Base16. Basically a byte can have the value from 0 to 255 (or -128 to 127 signed) which is equivalent to the HEX representation of 0x00-0xFF. Therefore, hex will double the required length of the output, that means a 20 byte output will create a 40 character long hex string, e.g.:

2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

Note that it is not required to use hex encoding. You could also use something like base64. Hex is often preferred because it is easier readable by humans and has a defined output length without the need for padding.

You can convert a byte array to hex with JDK functionality alone:

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

Note however that BigInteger will interpret given byte array as number and not as a byte string. That means leading zeros will not be outputted, and the resulting string may be shorter than 40 chars.

Using Libraries to Encode to HEX

You could now copy and paste an untested byte-to-hex method from Stack Overflow or use massive dependencies like Guava.

To have a go-to solution for most byte related issues I implemented a utility to handle these cases: bytes-java (GitHub)

To convert your message digest byte array you could just do

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

or you could just use the built-in hash feature

String hex =  Bytes.from(subject).hashSha1().encodeHex();
Patrick
  • 33,984
  • 10
  • 106
  • 126
3

Base 64 Representation of SHA1 Hash:

String hashedVal = Base64.getEncoder().encodeToString(DigestUtils.sha1(stringValue.getBytes(Charset.forName("UTF-8"))));
PUG
  • 4,301
  • 13
  • 73
  • 115
2

Convert byte array to hex string.

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);
}
Diego Agulló
  • 9,298
  • 3
  • 27
  • 41
abhihere
  • 143
  • 1
  • 6
1

The reason this doesn't work is that when you call String(md.digest(convertme)), you are telling Java to interpret a sequence of encrypted bytes as a String. What you want is to convert the bytes into hexadecimal characters.

Zarkonnen
  • 22,200
  • 14
  • 65
  • 81
0

Maybe this helps (works on java 17):

import org.apache.tomcat.util.codec.binary.Base64;
return new String(Base64.encodeBase64(md.digest(convertme)));
Mustafa Poya
  • 2,615
  • 5
  • 22
  • 36