Шифрование с использованием AES 256 в Android и IPhone (другой результат)

1

Я пытаюсь реализовать шифрование/дешифрование на стороне клиента на платформе Android, ссылаясь на реализацию IOS. Я борюсь с вопросом, что шифрование и дешифрование на платформе Android и IOS различны, даже если они использовали один и тот же алгоритм. Скажем, когда устройство Android зашифровывает и загружает файл на сервер, устройство IOS не может скачать и дешифровать его правильно.

Алгоритм, который я использую

  • Зашифруйте ключ файла с предоставленным пользователем паролем. Сначала мы используем алгоритм PBKDF2 (1000 итераций SHA256) для получения пары ключ /iv из пароля, а затем используйте AES 256/CBC для шифрования файла. Результат называется "зашифрованным файловым ключом". Этот зашифрованный файл будет отправлен и сохранен на сервере. Когда вам нужно получить доступ к данным, вы можете расшифровать ключ файла из зашифрованного файла.
  • Все данные файла зашифровываются ключом файла с AES 256/CBC. Мы используем алгоритм PBKDF2 (1000 итераций SHA256) для получения ключа /iv пары из файла.
  • локально хранить производную пару key/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];
}

Вот полный проект для тех, кто нашел это полезным.

Теги:
encryption
algorithm
cryptography

1 ответ

1

У меня тоже была та же проблема.

Я обнаружил 2 вещи, которые вызывают несоответствие для моего кода:  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;
}

Android: (Мы изменили метод aes w.r.t iOS по умолчанию)

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

Надеюсь, что эта помощь у ребят Благодаря

Ещё вопросы

Сообщество Overcoder
Наверх
Меню