Spongycastle encryption equivalent for ios

This put me on my guard - the following code uses SpongyCastle encryption / decryption for Android - I'm trying to achieve cross-platform encryption / decryption for iOS.

The following code (from Android) works with AES 128bit CBC with PKCS7Padding, using the supplied salt and password, which salt is stored in the mysql database, the password is by the end user, the following code is adapted from this kelhoer answer.

The reason I used AES128bit is because AES256 is not available in iOS 4+, it was introduced in iOS5 +, and in order to generate a derivative key and initialization vector (iv), you had to dip your sock into using openssl as Apple found out that Apple rejects applications statically linked to the openssl library.

Since the platform is based on iOS 4.2+, resorting to linking and static linking the openssl library , it seems preferable to use the CommonCryptor library.

Here is the Android version with the 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, in iOS 4.2 (working with Xcode), I cannot figure out how to make an equivalent,

This is what I tried in Objective-C, with the goal of decrypting the data from the Android side stored in the mysql database, in order to verify this:

 +(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; } 

We CCCrypt user data and password and received a return code from CCCrypt as -4304 , which indicates unsuccessful and bad decoding.

I thought that perhaps the encoding scheme would discard the routing of CommonCryptor decryption, hence the long way to convert to NSASCIIStringEncoding .

The salt is stored along with encryption data and has a length of 32 bytes.

What I miss in this regard, bearing in mind the weakness in cryptography.

+4
source share
2 answers

That's right, I had to abandon the encryption algorithm on the Android side, which set myself the task of finding cross-platform compatibility.

I read a lot about Rob Napier RNCryptor and after searching Google for the Android equivalent in which I found JNCryptor , I took a decisive step and used RNCryptor on the iOS side.

Compiled JNCryptor code on github to add improvements to the ability to specify user preferences and use SpongyCastle for older versions of Android. From there, both platforms were able to encrypt / decrypt interchangeably.

The reason I increased JNCryptor was the iteration counter for the PKDBF2 function, it was too high - 10,000 and was the default value (since the code will work on older phones - it captured - great if you have a dual / quad core!) , and it is necessary to redefine the iteration counter more "tolerantly" - 1000. Using custom settings is possible using RNCryptor.

Thanks to Rob Napier and Duncan Jones for their work!

0
source

I took the liberty of coding the direct port of the PKCS12Parameters generator used on the Android side, the essence of this header above,

The implementation is also a direct copy, since the password found here is converted to the PKCS12 equivalent - unicode, big-endian, with two additional zeros being completed to the end.

The generator generates the derivative key and iv by performing a number of iterations, in this case 1000, as on the Android side, using the SHA256 Digest, the final generated key and iv are then used as parameters for CCCryptorCreate .

Using the following code example also does not work, it ends with -4304 when calling CCCryptorFinal

Code excerpt looks like this:

 #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 is that decryption works, the final call to CCCryptorFinal returns 0 if I substitute kCCOptionPKCS7Padding for 0x0000 at the beginning of CCCryptorCreate , i.e. without filling. Alas, the data is not what I expected, still completely scrambled, no matter when it “doesn't work”.

He fails somewhere, so if anyone has any better ideas on how to achieve the equivalent, I would be glad to hear other opinions.

Either this will change the mechanism on the Android side to make it cross-platform compatible with iPhone, or look for an alternative cryptographic solution for compatibility at both ends due to weaker cryptography on both sides of the platforms used to create exchangeable portable data.

Input data included:

  • Base64 encoded salt and encrypted encryption separated by the ':' tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
  • Password f00b4r
  • Original string The quick brown fox jumped over the lazy dog and ran away
+4
source

Source: https://habr.com/ru/post/1479201/


All Articles