2016-04-13 109 views
1

我想通過引用IOS實現在Android平臺上實現客戶端加密/解密。我正在與Android和IOS平臺上的加密和解密不同的問題搏鬥,即使他們使用了相同的算法。比方說,當Android設備加密並上傳文件到服務器時,IOS設備無法正確下載和解密。在Android和iPhone中使用AES 256加密(不同的結果)

我使用

  1. 加密與用戶提供的密碼文件密鑰的算法。我們首先使用PBKDF2算法(SHA256的1000次迭代)從密碼導出密鑰/ iv對,然後使用AES 256/CBC加密文件密鑰。結果被稱爲「加密文件密鑰」。此加密文件密鑰將被髮送並存儲在服務器上。當您需要訪問數據時,您可以從加密的文件密鑰中解密文件密鑰。
  2. 所有文件數據均使用AES 256/CBC文件密鑰進行加密。我們使用PBKDF2算法(SHA256的1000次迭代)從文件關鍵字派生key/iv對。
  3. 在本地存儲派生密鑰/ iv對並使用它們來加密文件。加密後,數據被上傳到服務器。下載文件時與解密文件相同。

的Android代碼

private static final String TAG = Crypto.class.getSimpleName(); 

    private static final String CIPHER_ALGORITHM = "AES/CBC/NoPadding"; 

    private static int KEY_LENGTH = 32; 
    private static int KEY_LENGTH_SHORT = 16; 
    // minimum values recommended by PKCS#5, increase as necessary 
    private static int ITERATION_COUNT = 1000; 
    // Should generate random salt for each repo 
    private static byte[] salt = {(byte) 0xda, (byte) 0x90, (byte) 0x45, (byte) 0xc3, (byte) 0x06, (byte) 0xc7, (byte) 0xcc, (byte) 0x26}; 

    private Crypto() { 
    } 

    /** 
    * decrypt repo encKey 
    * 
    * @param password 
    * @param randomKey 
    * @param version 
    * @return 
    * @throws UnsupportedEncodingException 
    * @throws NoSuchAlgorithmException 
    */ 
    public static String deriveKeyPbkdf2(String password, String randomKey, int version) throws UnsupportedEncodingException, NoSuchAlgorithmException { 
     if (TextUtils.isEmpty(password) || TextUtils.isEmpty(randomKey)) { 
      return null; 
     } 

     PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest()); 
     gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password.toCharArray()), salt, ITERATION_COUNT); 
     byte[] keyBytes; 

     if (version == 2) { 
      keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH * 8)).getKey(); 
     } else 
      keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey(); 

     SecretKey realKey = new SecretKeySpec(keyBytes, "AES"); 

     final byte[] iv = deriveIVPbkdf2(realKey.getEncoded()); 

     return seafileDecrypt(fromHex(randomKey), realKey, iv); 
    } 

    public static byte[] deriveIVPbkdf2(byte[] key) throws UnsupportedEncodingException { 
     PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest()); 
     gen.init(key, salt, 10); 
     return ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey(); 
    } 

    /** 
    * All file data is encrypted by the file key with AES 256/CBC. 
    * 
    * We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key. 
    * After encryption, the data is uploaded to the server. 
    * 
    * @param plaintext 
    * @param key 
    * @return 
    */ 
    private static byte[] seafileEncrypt(byte[] plaintext, SecretKey key, byte[] iv) { 
     try { 
      Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 

      IvParameterSpec ivParams = new IvParameterSpec(iv); 
      cipher.init(Cipher.ENCRYPT_MODE, key, ivParams); 

      return cipher.doFinal(plaintext); 
     } catch (NoSuchAlgorithmException e) { 
      e.printStackTrace(); 
      Log.e(TAG, "NoSuchAlgorithmException " + e.getMessage()); 
      return null; 
     } catch (InvalidKeyException e) { 
      e.printStackTrace(); 
      Log.e(TAG, "InvalidKeyException " + e.getMessage()); 
      return null; 
     } catch (InvalidAlgorithmParameterException e) { 
      e.printStackTrace(); 
      Log.e(TAG, "InvalidAlgorithmParameterException " + e.getMessage()); 
      return null; 
     } catch (NoSuchPaddingException e) { 
      e.printStackTrace(); 
      Log.e(TAG, "NoSuchPaddingException " + e.getMessage()); 
      return null; 
     } catch (IllegalBlockSizeException e) { 
      e.printStackTrace(); 
      Log.e(TAG, "IllegalBlockSizeException " + e.getMessage()); 
      return null; 
     } catch (BadPaddingException e) { 
      e.printStackTrace(); 
      Log.e(TAG, "BadPaddingException " + e.getMessage()); 
      return null; 
     } 
    } 

    /** 
    * All file data is encrypted by the file key with AES 256/CBC. 
    * 
    * We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key. 
    * After encryption, the data is uploaded to the server. 
    * 
    * @param plaintext 
    * @param key 
    * @return 
    */ 
    public static byte[] encrypt(byte[] plaintext, String key, byte[] iv, int version) throws NoSuchAlgorithmException { 
     PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest()); 
     gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(key.toCharArray()), salt, ITERATION_COUNT); 
     byte[] keyBytes; 

     if (version == 2) { 
      keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH * 8)).getKey(); 
     } else 
      keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey(); 

     SecretKey realKey = new SecretKeySpec(keyBytes, "AES"); 
     return seafileEncrypt(plaintext, realKey , iv); 
    } 

