This has stumped me - the following code uses SpongyCastle's encryption/decryption for Android - I am trying to achieve cross-platform encryption/decryption for iOS.
The following code (from Android) works a treat, AES 128bit CBC with PKCS7Padding, using a supplied salt and password, which the salt is stored on the mysql database, the password is by the end-user, the following code is adapted from this answer by kelhoer.
The reason I used AES128bit is that AES256 is not available in iOS 4+, it was introduced in iOS5+, and having to dip the toe into using openssl
to generate a derived key and initialization vector (iv), it was dicey as learnt that Apple rejects apps that are statically linked with openssl library.
Since the platform is based on iOS 4.2+, having resorted to bundling and statically linking the openssl library seems rather, over-kill and would preferably use the CommonCryptor library.
Here's the Android version with Spongycastle code in place:
private static void encrypt(InputStream fin,
OutputStream fout,
String password,
byte[] bSalt) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
new SHA256Digest()
);
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes =
PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams =
(ParametersWithIV) pGen.generateDerivedParameters(128, 128);
aesCBC.init(true, aesCBCParams);
PaddedBufferedBlockCipher aesCipher =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(true, aesCBCParams);
byte[] buf = new byte[BUF_SIZE];
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[
aesCipher.getUpdateOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void decrypt(InputStream fin,
OutputStream fout,
String password,
byte[] bSalt) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
new SHA256Digest()
);
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes =
PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams =
(ParametersWithIV) pGen.generateDerivedParameters(128, 128);
aesCBC.init(false, aesCBCParams);
PaddedBufferedBlockCipher aesCipher =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(false, aesCBCParams);
byte[] buf = new byte[BUF_SIZE];
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[
aesCipher.getUpdateOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
// int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[
aesCipher.getOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
However under iOS 4.2 (Working with XCode) I cannot figure out how to do the equivalent,
This is what I have tried under Objective C, with the goal of decrypting data from the Android side, stored in mysql database, to test this out:
+(NSData*) decrypt:(NSData*)cipherData
userPassword:(NSString*)argPassword
genSalt:(NSData*)argPtrSalt{
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
uint8_t *ptrPlainBuf = malloc(szPlainBufLen);
//
const unsigned char *ptrPasswd =
(const unsigned char*)[argPassword
cStringUsingEncoding:NSASCIIStringEncoding];
int ptrPasswdLen = strlen(ptrPasswd);
//
NSString *ptrSaltStr = [[NSString alloc]
initWithData:argPtrSalt
encoding:NSASCIIStringEncoding];
const unsigned char *ptrSalt =
(const unsigned char *)[ptrSaltStr UTF8String];
NSString *ptrCipherStr =
[[NSString alloc]initWithData:cipherData
encoding:NSASCIIStringEncoding];
unsigned char *ptrCipher = (unsigned char *)[ptrCipherStr UTF8String];
unsigned char key[kCCKeySizeAES128];
unsigned char iv[kCCKeySizeAES128];
//
//int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
//const unsigned char *salt, const unsigned char *data,
//int datal, int count, unsigned char *key,unsigned char *iv);
int i = EVP_BytesToKey(EVP_aes_128_cbc(),
EVP_sha256(),
ptrSalt,
ptrPasswd,
ptrPasswdLen,
PBKDF2_ITERATIONS,
key,
iv);
NSAssert(i == kCCKeySizeAES128,
@"Unable to generate key for AES");
//
size_t cipherLen = [cipherData length];
size_t outlength = 0;
//
CCCryptorStatus resultCCStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key,
kCCBlockSizeAES128,
iv,
ptrCipher,
cipherLen,
ptrPlainBuf,
szPlainBufLen,
&outlength);
NSAssert(resultCCStatus == kCCSuccess,
@"Unable to perform PBE AES128bit decryption: %d", errno);
NSData *ns_dta_PlainData = nil;
if (resultCCStatus == kCCSuccess){
ns_dta_PlainData =
[NSData dataWithBytesNoCopy:ptrPlainBuf length:outlength];
}else{
return nil;
}
return ns_dta_PlainData;
}
Have supplied the data and user's password and get a return code from CCCrypt
as -4304
which indicates not successful and bad decoding.
I have thought that perhaps the encoding scheme would be throwing off the CommonCryptor's decryption routing hence the long-winded way of converting to NSASCIIStringEncoding
.
The Salt is stored along with the cipher data, and is 32bytes in length.
What am I missing in this regard, bearing in mind, am weak on cryptography.
I have taken the liberty in coding a direct port of the PKCS12Parameters generator as used on the Android side, the gist for this header is above.
The implementation is direct copy also, as found here, the password, is converted to PKCS12 equivalent - unicode, big-endian, with two extra zeros padded on to the end.
The Generator generates the derived key and iv via performing the number of iterations, in this case, 1000, as is on the Android side, using the SHA256 Digest, the final generated key and iv is then used as the parameters to CCCryptorCreate
.
Using the following code sample is not working either, it ends with -4304
upon a call to CCCryptorFinal
The code excerpt is as shown:
#define ITERATIONS 1000
PKCS12ParametersGenerator *pGen = [[PKCS12ParametersGenerator alloc]
init:argPassword
saltedHash:argPtrSalt
iterCount:ITERATIONS
keySize:128
initVectSize:128];
//
[pGen generateDerivedParameters];
//
CCCryptorRef decryptor = NULL;
// Create and Initialize the crypto reference.
CCCryptorStatus ccStatus = CCCryptorCreate(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
pGen.derivedKey.bytes,
kCCKeySizeAES128,
pGen.derivedIV.bytes,
&decryptor
);
NSAssert(ccStatus == kCCSuccess,
@"Unable to initialise decryptor!");
//
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
// Calculate byte block alignment for all calls through to and including final.
size_t szPtrPlainBufSize = CCCryptorGetOutputLength(decryptor, szPlainBufLen, true);
uint8_t *ptrPlainBuf = calloc(szPtrPlainBufSize, sizeof(uint8_t));
//
// Set up initial size.
size_t remainingBytes = szPtrPlainBufSize;
uint8_t *ptr = ptrPlainBuf;
size_t movedBytes = 0;
size_t totalBytesWritten = 0;
// Actually perform the encryption or decryption.
ccStatus = CCCryptorUpdate(decryptor,
(const void *) cipherData.bytes,
szPtrPlainBufSize,
ptr,
remainingBytes,
&movedBytes
);
NSAssert(ccStatus == kCCSuccess,
@"Unable to update decryptor! Error: %d", ccStatus);
ptr += movedBytes;
remainingBytes -= movedBytes;
totalBytesWritten += movedBytes;
//
// Finalize everything to the output buffer.
CCCryptorStatus resultCCStatus = CCCryptorFinal(decryptor,
ptr,
remainingBytes,
&movedBytes
);
totalBytesWritten += movedBytes;
if(decryptor) {
(void) CCCryptorRelease(decryptor);
decryptor = NULL;
}
NSAssert(resultCCStatus == kCCSuccess,
@"Unable to perform PBE AES128bit decryption: %d", resultCCStatus);
The funny thing, the decryption works, the final call to CCCryptorFinal
returns 0
if I substitute the kCCOptionPKCS7Padding
for 0x0000
at the start of CCCryptorCreate
, i.e no padding. Alas, the data is not what I expected, still totally scrambled regardless when that "does not work".
It is failing somewhere, so if anyone has any better ideas on how to achieve the equivalent, I would be delighted to hear other opinions.
Either its change the mechanism on the Android side to make it "cross-platform" compatible with iPhone or seek an alternative cryptographic solution to be compatible on both ends at the expense of weaker cryptography on both sides of platforms used for making data interchange portable.
The input data as supplied:
tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
f00b4r
The quick brown fox jumped over the lazy dog and ran away
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With