75

In Objective C I've been using the following code to hash a string:

-(NSString *) sha1:(NSString*)stringToHash {    
    const char *cStr = [stringToHash UTF8String];
    unsigned char result[20];
    CC_SHA1( cStr, strlen(cStr), result );
    return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
        result[0], result[1], result[2], result[3], 
        result[4], result[5], result[6], result[7],
        result[8], result[9], result[10], result[11],
        result[12], result[13], result[14], result[15],
        result[16], result[17], result[18], result[19]
        ];  
}

Now I need the same for Android but can't find out how to do it. I've been looking for example at this: Make SHA1 encryption on Android? but that doesn't give me the same result as on iPhone. Can anyone point me in the right direction?

Community
  • 1
  • 1
Martin
  • 7,190
  • 9
  • 40
  • 48

11 Answers11

161

You don't need andorid for this. You can just do it in simple java.

Have you tried a simple java example and see if this returns the right sha1.

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class AeSimpleSHA1 {
    private static String convertToHex(byte[] data) {
        StringBuilder buf = new StringBuilder();
        for (byte b : data) {
            int halfbyte = (b >>> 4) & 0x0F;
            int two_halfs = 0;
            do {
                buf.append((0 <= halfbyte) && (halfbyte <= 9) ? (char) ('0' + halfbyte) : (char) ('a' + (halfbyte - 10)));
                halfbyte = b & 0x0F;
            } while (two_halfs++ < 1);
        }
        return buf.toString();
    }

    public static String SHA1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] textBytes = text.getBytes("iso-8859-1");
        md.update(textBytes, 0, textBytes.length);
        byte[] sha1hash = md.digest();
        return convertToHex(sha1hash);
    }
}

Also share what your expected sha1 should be. Maybe ObjectC is doing it wrong.

Ratata Tata
  • 2,781
  • 1
  • 34
  • 49
Amir Raminfar
  • 33,777
  • 7
  • 93
  • 123
  • Thanks for this answer! The convertToHex() I was using was doing it wrong. Your code is working fine. – Martin May 12 '11 at 16:05
  • 24
    Why iso-8559-1 encoding instead of UTF-8? These is little reason to use such legacy encodings unless you need to be compatible with some existing application. – CodesInChaos Feb 15 '13 at 12:53
  • 8
    As someone mentioned below, text.length() isn't correct, as it won't return the number of bytes. You need to use the length of the array returned by getBytes. And, yeah, it really probably shouldn't be iso-8859. – Jesse Rusak Feb 25 '13 at 17:19
  • Can "md" ever be null on Android ? – android developer Nov 06 '16 at 15:06
40