IOS代碼

+ (int)deriveKey:(const char *)data_in inlen:(int)in_len version:(int)version key:(unsigned char *)key iv:(unsigned char *)iv 
{ 
    unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 }; 
    if (version == 2) { 
     PKCS5_PBKDF2_HMAC (data_in, in_len, 
          salt, sizeof(salt), 
          1000, 
          EVP_sha256(), 
          32, key); 
     PKCS5_PBKDF2_HMAC ((char *)key, 32, 
          salt, sizeof(salt), 
          10, 
          EVP_sha256(), 
          16, iv); 
     return 0; 
    } else if (version == 1) 
     return EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */ 
           EVP_sha1(),  /* message digest */ 
           salt,    /* salt */ 
           (unsigned char*)data_in, 
           in_len, 
           1 << 19, /* iteration times */ 
           key, /* the derived key */ 
           iv); /* IV, initial vector */ 
    else 
     return EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */ 
           EVP_sha1(),  /* message digest */ 
           NULL,    /* salt */ 
           (unsigned char*)data_in, 
           in_len, 
           3, /* iteration times */ 
           key, /* the derived key */ 
           iv); /* IV, initial vector */ 
} 

+(int)seafileEncrypt:(char **)data_out outlen:(int *)out_len datain:(const char *)data_in inlen:(const int)in_len version:(int)version key:(uint8_t *)key iv:(uint8_t *)iv 
{ 
    int ret, blks; 
    EVP_CIPHER_CTX ctx; 
    EVP_CIPHER_CTX_init (&ctx); 
    if (version == 2) 
     ret = EVP_EncryptInit_ex (&ctx, 
            EVP_aes_256_cbc(), /* cipher mode */ 
            NULL, /* engine, NULL for default */ 
            key, /* derived key */ 
            iv); /* initial vector */ 
    else if (version == 1) 
     ret = EVP_EncryptInit_ex (&ctx, 
            EVP_aes_128_cbc(), /* cipher mode */ 
            NULL, /* engine, NULL for default */ 
            key, /* derived key */ 
            iv); /* initial vector */ 
    else 
     ret = EVP_EncryptInit_ex (&ctx, 
            EVP_aes_128_ecb(), /* cipher mode */ 
            NULL, /* engine, NULL for default */ 
            key, /* derived key */ 
            iv); /* initial vector */ 
    if (ret == DEC_FAILURE) 
     return -1; 

    blks = (in_len/BLK_SIZE) + 1; 
    *data_out = (char *)malloc (blks * BLK_SIZE); 
    if (*data_out == NULL) { 
     Debug ("failed to allocate the output buffer.\n"); 
     goto enc_error; 
    } 
    int update_len, final_len; 

    /* Do the encryption. */ 
    ret = EVP_EncryptUpdate (&ctx, 
          (unsigned char*)*data_out, 
          &update_len, 
          (unsigned char*)data_in, 
          in_len); 

    if (ret == ENC_FAILURE) 
     goto enc_error; 


    /* Finish the possible partial block. */ 
    ret = EVP_EncryptFinal_ex (&ctx, 
           (unsigned char*)*data_out + update_len, 
           &final_len); 

    *out_len = update_len + final_len; 

    /* out_len should be equal to the allocated buffer size. */ 
    if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE)) 
     goto enc_error; 

    EVP_CIPHER_CTX_cleanup (&ctx); 

    return 0; 

enc_error: 
    EVP_CIPHER_CTX_cleanup (&ctx); 
    *out_len = -1; 

    if (*data_out != NULL) 
     free (*data_out); 

    *data_out = NULL; 

    return -1; 
} 

