SHA256 Hash results different across Android & iOS for Big numbers
Asked Answered
P

2

8

I'm trying to Hash a BigInteger/BigNum and I'm getting different results in Android/iOS. I need to get the same Hash result so that both the apps work as per the SRP protocol. On closer inspection it is working fine for positive numbers but not working for negative numbers (first nibble greater than 7). Not sure which one is correct and which one is to be adjusted to match with the other.

Android:

    void hashBigInteger(String s) {
    try {
        BigInteger a = new BigInteger(s, 16);
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        byte[] b = a.toByteArray();
        sha.update(b, 0, b.length);
        byte[] digest = sha.digest();
        BigInteger d = new BigInteger(digest);
        Log.d("HASH", "H = " + d.toString(16));
    } catch (NoSuchAlgorithmException e) {
        throw new UnsupportedOperationException(e);
    }
}

iOS:

void hashBigNum(unsigned char *c) {
    BIGNUM *n = BN_new();
    BN_hex2bn(&n, c);
    unsigned char   buff[ SHA256_DIGEST_LENGTH ];
    int             len  = BN_num_bytes(n);
    unsigned char * bin    = (unsigned char *) malloc( len );
    BN_bn2bin(n, bin);
    hash( SRP_SHA256, bin, len, buff ); 
    fprintf(stderr, "H: ");
    for (int z = 0; z < SHA256_DIGEST_LENGTH; z++)
        fprintf(stderr, "%2x", buff[z]);
    fprintf(stderr, "\n");
    free(bin);
}

Results:

Source String = "6F"
Android Hash = 65c74c15a686187bb6bbf9958f494fc6b80068034a659a9ad44991b08c58f2d2
iOS     Hash = 65c74c15a686187bb6bbf9958f494fc6b80068034a659a9ad44991b08c58f2d2

Source String = "FF"
Android Hash = 06eb7d6a69ee19e5fbdf749018d3d2abfa04bcbd1365db312eb86dc7169389b8
iOS     Hash = a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89
Peggiepeggir answered 18/8, 2016 at 12:50 Comment(4)
If you want to implement SRP, it is not enough to have the hash match between Android and iOS. The hash needs to match what the spec says they should be. I suggest using someone else's implementation and seeing what hashes they get.Bannock
@brianbeuning, we have implemented all of the protocol except this bit. Once I fixed the JAVA code for the leading 0s problem now it is working fine cross platform.Peggiepeggir
For example, OpenSSL supports SRP. If the way you compute SRP hashes does not match the way OpenSSL computes SRP hashes, your code will not be able to talk to OpenSSL.Bannock
Yes, we got this working with OpenSSL Hash.Peggiepeggir
C
4

The problem is in the JAVA code. new BigInteger(s, 16).toByteArray() is not safe for leading zeros. See poster comment at Convert a string representation of a hex dump to a byte array using Java?

The bit representation of FF with Android is 00000000 11111111 whereas in iOS is 11111111. The leading zeros is the reason because the SHA256 hashing is different.

Just change the Hex to byte converter using one method of the linked post to get the same byte array (without zeros). For example

public static byte[] hexStringToByteArray(String s) {
    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                             + Character.digit(s.charAt(i+1), 16));
    }
    return data;
}

void hashBigInteger(String s){
    try{
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        byte b[] = hexStringToByteArray(s);
        sha.update(b,0,b.length);
        byte digest[] = sha.digest();
        BigInteger d = new BigInteger(1,digest);

        System.out.println("H "+d.toString(16));
    }catch (NoSuchAlgorithmException e){
        throw new UnsupportedOperationException(e);
    }
}

To proper HEX printing, change also BigInteger d = new BigInteger(digest); with

BigInteger d = new BigInteger(1,digest); 
Crossover answered 21/8, 2016 at 10:20 Comment(1)
In other words, this is the reverse of what I did in your question about the two's complement. Convert the BigInteger to a byte array and remove the leading zero byte, and create a new BigInteger with the modified array. Java will interpret that as -1, but try it.Quietism
U
0

One way is to convert your big numbers to strings and get hash of them.

Ubiquitarian answered 18/8, 2016 at 13:10 Comment(4)
The BigInteger/BigNum are created from Strings only so would it make a difference if we convert them back to Strings? Also in iOS the Hash uses OpenSSL libraries which are low level C APIs, not sure how we can use a String for the Hash API.Peggiepeggir
BigInteger and BigNum may have different implementations, so their conversions to byte arrays may produce different results.Ubiquitarian
But the SRP protocol (en.wikipedia.org/wiki/Secure_Remote_Password_protocol) calculations need BigInteger/BigNum and hence I want to get the same Hash results. Its working for +ve numbers so want to know if one platform treats -ve numbers as signed and the other treats as +vePeggiepeggir
@Harish: yes, it seems so. See pedrofb's answer. On iOS the string is simply seen as one unsigned byte, while in Java it is seen as 255, which needs a leading 0 byte to keep it positive. The extra 0 is probably the difference.Quietism

© 2022 - 2024 — McMap. All rights reserved.