2013-05-05 48 views
4

這讓我難住 - 下面的代碼使用SpongyCastle的Android加密/解密 - 我試圖爲iOS實現跨平臺加密/解密。ios上的海綿堡壘加密等效物

下面的代碼(來自Android)使用提供的鹽和密碼來處理AES 128位CBC和PKCS7Padding,其中salt存儲在mysql數據庫中,密碼由最終用戶使用,以下代碼從kelhoer改編自這個答案。

我使用AES128bit的原因是AES256在iOS 4+中不可用,它是在iOS5 +中引入的,並且必須將腳趾插入到使用openssl來生成派生密鑰和初始化向量(iv),這是冒險的據悉,蘋果拒絕與openssl庫靜態鏈接的應用程序。

由於該平臺基於iOS 4.2+,採用bundling and statically linking the 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的工作),我想不出如何做等價,

這是我曾嘗試下目標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的冗長方式。

Salt與密碼數據一起存儲,長度爲32bytes。

我在這方面缺少什麼,要牢記在密碼學方面很薄弱。

+1

我想你想要嘗試在iOS和Android之間使用不同的代碼庫時遇到麻煩,如果你想兼容。你爲什麼不找到一個C/C++ AES實現並將它編譯到兩個平臺的代碼庫中? – 2013-05-13 13:03:25

回答

0

沒錯,我不得不放棄Android端的加密算法,這提出了一個挑戰,找到一個是跨平臺兼容。

我已經閱讀了很多關於Rob Napier's RNCryptor的文章,並且在Google上搜索了一個相當於Android的搜索結果之後,我發現了JNCryptor,我花了一大筆錢在iOS端使用RNCryptor。

github上分叉了JNCryptor代碼,以便爲指定自定義設置添加增強功能,併爲舊版Android應用SpongyCastle。從那裏開始,兩個平臺都可以互換地加密/解密。

我增強JNCryptor的原因是PKDBF2函數的迭代計數相當高--100000是默認值(因爲代碼將在舊手機上運行 - 它佔據了 - 很好,如果你有雙/四核心!),並且需要覆蓋迭代計數,使其更加「可承受」 - 1,000。使用RNCryptor可以使用自定義設置。

感謝Rob Napier和Duncan Jones的工作!

4

我已經自由地編寫了在Android端使用的PKCS12Parameters generator的直接端口,這個標題的要點在上面。

執行過程也是直接複製,如發現here,密碼被轉換爲PKCS12相當於unicode,big-endian,最後填充了兩個額外的零。

發電機生成通過執行迭代的次數,在此情況下,1000導出密鑰和iv,由於是對Android側,使用SHA256文摘,最終生成的密鑰,然後靜脈被用作參數到CCCryptorCreate

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

有趣的,解密工程,:

使用下面的代碼示例不工作或者,它-4304後到CCCryptorFinal

呼叫如圖所示的代碼片段結束如果我在CCCryptorCreate開頭替換kCCOptionPKCS7Padding0x0000,即沒有填充,最後調用CCCryptorFinal返回0。唉,這些數據並不是我所期望的,無論何時「不起作用」,仍然完全混亂。

這是失敗的地方,所以如果任何人有更好的想法如何實現相當於,我會很高興聽到其他意見。

無論是它的變化在Android方面的機制,使其「跨平臺」與iPhone兼容或尋找替代的加密解決方案兼容上都在較弱的加密技術在平臺兩側使用的代價結束用於便攜式數據交換。

作爲供給的輸入數據:

  • Base64編碼密碼,與鹽和密分隔 ':' tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
  • 提供的密碼f00b4r
  • 原始字符串是The quick brown fox jumped over the lazy dog and ran away