2

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.

dangwu
  • 95
  • 6

2 Answers2

2

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");
Community
  • 1
  • 1
Mattias Buelens
  • 19,609
  • 4
  • 45
  • 51
0

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;
}
TeeTracker
  • 7,064
  • 8
  • 40
  • 46