+ // Append the salt to the encryption password byte array. This protects against rainbow table attacks.
+ byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
+ System.arraycopy(encryptionPasswordByteArray, 0, encryptionPasswordWithSaltByteArray, 0, encryptionPasswordByteArray.length);
+ System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.length, saltByteArray.length);
+
+ // Get a SHA-512 message digest.
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
+
+ // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored.
+ byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray);
+
+ // Truncate the encryption password byte array to 256 bits (32 bytes).
+ byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32);
+
+ // Create an AES secret key from the encryption password byte array.
+ SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES");
+
+ // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext. It doesn't use padding.
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+
+ // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
+
+ // Initialize the cipher.
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
+
+ // Create a cipher input stream.
+ CipherInputStream cipherInputStream = new CipherInputStream(encryptedImportFileInputStream, cipher);
+
+ // Initialize variables to store data as it is moved from the cipher input stream to the unencrypted import file output stream. Move 128 bits (16 bytes) at a time.
+ int numberOfBytesRead;
+ byte[] decryptedBytes = new byte[16];
+
+ // Read up to 128 bits (16 bytes) of data from the cipher input stream. `-1` will be returned when the end fo the file is reached.
+ while ((numberOfBytesRead = cipherInputStream.read(decryptedBytes)) != -1) {
+ // Write the data to the unencrypted import file output stream.
+ unencryptedImportFileOutputStream.write(decryptedBytes, 0, numberOfBytesRead);
+ }
+
+ // Close the streams.
+ unencryptedImportFileOutputStream.flush();
+ unencryptedImportFileOutputStream.close();
+ cipherInputStream.close();
+ encryptedImportFileInputStream.close();
+
+ // Wipe the encryption data from memory.
+ //noinspection UnusedAssignment
+ encryptionPasswordString = "";
+ Arrays.fill(saltByteArray, (byte) 0);
+ Arrays.fill(initializationVector, (byte) 0);
+ Arrays.fill(encryptionPasswordByteArray, (byte) 0);
+ Arrays.fill(encryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(hashedEncryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(truncatedHashedEncryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(decryptedBytes, (byte) 0);
+
+ // Import the unencrypted database from the private location.
+ importStatus = importExportDatabaseHelper.importUnencrypted(temporaryUnencryptedImportFile, this);
+
+ // Delete the temporary unencrypted import file.
+ //noinspection ResultOfMethodCallIgnored
+ temporaryUnencryptedImportFile.delete();
+ } catch (Exception exception) {
+ importStatus = exception.toString();
+ }
+ break;
+
+ case OPENPGP_ENCRYPTION:
+ try {
+ // Create an decryption intent for OpenKeychain.
+ Intent openKeychainDecryptIntent = new Intent("org.sufficientlysecure.keychain.action.DECRYPT_DATA");
+
+ // Include the URI to be decrypted.
+ openKeychainDecryptIntent.setData(FileProvider.getUriForFile(this, getString(R.string.file_provider), importFile));