0

One of my app needs to download a database with the content encrypted in AES 256. So I've used on server side phpAES to encode the strings in AES CBC with an IV.

On the iOS side I'm using FBEncryptor to decrypt the string.

This is the code on the server side:

$aes = new AES($key, "CBC", $IV);
$crypt = $aes->encrypt($string);
$b64_crypt = base64_encode($crypt);

On the iOS side I'm doing this:

NSString* decrypt = [FBEncryptorAES decryptBase64String:b64_crypt keyString:key iv:iv];

Actually everythings works fine on iOS 8. The problem is on iOS 7 where the decoded string is truncated at random length.

Thoughts?

zaph
  • 111,848
  • 21
  • 189
  • 228
Luca
  • 81
  • 1
  • 7

2 Answers2

2

Don't use phpAES. You're shooting yourself in the foot with an enormous cannon.

From their page:

The free version only supports ECB mode, and is useful for encrypting/decrypting credit card numbers.

This is incredibly wrong and misleading. ECB mode is not suitable for any purpose except as a building block for other modes of operation. You want an AEAD mode; or, failing that, CBC or CTR with HMAC-SHA2 and a CSPRNG-derived IV/nonce. Using unauthenticated encryption is a very bad idea.

For interoperability with iOS, you should use libsodium.

If you cannot use libsodium, your best bet is OpenSSL and explicitly not mcrypt, and a compatible interface on the iOS side.

All currently supported versions (5.4+) of PHP expose openssl_encrypt() and openssl_decrypt() which allow fast and secure AES-CBC and AES-CTR encryption. However, you should consider using a library that implements these functions for you instead of writing them yourself.

Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
  • The paid version does support CBC mode at $9.95. But I would not trust it. It states that it is FIPS 197 Compliant, what ever that means, it is not FIPS certified which is the gold standard. There may be bugs and/or a backdoor present, the NSA, etc put things like this out with weaknesses. At least mcrypt is reasonably vetted even if it was written by bozos who thought null padding in place of PKCS#7 padding was a good idea. – zaph Jun 08 '15 at 20:42
  • The problem I see with libraries such as php-encryption is interoperability. They rarely supply a through description of how the underlying primitives and options are used. As for counter mode, that is essentially asking for bugs, all to often the nonce is not handled correctly. Even Microsoft screwed that up on at least two occasion in major products. – zaph Jun 08 '15 at 20:47
  • php-encryption is straightforward AES-CBC + HMAC-SHA256 facilitated through `openssl_encrypt()` – Scott Arciszewski Jun 09 '15 at 00:22
  • That is rather simplistic and insufficient description for providing a compatible solution from cryptographic primitives, exactly my point. OpenSSL is not supplied by Apple due to version incompatibilities. – zaph Jun 09 '15 at 11:16
  • Virtual machines help on Mac servers. Why would you ever think about running PHP on a client? – Scott Arciszewski Jun 09 '15 at 15:39
  • Sodium is a great solution and supports several languages/platforms. Another solution on both sides is [RNCryptor](https://github.com/RNCryptor) which supports PHP, iOS and several additional language combinations. – zaph Jun 09 '15 at 15:52
  • I have two problems with RNCryptor: It depends on mcrypt, and it doesn't authenticate ciphertexts. :( – Scott Arciszewski Jun 09 '15 at 19:54
  • Not all encryption requires authentication. True mcrypt is a POS but it is true to php. ;-) RNCryptor adds PKCS#7 padding for CBC mode to mcrypt, uses PBKDF2 for key derivation and has an HMAC for authentication. See: [RNCryptor-Spec](https://github.com/RNCryptor/RNCryptor-Spec/blob/master/RNCryptor-Spec-v3.md) – zaph Jun 09 '15 at 20:12
1

The truncation could be the result of incompatible padding.

phpAES uses non-standard null padding similar to mcrypt, this is unfortunate since the standard for padding is PKCS#7. It is unfortunate that one has to read the code to find that out. It is important to supply a 256-bit (32-byte) key since that sets the key size for the algorithm.

FBEncryptor only supports PKCS#7 padding.

So, these two methods are incompatible.

One solution is to add PKCS#7 padding to the string in php prior to calling phpAES which will not then add the null padding. Then FBEncryptor will be compatible with the encrypted data.

PKCS#7 padding always adds padding. The padding is a series by bytes with the value of the number of padding bytes added. The length of the padding is the block_size - (length(data) % block_size.

For AES where the block is is 16-bytes (and hoping the php is valid, it had been a while):

$pad_count = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($pad_count), $pad_count);

Please add to the question working example keys, iv clear data and encrypted data as hex dumps.

zaph
  • 111,848
  • 21
  • 189
  • 228
  • Thank you Zaph. I encountered this problem when I started to encrypt in AES on PHP and decrypt on iOS. But magically I solved by using phpAES and FBEncryptor with AES 256 bit CBC. On iOS 8 I can read the decrypted string as I want, but I cannot do it on iOS 7. So I think that maybe could be a difference with the two OS version and not just a padding. What do you think about? – Luca Jun 06 '15 at 12:54
  • I doubt that there is a difference between iOS7 and iOS8 for correct inputs, that would affect many developers and apps and I have not seen any reports of that. The two methods are not compatible in the way they use padding. So the input to FBEncryptor is invalid and the results are not specified. phpAES pads the input to a block size with nulls and does not ever add an extra block. FBEncryptor looks at the last byte of the last block and truncates the decrypted data by the amount, it may do some validity error checking but can not fix an error. Sometimes you get lucky. – zaph Jun 06 '15 at 13:08
  • Encrypting using null padding and decrypting using pkcs#7 should normally give you extra nulls which you may not see. You may see unexpected truncation in the event that the size of your data is a multiple of the block size, since there will be no extra null padding. Do some more tests with different lengths of data. – Phil Jun 08 '15 at 16:42