A simpler SHA-1 method: (updated from the commenter's suggestions, also using a massively more efficient byte->string algorithm)

String sha1Hash( String toHash )
{
    String hash = null;
    try
    {
        MessageDigest digest = MessageDigest.getInstance( "SHA-1" );
        byte[] bytes = toHash.getBytes("UTF-8");
        digest.update(bytes, 0, bytes.length);
        bytes = digest.digest();

        // This is ~55x faster than looping and String.formating()
        hash = bytesToHex( bytes );
    }
    catch( NoSuchAlgorithmException e )
    {
        e.printStackTrace();
    }
    catch( UnsupportedEncodingException e )
    {
        e.printStackTrace();
    }
    return hash;
}

// http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex( byte[] bytes )
{
    char[] hexChars = new char[ bytes.length * 2 ];
    for( int j = 0; j < bytes.length; j++ )
    {
        int v = bytes[ j ] & 0xFF;
        hexChars[ j * 2 ] = hexArray[ v >>> 4 ];
        hexChars[ j * 2 + 1 ] = hexArray[ v & 0x0F ];
    }
    return new String( hexChars );
}
Adam
  • 25,966
  • 23
  • 76
  • 87
  • 2
    if the sha1 string is supposed to start with a 0, then the 0 is left out. So this method returns a wrong sha-1 encoded string in some cases. – Melvin Dec 20 '12 at 12:44
  • 2
    WARNING : not work if you want to hash '1122', it's start with 0 – NicoMinsk Jan 21 '13 at 11:45
  • Correct! Updated the answer to deal with this. Thanks for catching this guys. – Adam Jan 22 '13 at 21:59
  • 1
    As the code is currently it just returns a hex version of original string, no hashing. Need to call digest.digest() somewhere. – eselk Feb 04 '13 at 19:41
  • 4
    Using default encoding(`somestring.GetBytes()`) will give you platform dependent hashes. Not nice. Use a fixed encoding, preferably UTF-8. – CodesInChaos Feb 15 '13 at 12:54
33

If you can get away with using Guava it is by far the simplest way to do it, and you don't have to reinvent the wheel:

final HashCode hashCode = Hashing.sha1().hashString(yourValue, Charset.defaultCharset());

You can then take the hashed value and get it as a byte[], as an int, or as a long.

No wrapping in a try catch, no shenanigans. And if you decide you want to use something other than SHA-1, Guava also supports sha256, sha 512, and a few I had never even heard about like adler32 and murmur3.

yarian
  • 5,922
  • 3
  • 34
  • 48
  • 3
    I really like this answer because it fits into Android so well, and doesn't need me to double check SO code for security holes :p – Muz May 10 '16 at 11:16
21
final MessageDigest digest = MessageDigest.getInstance("SHA-1");
result = digest.digest(stringToHash.getBytes("UTF-8"));

// Another way to construct HEX, my previous post was only the method like your solution
StringBuilder sb = new StringBuilder();

for (byte b : result) // This is your byte[] result..
{
    sb.append(String.format("%02X", b));
}

String messageDigest = sb.toString();
James Riordan
  • 1,139
  • 1
  • 10
  • 25
Suphi ÇEVİKER
  • 175
  • 1
  • 2
19

Totally based on @Whymarrh's answer, this is my implementation, tested and working fine, no dependencies:

public static String getSha1Hex(String clearString)
{
    try
    {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        messageDigest.update(clearString.getBytes("UTF-8"));
        byte[] bytes = messageDigest.digest();
        StringBuilder buffer = new StringBuilder();
        for (byte b : bytes)
        {
            buffer.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        }
        return buffer.toString();
    }
    catch (Exception ignored)
    {
        ignored.printStackTrace();
        return null;
    }
}
cprcrack
  • 17,118
  • 7
  • 88
  • 91
8

Android comes with Apache's Commons Codec - or you add it as dependency. Then do:

String myHexHash = DigestUtils.shaHex(myFancyInput);

That is the old deprecated method you get with Android 4 by default. The new versions of DigestUtils bring all flavors of shaHex() methods like sha256Hex() and also overload the methods with different argument types.

http://commons.apache.org/proper/commons-codec//javadocs/api-release/org/apache/commons/codec/digest/DigestUtils.html

Risadinha
  • 16,058
  • 2
  • 88
  • 91
5

with Kotlin this can be shortened and put into one line:

MessageDigest.getInstance("SHA-1").digest(theString.toByteArray()).joinToString("") { "%02x".format(it) }
BeniBela
  • 16,412
  • 4
  • 45
  • 52
5

To simplify it using extentions funcions on kotlin:

/**
 * Encrypt String to SHA1 format
 */
fun String.toSha1(): String {
    return MessageDigest
        .getInstance("SHA-1")
        .digest(this.toByteArray())
        .joinToString(separator = "", transform = { "%02x".format(it) })
}
MakiX
  • 326
  • 3
  • 8
2

The method you are looking for is not specific to Android, but to Java in general. You're looking for the MessageDigest (import java.security.MessageDigest).

An implementation of a sha512(String s) method can be seen here, and the change for a SHA-1 hash would be changing line 71 to:

MessageDigest md = MessageDigest.getInstance("SHA-1");
Whymarrh
  • 13,139
  • 14
  • 57
  • 108
2

Here is the Kotlin version to get SHA encryption string.

import java.security.MessageDigest

object HashUtils {
    fun sha512(input: String) = hashString("SHA-512", input)

    fun sha256(input: String) = hashString("SHA-256", input)

    fun sha1(input: String) = hashString("SHA-1", input)

    /**
     * Supported algorithms on Android:
     *
     * Algorithm    Supported API Levels
     * MD5          1+
     * SHA-1        1+
     * SHA-224      1-8,22+
     * SHA-256      1+
     * SHA-384      1+
     * SHA-512      1+
     */
    private fun hashString(type: String, input: String): String {
        val HEX_CHARS = "0123456789ABCDEF"
        val bytes = MessageDigest
                .getInstance(type)
                .digest(input.toByteArray())
        val result = StringBuilder(bytes.size * 2)

        bytes.forEach {
            val i = it.toInt()
            result.append(HEX_CHARS[i shr 4 and 0x0f])
            result.append(HEX_CHARS[i and 0x0f])
        }

        return result.toString()
    }
}

Its originally posted here: https://www.samclarke.com/kotlin-hash-strings/

Hitesh Dhamshaniya
  • 2,088
  • 2
  • 16
  • 23
-19
String.format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", result[0], result[1], result[2], result[3], 
    result[4], result[5], result[6], result[7],
    result[8], result[9], result[10], result[11],
    result[12], result[13], result[14], result[15],
    result[16], result[17], result[18], result[19]);
Suphi ÇEVİKER
  • 175
  • 1
  • 2