7

I've been at this for a few days now. My original (and eventual) goal was to use CommonCrypto on iOS to encrypt a password with a given IV and key, then successfully decrypt it using .NET. After tons of research and failures I've narrowed down my goal to simply producing the same encrypted bytes on iOS and .NET, then going from there.

I've created simple test projects in .NET (C#, framework 4.5) and iOS (8.1). Please note the following code is not intended to be secure, but rather winnow down the variables in the larger process. Also, iOS is the variable here. The final .NET encryption code will be deployed by a client so it's up to me to bring the iOS encryption in line. Unless this is confirmed impossible the .NET code will not be changed.

The relevant .NET encryption code:

    static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
    {
        byte[] encrypted;
        // Create an Aes object 
        // with the specified key and IV. 
        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Padding = PaddingMode.PKCS7;
            aesAlg.KeySize = 256;
            aesAlg.BlockSize = 128;

            // Create an encryptor to perform the stream transform.
            ICryptoTransform encryptor = aesAlg.CreateEncryptor(Key, IV);

            // Create the streams used for encryption. 
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }
        return encrypted;
    }

The relevant iOS encryption code:

+(NSData*)AES256EncryptData:(NSData *)data withKey:(NSData*)key iv:(NSData*)ivector
{
Byte keyPtr[kCCKeySizeAES256+1]; // Pointer with room for terminator (unused)
// Pad to the required size
bzero(keyPtr, sizeof(keyPtr));

// fetch key data
[key getBytes:keyPtr length:sizeof(keyPtr)];

// -- IV LOGIC
Byte ivPtr[16];
bzero(ivPtr, sizeof(ivPtr));
[ivector getBytes:ivPtr length:sizeof(ivPtr)];

// Data length
NSUInteger dataLength = data.length;

// See the doc: For block ciphers, the output size will always be less than or equal to the input size plus the size of one block.
// That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);

size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                      keyPtr, kCCKeySizeAES256,
                                      ivPtr,
                                      data.bytes, dataLength,
                                      buffer, bufferSize,
                                      &numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
    return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}

free(buffer);
return nil;
}

The relevant code for passing the pass, key, and IV in .NET and printing result:

