Эквивалент шифрования spongycastle для ios

Это поставило меня в тупик - следующий код использует шифрование/дешифрование SpongyCastle для Android - я пытаюсь добиться кросс-платформенного шифрования/дешифрования для iOS.

Следующий код (из Android) отлично работает: 128-битный CBC AES с PKCS7Padding, используя предоставленную соль и пароль, которые хранятся в базе данных mysql, пароль предоставляется конечным пользователем, следующий код адаптирован из этого ответ kelhoer.

Причина, по которой я использовал AES128bit, заключается в том, что AES256 недоступен в iOS 4+, он был представлен в iOS5+, и мне пришлось использовать openssl для создания производного ключа и вектора инициализации (iv), это было рискованно, когда я узнал, что Apple отклоняет приложения, которые статически связаны с библиотекой openssl.

Поскольку платформа основана на iOS 4.2+, прибегнув к объединение и статическая компоновка библиотеки openssl кажется излишней и предпочтительно использовать библиотеку CommonCryptor.

Вот версия Android с кодом Spongycastle:

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

Однако в iOS 4.2 (работа с XCode) я не могу понять, как сделать эквивалент,

Это то, что я пробовал в Objective C с целью расшифровки данных со стороны Android, хранящихся в базе данных mysql, чтобы проверить это:

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

Предоставили данные и пароль пользователя и получили код возврата от CCCrypt как -4304, что указывает на неудачное и плохое декодирование.

Я подумал, что, возможно, схема кодирования будет отбрасывать маршрут дешифрования CommonCryptor, следовательно, многословный способ преобразования в NSASCIIStringEncoding.

Соль хранится вместе с зашифрованными данными и имеет длину 32 байта.

Что я упускаю в этом плане, имея в виду, что слаб в криптографии.


person t0mm13b    schedule 05.05.2013    source источник
comment
Я думаю, вы напрашиваетесь на проблему, пытаясь использовать другую базу кода между iOS и Android, если вам нужна совместимость. Почему бы вам просто не найти реализацию AES на C/C++ и не скомпилировать ее в свою кодовую базу на обеих платформах?   -  person Abhi Beckert    schedule 13.05.2013


Ответы (2)


Я позволил себе закодировать прямой порт генератора PKCS12Parameters, который используется на стороне Android, суть этого заголовка выше.

Реализация также является прямой копией, как указано здесь, пароль преобразуется в эквивалент PKCS12 - unicode. , с обратным порядком байтов, с двумя дополнительными нулями в конце.

Генератор генерирует производный ключ и iv, выполняя количество итераций, в данном случае 1000, как и на стороне Android, используя дайджест SHA256, окончательный сгенерированный ключ и iv затем используются как параметры в CCCryptorCreate.

Использование следующего примера кода также не работает, он заканчивается на -4304 при вызове CCCryptorFinal

Фрагмент кода выглядит следующим образом:

#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);

Самое смешное, что расшифровка работает, окончательный вызов CCCryptorFinal возвращает 0, если я заменяю kCCOptionPKCS7Padding на 0x0000 в начале CCCryptorCreate, т.е. без заполнения. Увы, данные не такие, как я ожидал, все равно полностью зашифрованы, независимо от того, когда это «не работает».

Где-то он терпит неудачу, поэтому, если у кого-то есть лучшие идеи о том, как добиться эквивалента, я был бы рад услышать другие мнения.

Либо изменить механизм на стороне Android, чтобы сделать его «кроссплатформенным» совместимым с iPhone, либо искать альтернативное криптографическое решение, совместимое с обеих сторон за счет более слабой криптографии на обеих сторонах платформ. используется для переноса обмена данными.

Входные данные в том виде, в котором они были предоставлены:

  • Шифр в кодировке Base64, соль и шифр разделены знаком ':' tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
  • Предоставленный пароль: f00b4r
  • Исходная строка The quick brown fox jumped over the lazy dog and ran away
person t0mm13b    schedule 07.05.2013

Верно, мне пришлось отказаться от алгоритма шифрования на стороне Android, который представлял собой проблему, чтобы найти тот, который совместим с кросс-платформой.

Я много читал о RNCryptor Роба Нейпира и после поиска в Google эквивалента Android, в котором я нашел JNCryptor , я рискнул и использовал RNCryptor на стороне iOS.

Разветвил код JNCryptor на github, чтобы добавить возможность задавать пользовательские настройки и использовать SpongyCastle, для старых версий Android. С этого момента обе платформы могли взаимозаменяемо шифровать/дешифровать.

Причина, по которой я улучшил JNCryptor, заключалась в том, что количество итераций для функции PKDBF2 было слишком большим — 10 000 и было значением по умолчанию (поскольку код будет работать на старых телефонах — он застрял — отлично, если у вас двухъядерный/четырехъядерный процессор!), и необходимо было переопределить количество итераций, чтобы оно было более «терпимым» - 1000. Использование пользовательской настройки было возможно с RNCryptor.

Спасибо Робу Нейпиру и Дункану Джонсу за их работу!

person t0mm13b    schedule 14.05.2013
comment
Привет, я также хотел реализовать ту же функциональность, которая была разработана для платформы Android с помощью Spongycastle. Должен ли я использовать RNCryptor для этого? Заранее спасибо за помощь в этом. - person Heena Beriya; 25.03.2021