4

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

Eric
  • 1,392
  • 17
  • 37
  • 1
    Signed vs unsigned bytes shouldn't make a difference when encoding/decoding a String. The used Charset may however (the default Charset in java may differ depending on the OS!). So in order to provide the answer as to how this can be solved you either need to explain how you are doing things (or better, show us some code). – n247s Jul 20 '18 at 16:15
  • I changed my implementation to use bigInteger directly and get the same results as suing my hexStringToByteArray function; the byte arrays have the same values; just signed instead of unsigned. – Eric Jul 20 '18 at 17:46

1 Answers1

3

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");
n247s
  • 1,898
  • 1
  • 12
  • 30
  • 1
    Just wanted to add the comment that I had my padding set incorrectly in android; accepting that the Unsigned byte vs signed byte thing made no difference put me on the right path. – Eric Jul 20 '18 at 21:51