Search code examples
javaandroidiosaesuint

iOS and Android AES Encryption (No UINT in Java)


All,

I am new to encryption so I'm not sure all of the information I need to share to get help; but I'll edit this question as I learn more about how to ask this question well :)

I am performing AES encryption on both an iOS and an android app that communicate over bluetooth to a device. I am using AES CTR encryption and it is fully implemented and functional on iOS. The problem I'm running into is that when I convert items such as my IV to a byte array; java bytes are signed and swift bytes are unsigned so while I can encrypt and decrypt my string on Java; it is a different result than what I would see in iOS.

How are other people dealing with this unsigned int issue? I feel like there's got to be some straight-forward thing I'm doing wrong. I'm really not sure what code to post. For android I'm using hex string to byte conversion functions I found here on stack overflow and they are working correctly...they're just signed instead of unsigned so the values are different than the 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();

All input values match exactly until I call the function: hextStringtoByteArray. At this point, the iOS byte arrays are unsigned and the android byte arrays are signed.

Here is that function 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;
}

Sample IV Byte Array:

iOS vs 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 might notice a difference between the two printed arrays because java by default displays a byte as a signed value. But in reality those are actually equal. To make it more clear I'll add a little 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 wise), only printed diffently as they are interpreted as different values. If you want to make sure things are correct, I would suggest looking at a few numbers with a calculator on programming mode. Usually there is a way to set the byte/word length so you can play around with signed vs unsigned interpretation of the same Hexadecimal value (there should also be a bit-representation of the value).

    As an alternative I found a small website containing a signed vs unsigned type-bit/hex converter, which will do the trick as well. (make sure you select either char-type, otherwise the signed values will be incorrect)


    So in the IV-bytes part of the code there shouldn't be any problem. There might be one however when you create your String using only a byte-array as parameter. e.i:

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

    Since most likely the used Charset is not UTF-8. (you can determine that by calling Charset#defaultCharset, and check its value). The alternative would be:

    new String(decryptedData, StandardCharsets.UTF_8)
    

    or:

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