Question

I am porting part of an iOS app to Android, and I'm having trouble porting the following signature generating code in iOS to Android. The iOS code is:

+ (NSString *)hashedBase64ValueOfData:(NSString *) data WithSecretKey:(NSString*)secret {
    // ascii convirsion
    const char *cKey  = [secret cStringUsingEncoding:NSASCIIStringEncoding];
    const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];

    // HMAC Data structure initializtion
    unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

    // Gerating hased value
    NSData *da =  [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];

    return [da base64EncodedString];// conversion to base64 string & returns
}

The Android Java code I have written and tried is:

private static String hashedBase64ValueOfDataWithSecretKey(String data, String secret) {
    try {
        SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);
        Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(data.getBytes());
        return Base64.encodeToString(rawHmac, 0);

    } catch (Exception e) {
        e.printStackTrace();
    }

    return null;
}

Upon testing, the Android function is not outputting the same thing as the iOS function (given the same input), and I'm not sure why.

Was it helpful?

Solution

Not an expert at this, but NSASCIIStringEncoding seems to imply that you want data and secret interpreted as ASCII, whereas String.getBytes() uses the default character set by default (i.e. UTF-8).

You probably need to use a different charset:

data.getBytes(StandardCharsets.US_ASCII);
secret.getBytes(StandardCharsets.US_ASCII);

For Java pre-1.7, you'll need to use this and catch the UnsupportedEncodingException:

data.getBytes("US-ASCII");
secret.getBytes("US-ASCII");

OTHER TIPS

You might use extras org.apache.commons.codec.binary.Base64. Google it and find it, then you can fellow the codes below. I think the hashed value will be generated by "private key" and appended behind a "public key" being sent to server with a "http-head" together. If no, you can just remove them. Anyway the codes might give you some suggestions. :)

private String getAppendedHeader(String str) {
    try {
        String hash = getHash(str);

        String signature = new String(Base64.encodeBase64(hash.getBytes()));
        StringBuilder sb = new StringBuilder();
        sb.append(PUBLIC_KEY).append(' ').append(signature);
        return sb.toString();
    } catch (NoSuchAlgorithmException _e) {
        LL.e("Get mac error: " + _e.getMessage());
        return null;
    } catch (InvalidKeyException _e) {
        LL.e("Init mac error: " + _e.getMessage());
        return null;
    }
}


private String getHash(String str) throws NoSuchAlgorithmException, InvalidKeyException {
    Mac mac = Mac.getInstance("HmacSHA256");
    SecretKeySpec secret = new SecretKeySpec(PRIVATE_KEY.getBytes(), "HmacSHA256");
    mac.init(secret);
    byte[] digest = mac.doFinal(str.getBytes());
    BigInteger hash = new BigInteger(1, digest);
    String hmac = hash.toString(16);
    if (hmac.length() % 2 != 0) {
        hmac = "0" + hmac;
    }
    return hmac;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top