5

これは私を困惑させました-次のコードはAndroid用のSpongyCastleの暗号化/復号化を使用しています-私はiOS用のクロスプラットフォーム暗号化/復号化を達成しようとしています.

次のコード (Android から) は、提供されたソルトとパスワードを使用して、PKCS7Padding を使用した AES 128 ビット CBC を処理します。ソルトは mysql データベースに保存されます。パスワードはエンドユーザーによって作成されます。kelhoerで答えてください。

私が AES128bit を使用した理由は、AES256 は iOS 4+ では利用できず、iOS5+ で導入されopenssl、派生キーと初期化ベクトルを生成するためにつま先を使用する必要があり (iv)、Apple が拒否したことを学んだので危険でした。 openssl ライブラリと静的にリンクされているアプリ。

プラットフォームは iOS 4.2+ に基づいているため、openssl ライブラリをバンドルして静的にリンクすることに頼ったのは、やり過ぎのようであり、CommonCryptor ライブラリを使用することをお勧めします。

Spongycastle コードを配置した Android バージョンは次のとおりです。

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 での作業) では、同等の方法を理解できません。

これは、これをテストするために、mysql データベースに保存されている Android 側からのデータを復号化することを目的として、Objective C で試したものです。

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

データとユーザーのパスワードを提供し、CCCryptasからリターン コードを取得し-4304ました。

おそらくエンコーディング スキームが CommonCryptor の復号化ルーティングを無効にするのではないかと考えましたNSASCIIStringEncoding

Salt は暗号データ​​とともに格納され、長さは 32 バイトです。

この点で私が欠けているものは、暗号化が苦手であることを念頭に置いてください。

4

2 に答える 2

4

Android 側で使用されるPKCS12Parameters ジェネレーターの直接ポートを自由にコーディングしました。このヘッダーの要点は上記のとおりです。

実装も直接コピーです。ここにあるように、パスワードは PKCS12 と同等の Unicode、ビッグエンディアンに変換され、最後に 2 つの余分なゼロが埋め込まれます。

Generatorは、反復回数 (この場合は Android 側と同様に 1000 回) を実行して派生キーと iv を生成CCCryptorCreateし、SHA256 ダイジェストを使用して、最終的に生成されたキーと iv を へのパラメータとして使用します。

次のコード サンプルを使用しても機能しません-4304CCCryptorFinal

コードの抜粋は次のとおりです。

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

おもしろいことに、復号化が機能し、 の先頭でforを置き換えると、最後の呼び出しがCCCryptorFinal返されます。つまり、パディングはありません。残念ながら、データは私が期待したものではなく、「機能しない」場合でも完全にスクランブルされています。0kCCOptionPKCS7Padding0x0000CCCryptorCreate

それはどこかで失敗しているので、同等のものを達成する方法について誰かがより良いアイデアを持っているなら、私は他の意見を聞いて喜んでいます.

Android 側のメカニズムを変更して iPhone との「クロスプラットフォーム」互換性を持たせるか、データ交換をポータブルにするために使用されるプラットフォームの両側で暗号化が弱くなるという犠牲を払って、両端で互換性を持つ代替の暗号化ソリューションを探すかのいずれかです。

提供された入力データ:

  • ソルトと暗号が「:」で区切られた Base64 でエンコードされた暗号tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
  • 提供されたパスワードは、f00b4r
  • 元の文字列はThe quick brown fox jumped over the lazy dog and ran away
于 2013-05-07T15:12:10.010 に答える
0

そうです、Android 側の暗号化アルゴリズムを破棄して、クロスプラットフォームで互換性のあるものを見つけるという課題がありました。

私はRob Napier の RNCryptorについて多くのことを読み、 JNCryptorを見つけた Android の同等物をグーグルで検索した後、思い切って iOS 側で RNCryptor を採用しました。

githubで JNCryptor コードをフォークして、カスタム設定を指定できる拡張機能を追加し、古いバージョンの Android で SpongyCastle を使用できるようにしました。それ以降、両方のプラットフォームで暗号化/復号化を交互に行うことができました。

私が JNCryptor を強化した理由は、PKDBF2 関数の反復回数が非常に多すぎたためでした - 10,000 であり、デフォルト値でした (コードは古いハンドセットで実行されるため - それは停止しました - デュアル/クアッド コアを使用している場合に最適です!)、繰り返し回数をオーバーライドして、より「耐えられる」ようにする必要がありました-1,000。RNCryptorではカスタム設定が可能でした。

Rob Napier と Duncan Jones の仕事に感謝します!

于 2013-05-14T16:20:50.070 に答える