+ // Get the import file.
+ File importFile = new File(fileNameEditText.getText().toString());
+
+ // Initialize the import status string
+ String importStatus = "";
+
+ // Import according to the encryption type.
+ switch (encryptionSpinner.getSelectedItemPosition()) {
+ case NO_ENCRYPTION:
+ // Import the unencrypted file.
+ importStatus = importExportDatabaseHelper.importUnencrypted(importFile, this);
+ break;
+
+ case PASSWORD_ENCRYPTION:
+ // Use a private temporary import location.
+ File temporaryUnencryptedImportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings));
+
+ try {
+ // Create an encrypted import file input stream.
+ FileInputStream encryptedImportFileInputStream = new FileInputStream(importFile);
+
+ // Delete the temporary import file if it exists.
+ if (temporaryUnencryptedImportFile.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ temporaryUnencryptedImportFile.delete();
+ }
+
+ // Create an unencrypted import file output stream.
+ FileOutputStream unencryptedImportFileOutputStream = new FileOutputStream(temporaryUnencryptedImportFile);
+
+ // Get a handle for the encryption password EditText.
+ EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext);
+
+ // Get the encryption password.
+ String encryptionPasswordString = encryptionPasswordEditText.getText().toString();
+
+ // Get the salt from the beginning of the import file.
+ byte[] saltByteArray = new byte[32];
+ //noinspection ResultOfMethodCallIgnored
+ encryptedImportFileInputStream.read(saltByteArray);
+
+ // Get the initialization vector from the import file.
+ byte[] initializationVector = new byte[12];
+ //noinspection ResultOfMethodCallIgnored
+ encryptedImportFileInputStream.read(initializationVector);
+
+ // Convert the encryption password to a byte array.
+ byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
+
+ // 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));
+
+ // Allow OpenKeychain to read the file URI.
+ openKeychainDecryptIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Send the intent to the OpenKeychain package.
+ openKeychainDecryptIntent.setPackage("org.sufficientlysecure.keychain");
+
+ // Make it so.
+ startActivity(openKeychainDecryptIntent);
+ } catch (IllegalArgumentException exception) { // The file import location is not valid.
+ // Display a snack bar with the import error.
+ Snackbar.make(fileNameEditText, getString(R.string.import_failed) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
+ }