+ (void)generateKey:(NSString *)password version:(int)version encKey:(NSString *)encKey key:(uint8_t *)key iv:(uint8_t *)iv 
{ 
    unsigned char key0[32], iv0[16]; 
    char passwordPtr[256] = {0}; // room for terminator (unused) 
    [password getCString:passwordPtr maxLength:sizeof(passwordPtr) encoding:NSUTF8StringEncoding]; 
    if (version < 2) { 
     [NSData deriveKey:passwordPtr inlen:(int)password.length version:version key:key iv:iv]; 
     return; 
    } 
    [NSData deriveKey:passwordPtr inlen:(int)password.length version:version key:key0 iv:iv0]; 
    char enc_random_key[48], dec_random_key[48]; 
    int outlen; 
    hex_to_rawdata(encKey.UTF8String, enc_random_key, 48); 
    [NSData seafileDecrypt:dec_random_key outlen:&outlen datain:(char *)enc_random_key inlen:48 version:2 key:key0 iv:iv0]; 
    [NSData deriveKey:dec_random_key inlen:32 version:2 key:key iv:iv]; 
} 

- (NSData *)encrypt:(NSString *)password encKey:(NSString *)encKey version:(int)version 
{ 
    uint8_t key[kCCKeySizeAES256+1] = {0}, iv[kCCKeySizeAES128+1]; 
    [NSData generateKey:password version:version encKey:encKey key:key iv:iv]; 
    char *data_out; 
    int outlen; 
    int ret = [NSData seafileEncrypt:&data_out outlen:&outlen datain:self.bytes inlen:(int)self.length version:version key:key iv:iv]; 
    if (ret < 0) return nil; 
    return [NSData dataWithBytesNoCopy:data_out length:outlen]; 
} 

這是任何完整的項目誰發現這個有用。

  1. Support client side encryption #487
  2. How does an encrypted library work?
+0

「不能正確解密」。超級模糊。提供更多信息,例外情況等 –

+0

@LukePark Android設備的例外只能對自己以前處理過的文件進行加密/解密,而不是IOS設備處理的文件。我還沒有找到任何線索,因爲我不熟悉Objective-C(或C)語言加密。 –

+1

你不可能得到有用的答案,因爲你的問題包含太多的代碼。請做更多分析。你的代碼由幾個步驟組成。使用相同的輸入數據並比較每個步驟的輸出。這樣你就可以找出iOS和Android真正發生的區別。一旦你更好地分離了問題,發佈一個更具體的問題。 – Codo

回答

1

你好我也有同樣的問題。

我發現兩件事情引起不匹配我的代碼: 1. iOS和Android的加密算法是不一樣的(我已要求PKCS7Padding算法在iOS和我試圖NoPadding和AES/CBC/PKCS5Padding算法的Android 2。在iOS中產生的關鍵是比Android的不同

請參閱我的工作代碼都給出了相同的輸出在iOS和Android。

IOS:

- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key { 
NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key]; 
return [data base64EncodedStringWithOptions:kNilOptions]; 
} 
- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key { 
if ([ciphertext isKindOfClass:[NSString class]]) { 

    NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions]; 
    return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding]; 
} 
return nil; 
} 

穿心蓮內酯ID:(我們修改w.r.t iOS的默認方法AES)

private static String Key = "your key"; 
public static String encryptString(String stringToEncode) throws NullPointerException { 

    try { 
     SecretKeySpec skeySpec = getKey(Key); 
     byte[] clearText = stringToEncode.getBytes("UTF8"); 
     final byte[] iv = new byte[16]; 
     Arrays.fill(iv, (byte) 0x00); 
     IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); 
     cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec); 
     String encrypedValue = Base64.encodeToString(cipher.doFinal(clearText), Base64.DEFAULT); 
     return encrypedValue; 

    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    return ""; 
} 
public static String encryptString(String stringToEncode) throws NullPointerException { 

    try { 
     SecretKeySpec skeySpec = getKey(Key); 
     byte[] clearText = stringToEncode.getBytes("UTF8"); 
     final byte[] iv = new byte[16]; 
     Arrays.fill(iv, (byte) 0x00); 
     IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 
     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); 
     cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec); 
     byte[] cipherData = cipher.doFinal(Base64.decode(stringToEncode.getBytes("UTF-8"), Base64.DEFAULT)); 
     String decoded = new String(cipherData, "UTF-8"); 
     return decoded; 

    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    return ""; 
} 


private static SecretKeySpec getKey(String password) throws UnsupportedEncodingException { 
    int keyLength = 256; 
    byte[] keyBytes = new byte[keyLength/8]; 
    Arrays.fill(keyBytes, (byte) 0x0); 
    byte[] passwordBytes = password.getBytes("UTF-8"); 
    int length = passwordBytes.length < keyBytes.length ? passwordBytes.length : keyBytes.length; 
    System.arraycopy(passwordBytes, 0, keyBytes, 0, length); 
    SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); 
    return key; 
} 

希望這有助於ü傢伙 感謝