Java – iOS and Android AES encryption (no UINT in Java)

iOS and Android AES encryption (no UINT in Java)… here is a solution to the problem.

iOS and Android AES encryption (no UINT in Java)

All,

I’m new to the crypto space, so I’m not sure what information I need to share to get help; But I’ll edit this issue as I learn more about how to present this question well 🙂

I’m performing AES encryption on iOS and Android apps that communicate with my device via Bluetooth. I’m using AES CTR encryption and it’s fully implemented and working on iOS. The problem I’m having is when I convert items like IV to byte arrays; Java bytes are signed, while swift bytes are unsigned, so I can encrypt and decrypt my strings on Java; This is different from the results I see in iOS.

How do others handle this unsigned int issue? I feel like I’m doing something straightforward. I’m not really sure what code to release. For android I’m using hex string to byte conversion functions, I found them here on stack overflow and they work fine… They are just signed and not unsigned, so the values are different from unsigned byte arrays in iOS.

iOS implementation:

let aesPrivateKey = "********************************"

print("MacAddress:-> \(macAddress)")

var index = 0

let aesPrivateKeyStartIndex = aesPrivateKey.startIndex
let macAddressStartIndex = macAddress.startIndex

Perform an XOR to get the device key
var deviceKeyArray: Array<Character> = Array(repeating: "?", count: 32)
for _ in macAddress {
    let nextPrivateKeyIndex = aesPrivateKey.index(aesPrivateKeyStartIndex, offsetBy: index)
    let nextMacAddressIndex = macAddress.index(macAddressStartIndex, offsetBy: index)

let nextPrivateKeyString = String(aesPrivateKey[nextPrivateKeyIndex])
    let nextMacAddressString = String(macAddress[nextMacAddressIndex])

let nextPrivateKeyByte = Int(nextPrivateKeyString, radix: 16)
    let nextMacAddressByte = Int(nextMacAddressString, radix: 16)

let nextCombinedByte = nextPrivateKeyByte! ^ nextMacAddressByte!

let nextCombinedString = nextCombinedByte.hexString

deviceKeyArray[index] = nextCombinedString[nextCombinedString.index(nextCombinedString.startIndex, offsetBy: 1)]

index+=1
}
while(index < 32) {

let nextPrivateKeyIndex = aesPrivateKey.index(aesPrivateKeyStartIndex, offsetBy: index)
    deviceKeyArray[index] = aesPrivateKey[nextPrivateKeyIndex]
    index += 1
}

Convert the device key to a byte array
let deviceKey = "0x" + String(deviceKeyArray)
let deviceKeyByte = Array<UInt8>(hex: deviceKey)

Convert the password to a byte array
let passwordByte : Array<UInt8> = password.bytes

Convert the initialization vector to a byte array
let aesIVHex = "0x" + AESIV
let aesIVByte = Array<UInt8>(hex: aesIVHex)

Encrypt the password
var encrypted = [Unicode.UTF8.CodeUnit]()
do{
    encrypted = try AES(key: deviceKeyByte, blockMode: CTR(iv: aesIVByte)).encrypt(passwordByte)
}
catch{
    print(error)
}

print("The Encrypted Password Data: \(encrypted)")

let encryptedData = encrypted.toHexString()

Write password to bluetooth and check result
UserDefaultUtils.setObject(encryptedData as AnyObject, key: userDefaults.password)
DeviceLockManager.shared().isEncrypted = false.
DeviceLockManager.share().setVerifyPasswordForDevice(isGunboxDevice:true)

Android implementation:

System.out.println("ble_ Password:"+str_password+"\nble_ AesKey:"+aesDeviceKey+"\nble_ AesIV:"+aesIV);

byte[] encryptedData = encrypt(
        str_password.getBytes(),
        Utility.getInstance().hexStringToByteArray(aesDeviceKey),
        Utility.getInstance().hexStringToByteArray(aesIV));

String encryptedPassword = Utility.getInstance().bytesToHexString(encryptedData);
System.out.println("ble_ AES Encrypted password " + encryptedPassword);
byte[] decryptedData = decrypt(encryptedData, aesDeviceKey.getBytes(), aesIV.getBytes());
System.out.println("ble_ Cipher Decrypt:"+new String(decryptedData));

Write password to bluetooth and check result
deviceManager.writePassword(encryptedPassword);
Utility.getInstance().sleep(100);
deviceManager.readPasswordResult();

Before calling the function hextStringtoByteArray, all input values match exactly. At this point, the iOS byte array is unsigned and the android byte array is signed.

This is a feature for reference:

public static byte[] hexStringToByteArray(String s){
    byte[] b = new byte[s.length() / 2];
    for (int i = 0; i < b.length; i++) {
        int index = i * 2;
        int v = Integer.parseInt(s.substring(index, index + 2), 16);
        b[i] = (byte) v;
    }
    return b;
}

Example IV byte array:

iOS and Android:

43、34、95、101、57、150、75、100、250、178、194、70、253、236、92、70

43、34、95、101、57、-106、75、100、-6、-78、-62、70、-3、-20、92、70

Solution

You may notice the difference between the two printed arrays because Java displays bytes as signed values by default. But in fact they are practically equal. To make it clearer, I’ll add a small table with the last 5 values of the example IV array you provided.

|----------------------------------------|
| hex      |  46 |  FD |  EC |  5C |  46 |
|----------------------------------------|
| unsigned |  70 | 253 | 236 |  92 |  70 |
|----------------------------------------|
| signed   |  70 | -3  | -20 |  92 |  70 |
|----------------------------------------|

So they are

actually the same (bit way), only printed differently because they are interpreted as different values. If you want to make sure everything is correct, I recommend that you use the calculator to look at some numbers in programming mode. There is usually a way to set the length of bytes/words so that you can have both signed and unsigned interpretations of the same hexadecimal value (there should also be bit representations of the values).

As an alternative, I found a small website contains a signed vs. unsigned type bit/hex converter, which also solves the problem. (Make sure you select a character type, otherwise the value of the signature will be incorrect).


So there shouldn’t be any problems with the IV-byte part of the code. However, when you create a String with only a byte array as a parameter, there may be one. Namely:

byte[] decryptedData = decrypt(encryptedData, aesDeviceKey.getBytes(), aesIV.getBytes());
System.out.println("ble_ Cipher Decrypt:" + new String(decryptedData));

Because it is likely that the character set used is not UTF-8. (You can determine this by calling Charset#defaultCharset and checking its value.) The alternative is:

new String(decryptedData, StandardCharsets.UTF_8)

Or:

new String(decryptedData, "UTF-8");

Related Problems and Solutions