byte[] c_IV = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
byte[] c_Key = { 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
String passPhrase = "X";

// Encrypt
byte[] encrypted = EncryptStringToBytes_Aes(passPhrase, c_Key, c_IV);
// Print result
for (int i = 0; i < encrypted.Count(); i++)
{
    Console.WriteLine("[{0}] {1}", i, encrypted[i]);
}

The relevant code for passing the parameters and printing the result in iOS:

Byte c_iv[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
Byte c_key[16] = { 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
NSString* passPhrase = @"X";
// Convert to data
NSData* ivData = [NSData dataWithBytes:c_iv length:sizeof(c_iv)];
NSData* keyData = [NSData dataWithBytes:c_key length:sizeof(c_key)];
// Convert string to encrypt to data
NSData* passData = [passPhrase dataUsingEncoding:NSUTF8StringEncoding];
NSData* encryptedData = [CryptoHelper AES256EncryptData:passData withKey:keyData iv:ivData];

long size = sizeof(Byte);
for (int i = 0; i < encryptedData.length / size; i++) {
    Byte val;
    NSRange range = NSMakeRange(i * size, size);
    [encryptedData getBytes:&val range:range];
    NSLog(@"[%i] %hhu", i, val);
}

Upon running the .NET code it prints out the following bytes after encryption:

[0] 194
[1] 154
[2] 141
[3] 238
[4] 77
[5] 109
[6] 33
[7] 94
[8] 158
[9] 5
[10] 7
[11] 187
[12] 193
[13] 165
[14] 70
[15] 5

Conversely, iOS prints the following after encryption:

[0] 77
[1] 213
[2] 61
[3] 190
[4] 197
[5] 191
[6] 55
[7] 230
[8] 150
[9] 144
[10] 5
[11] 253
[12] 253
[13] 158
[14] 34
[15] 138

I cannot for the life of me determine what is causing this difference. Some things I've already confirmed:

  1. Both iOS and .NET can successfully decrypt their encrypted data.

  2. The lines of code in the .NET project:

    aesAlg.Padding = PaddingMode.PKCS7;
    aesAlg.KeySize = 256;
    aesAlg.BlockSize = 128;

Do not affect the result. They can be commented and the output is the same. I assume this means they are the default valus. I've only left them in to make it obvious I'm matching iOS's encryption properties as closely as possible for this example.

  1. If I print out the bytes in the iOS NSData objects "ivData" and "keyData" it produces the same list of bytes that I created them with- so I don't think this is a C <-> ObjC bridging problem for the initial parameters.

  2. If I print out the bytes in the iOS variable "passData" it prints the same single byte as .NET (88). So I'm fairly certain they are starting the encryption with the exact same data.

Due to how concise the .NET code is I've run out of obvious avenues of experimentation. My only thought is that someone may be able to point out a problem in my "AES256EncryptData:withKey:iv:" method. That code has been modified from the ubiquitous iOS AES256 code floating around because the key we are provided is a byte array- not a string. I'm pretty studied at ObjC but not nearly as comfortable with the C nonsense- so it's certainly possible I've fumbled the required modifications.

All help or suggestions would be greatly appreciated.

Sean G
  • 479
  • 4
  • 9
  • Just for fun, what happens when you declare `val` as `Byte val[1];`? – Ian MacDonald Jan 13 '15 at 21:14
  • @Ian I assume you mean what kind of output does it produce? Obviously if I do a log of val[0] the results are the same. To print the array itself I had to use the "%s" formatter. It results in: M, ’. =, æ, ≈, ø, 7, Ê, ñ, ê , ˝, ˝, û, ”, ä. – Sean G Jan 13 '15 at 21:35
  • Note: They only produce different output for different input. Check all the inputs, modes, key lengths, etc. – zaph Jan 13 '15 at 22:11
  • Hex log all data just prior to the encryption call and just after it. For a test use data that is exactly one block in length. The logging will cover the case of string encodings. BTW, hex dumps rule because among other things many encodings have patterns that clearly provide information about the data such as the encoding. – zaph Jan 13 '15 at 23:41
  • @Zaph That's definitely my most basic premise. Having checked all the relevant parameters I have appealed to SO. – Sean G Jan 13 '15 at 23:46
  • 1
    I notice you are using AES256 but have a 128-bit key! 16-bytes x 8-bits. You can not count on various functions to pad a key the same, that is undefined. Supply all inputs in the exact length required. – zaph Jan 14 '15 at 00:08
  • @Zaph You win! If the iOS keys and .NET keys are padded with 0s to 32 bytes they absolutely match! If you go ahead and post as an answer I'll accept it. – Sean G Jan 14 '15 at 00:45

2 Answers2

3

I notice you are using AES256 but have a 128-bit key! 16-bytes x 8-bits. You can not count on various functions to pad a key the same, that is undefined.

zaph
  • 111,848
  • 21
  • 189
  • 228
  • Can you please explain this little more? I am having similar kind of issue. I am trying to use CommonCrypto library in Swift but this is not generating the same output that it generates in Objective-C. This encrypted output is further decrypted on .Net server. – Nah Jul 06 '18 at 05:00
  • Create a [mcve] question with both the ObjC and Swift code, example data and results. – zaph Jul 06 '18 at 11:24
0

You're likely dealing with an issue of string encoding. In your iOS code I see that you are passing the string as UTF-8, which would result in a one-byte string of "X". .NET by default uses UTF-16, which means you have a two-byte string of "X".

You can use How to convert a string to UTF8? to convert your string to a UTF-8 byte array in .NET. You can try writing out the byte array of the plain-text string in both cases to determine that you are in fact passing the same bytes.

Community
  • 1
  • 1
NextInLine
  • 2,126
  • 14
  • 23
  • So you're saying the .NET StreamWriter object that is passed the "plainText" parameter is assuming a UTF-16 encoding, and thereby producing different data? If that's the case I should be able to simply use some form of the UTF-16 encoding that iOS provides- correct? I've tried all the NSUTF16* encoding options provided and encoding my passPhrase using any of those options produces a different result than .NET. **However**, NSUTF16LittleEndianStringEncoding does produce data with the same bytes ([88, 0]) as .NET does if the string isn't converted to UTF-8 first. That seems relevant somehow. – Sean G Jan 14 '15 at 00:18
  • If you just look at the first few bytes in hex it is relatively easy to determine what the encoding is. – zaph Jan 14 '15 at 00:44
  • @NextInLine Note that internally `NSString` uses UTF-16 encoding. – zaph Jan 14 '15 at 00:45
  • UTF-8 and ASCII, with the padding suggested by the solution, both produce the same result. Any NSUTF16** encoding does **not** match the result of the .NET code I posted after applying the padding fix. – Sean G Jan 14 '15 at 00:56