Thursday, May 24, 2018

Share encrypted data between ios and android clients

It is a common use case to use password to encrypt sensitive data in mobile applications. The basic logic is first deriving an encryption key based on password, and then use the password to encrypt the sensitive data.

The question is when using the same algorithm and password on both ios and android client,
1. Would both clients generate the same encryption key?
2. If so, could data encrypted from one client be decrypted by another client?

Question 1: Encryption key generation

For this testing, key generation is implemented using PBKDF2.

For iOS client (iOS 11.3 on iphone 7) , CCKeyDerivationPBKDF method can be used for this purpose. For testing purpose, the password is hardcoded as "password", and the salt is hardcoded as "salt", and the hash algorithm is KCCPRFHmacAlgSHA512,  the round is set to 100. Note the derived keysize should match the selected algorithm, there is no point to use KCCPRFHmacAlgSHA512 to generate an 32 bytes key (256 bit), as the total result hash is limited to 32 byte instead of 64 byte. Actually in iOS, if the keysize is smaller than the algorithm generated keysize, the generated key will be truncated to the specified keysize.

        var password = "password"
        var salt = "salt"
        var derivedKey : NSMutableData = NSMutableData(length: kCCKeySizeAES256)!
        var passwordData = NSString(string: password).utf8String
        var passwordDataSize = password.utf8.count
        var saltData = NSString(string: salt).utf8String
        var saltDataSize = salt.utf8.count
        var saltDataPointer = UnsafeRawPointer(saltData!).bindMemory(to:UInt8.self, capacity:saltDataSize)

        CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordData,
                             passwordDataSize, saltDataPointer, saltDataSize,
                             CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
                             uint(100),
                             UnsafeMutablePointer<UInt8>         (derivedKey.mutableBytes.assumingMemoryBound(to:UInt8.self)),
                             derivedKey.length)

For Android client, to match the iOS side configuration, SecretKeyFactory is used with the algorithm of PBKDF2withHmacSHA512. Note this algorithm is only supported on Android API 26 (Android 8.0) and above. The testing is done on Samsung S9 device.

SecretKeyFactory provider = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
String password = "password";
String salt = "salt";
char[] passwordData = password.toCharArray();
byte[] saltData = salt.getBytes("UTF8");
KeySpec keySpec = new PBEKeySpec(passwordData, saltData, 100, 256);
SecretKey key = provider.generateSecret(keySpec);
byte[] keydata = key.getEncoded();

The test shows both ios and android client generate the same encryption key as showing below in hex format, so this confirms the ios and android clients can derive the same encryption key using the same password and algorithm.

"07e6997180cf7f12904f04100d405d34888fdf62af6d506a0ecc23b196fe99d8"


Question 2: Data encryption

Now that we know that the same key can be generated by ios and android client based on the same password, the second steps is checking whether data encrypted by one client can be decrypted by another client using the same encryption key.

The IV is set to a 16 byte array of 0.  The data is a simple string of "this is a testing string".

On ios the encrypt method is using CCCrypte

        var outLength : size_t = 0;
        let cipherData : NSMutableData? = NSMutableData(length: dataToEncrypt.count + kCCBlockSizeAES128);
        let ivb : [UInt8] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
        let iv = NSData(bytes: ivb, length: 16)
        let result = CCCrypt(            UInt32(kCCEncrypt), // operation
            UInt32(kCCAlgorithmAES128), // algorithm
            UInt32(kCCOptionPKCS7Padding), // options
            (encryptionKey as NSData).bytes, // key
            encryptionKey.count, // keylength
            iv.bytes, // iv
            (dataToEncrypt as NSData).bytes, // dataIn
            dataToEncrypt.count, // dataInLength,
            UnsafeMutablePointer<UInt8>(cipherData!.mutableBytes.assumingMemoryBound(to:UInt8.self)), // dataOut
            cipherData!.length, // dataOutAvailable
            &outLength); // dataOutMoved


On Android client, Cipher is used to encrypt data as below

byte[] dataByteToEncrypt = dataToEncrypt.getBytes("UTF8");
Cipher dataCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
dataCipher.init(Cipher.ENCRYPT_MODE, key, iv );
byte[] encrypedData = dataCipher.doFinal(dataByteToEncrypt);

On both client platform, the encryption generates the same output byte array of
"92a78f657da19a444e28c83f604a63401dc9a81300dcf4b2707fe66a9d62f158"ta 

Conclusion:
The testing result indicates when the same algorithm, salt, iv and password are used, the data encrypted in one platform can be decrypted in another platform, so there is no need to use external third party library to handle data encryption/decryption when the encrypted data need to be transferred between android and ios clients.


1 comment: