From bd0c41c2d3f1e4f45f41fb6dbe2775c4e9733905 Mon Sep 17 00:00:00 2001 From: Steve Soltys Date: Fri, 22 Feb 2019 01:02:06 -0500 Subject: [PATCH] Stop regenerating secret key for each package --- .../backup/security/CipherUtil.java | 33 +++--- .../ContentProviderBackupComponent.java | 26 ++-- .../provider/ContentProviderBackupState.java | 11 ++ .../ContentProviderRestoreComponent.java | 111 ++++++++++-------- .../provider/ContentProviderRestoreState.java | 23 ++++ 5 files changed, 125 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java b/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java index 66c8089d..93e76189 100644 --- a/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java +++ b/app/src/main/java/com/stevesoltys/backup/security/CipherUtil.java @@ -5,7 +5,6 @@ import javax.crypto.spec.IvParameterSpec; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; /** * A utility class for encrypting and decrypting data using a {@link Cipher}. @@ -19,39 +18,35 @@ public class CipherUtil { */ public static final String CIPHER_ALGORITHM = "AES/CFB/PKCS5Padding"; - /** - * Encrypts the given payload using a key generated from the provided password and salt. + /**. + * Encrypts the given payload using the provided secret key. * * @param payload The payload. - * @param password The password. - * @param salt The salt. + * @param secretKey The secret key. + * @param iv The initialization vector. */ - public static byte[] encrypt(byte[] payload, String password, byte[] salt) throws NoSuchPaddingException, - NoSuchAlgorithmException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException, + public static byte[] encrypt(byte[] payload, SecretKey secretKey, byte[] iv) throws NoSuchPaddingException, + NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException { - SecretKey secretKey = KeyGenerator.generate(password, salt); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(salt)); - + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); return cipher.doFinal(payload); } /** - * Decrypts the given payload using a key generated from the provided password and salt. + * Decrypts the given payload using the provided secret key. * - * @param payload The payload. - * @param password The password. - * @param salt The salt. + * @param payload The payload. + * @param secretKey The secret key. + * @param iv The initialization vector. */ - public static byte[] decrypt(byte[] payload, String password, byte[] salt) throws NoSuchPaddingException, - NoSuchAlgorithmException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException, + public static byte[] decrypt(byte[] payload, SecretKey secretKey, byte[] iv) throws NoSuchPaddingException, + NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException { - SecretKey secretKey = KeyGenerator.generate(password, salt); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(salt)); - + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); return cipher.doFinal(payload); } } diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java index 6c037663..b070f1e2 100644 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java +++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java @@ -7,10 +7,12 @@ import android.os.ParcelFileDescriptor; import android.util.Base64; import android.util.Log; import com.stevesoltys.backup.security.CipherUtil; +import com.stevesoltys.backup.security.KeyGenerator; import com.stevesoltys.backup.transport.component.BackupComponent; import libcore.io.IoUtils; import org.apache.commons.io.IOUtils; +import javax.crypto.SecretKey; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -209,13 +211,23 @@ public class ContentProviderBackupComponent implements BackupComponent { clearBackupState(false); } - private void initializeBackupState() throws IOException { + private void initializeBackupState() throws Exception { if (backupState == null) { backupState = new ContentProviderBackupState(); } if (backupState.getOutputStream() == null) { initializeOutputStream(); + + ZipEntry saltZipEntry = new ZipEntry(ContentProviderBackupConstants.SALT_FILE_PATH); + backupState.getOutputStream().putNextEntry(saltZipEntry); + backupState.getOutputStream().write(backupState.getSalt()); + backupState.getOutputStream().closeEntry(); + + + if (configuration.getPassword() != null && !configuration.getPassword().isEmpty()) { + backupState.setSecretKey(KeyGenerator.generate(configuration.getPassword(), backupState.getSalt())); + } } } @@ -227,11 +239,6 @@ public class ContentProviderBackupComponent implements BackupComponent { FileOutputStream fileOutputStream = new FileOutputStream(outputFileDescriptor.getFileDescriptor()); ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream); backupState.setOutputStream(zipOutputStream); - - ZipEntry saltZipEntry = new ZipEntry(ContentProviderBackupConstants.SALT_FILE_PATH); - zipOutputStream.putNextEntry(saltZipEntry); - zipOutputStream.write(backupState.getSalt()); - zipOutputStream.closeEntry(); } private int transferIncrementalBackupData(BackupDataInput backupDataInput) throws IOException { @@ -257,13 +264,12 @@ public class ContentProviderBackupComponent implements BackupComponent { backupDataInput.readEntityData(buffer, 0, dataSize); try { - if (configuration.getPassword() != null && !configuration.getPassword().isEmpty()) { - + if (backupState.getSecretKey() != null) { byte[] payload = Arrays.copyOfRange(buffer, 0, dataSize); - String password = configuration.getPassword(); + SecretKey secretKey = backupState.getSecretKey(); byte[] salt = backupState.getSalt(); - outputStream.write(CipherUtil.encrypt(payload, password, salt)); + outputStream.write(CipherUtil.encrypt(payload, secretKey, salt)); } else { outputStream.write(buffer, 0, dataSize); diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java index 02a14adb..a2c18baa 100644 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java +++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupState.java @@ -2,6 +2,7 @@ package com.stevesoltys.backup.transport.component.provider; import android.os.ParcelFileDescriptor; +import javax.crypto.SecretKey; import java.io.InputStream; import java.security.SecureRandom; import java.util.zip.ZipOutputStream; @@ -29,6 +30,8 @@ class ContentProviderBackupState { private byte[] salt; + private SecretKey secretKey; + public ContentProviderBackupState() { salt = new byte[16]; SECURE_RANDOM.nextBytes(salt); @@ -93,4 +96,12 @@ class ContentProviderBackupState { byte[] getSalt() { return salt; } + + public SecretKey getSecretKey() { + return secretKey; + } + + public void setSecretKey(SecretKey secretKey) { + this.secretKey = secretKey; + } } diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java index eb7841c5..eaf0d00f 100644 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java +++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java @@ -15,10 +15,10 @@ import com.stevesoltys.backup.transport.component.RestoreComponent; import libcore.io.IoUtils; import libcore.io.Streams; -import javax.crypto.Cipher; import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; import java.io.*; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -59,15 +59,37 @@ public class ContentProviderRestoreComponent implements RestoreComponent { seekToEntry(inputStream, ContentProviderBackupConstants.SALT_FILE_PATH); restoreState.setSalt(Streams.readFullyNoClose(inputStream)); + restoreState.setSecretKey(KeyGenerator.generate(configuration.getPassword(), restoreState.getSalt())); IoUtils.closeQuietly(inputFileDescriptor); IoUtils.closeQuietly(inputStream); - } catch (IOException ex) { + } catch (Exception ex) { Log.e(TAG, "Salt not found", ex); } } + try { + List zipEntries = new LinkedList<>(); + + ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor(); + ZipInputStream inputStream = buildInputStream(inputFileDescriptor); + + ZipEntry zipEntry; + while ((zipEntry = inputStream.getNextEntry()) != null) { + zipEntries.add(zipEntry); + inputStream.closeEntry(); + } + + IoUtils.closeQuietly(inputFileDescriptor); + IoUtils.closeQuietly(inputStream); + + restoreState.setZipEntries(zipEntries); + + } catch (Exception ex) { + Log.e(TAG, "Salt not found", ex); + } + return TRANSPORT_OK; } @@ -82,54 +104,21 @@ public class ContentProviderRestoreComponent implements RestoreComponent { restoreState.setPackageIndex(packageIndex); String name = packages[packageIndex].packageName; - try { - if (containsPackageFile(configuration.getIncrementalBackupDirectory() + name)) { - restoreState.setRestoreType(TYPE_KEY_VALUE); - return new RestoreDescription(name, restoreState.getRestoreType()); + if (containsPackageFile(configuration.getIncrementalBackupDirectory() + name)) { + restoreState.setRestoreType(TYPE_KEY_VALUE); + return new RestoreDescription(name, restoreState.getRestoreType()); - } else if (containsPackageFile(configuration.getFullBackupDirectory() + name)) { - restoreState.setRestoreType(TYPE_FULL_STREAM); - return new RestoreDescription(name, restoreState.getRestoreType()); - } - - } catch (IOException ex) { - Log.e(TAG, "Error choosing package " + name + " at index " + packageIndex + "failed selection:", ex); + } else if (containsPackageFile(configuration.getFullBackupDirectory() + name)) { + restoreState.setRestoreType(TYPE_FULL_STREAM); + return new RestoreDescription(name, restoreState.getRestoreType()); } } return RestoreDescription.NO_MORE_PACKAGES; } - private boolean containsPackageFile(String fileName) throws IOException { - ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor(); - ZipInputStream inputStream = buildInputStream(inputFileDescriptor); - - Optional zipEntry = seekToEntry(inputStream, fileName); - IoUtils.closeQuietly(inputFileDescriptor); - IoUtils.closeQuietly(inputStream); - return zipEntry.isPresent(); - } - - private ParcelFileDescriptor buildInputFileDescriptor() throws FileNotFoundException { - ContentResolver contentResolver = configuration.getContext().getContentResolver(); - return contentResolver.openFileDescriptor(configuration.getUri(), "r"); - } - - private ZipInputStream buildInputStream(ParcelFileDescriptor inputFileDescriptor) throws FileNotFoundException { - FileInputStream fileInputStream = new FileInputStream(inputFileDescriptor.getFileDescriptor()); - return new ZipInputStream(fileInputStream); - } - - private Optional seekToEntry(ZipInputStream inputStream, String entryPath) throws IOException { - ZipEntry zipEntry; - while ((zipEntry = inputStream.getNextEntry()) != null) { - - if (zipEntry.getName().startsWith(entryPath)) { - return Optional.of(zipEntry); - } - inputStream.closeEntry(); - } - - return Optional.empty(); + private boolean containsPackageFile(String fileName) { + return restoreState.getZipEntries().stream() + .anyMatch(zipEntry -> zipEntry.getName().startsWith(fileName)); } @Override @@ -177,14 +166,36 @@ public class ContentProviderRestoreComponent implements RestoreComponent { return TRANSPORT_OK; } + private ParcelFileDescriptor buildInputFileDescriptor() throws FileNotFoundException { + ContentResolver contentResolver = configuration.getContext().getContentResolver(); + return contentResolver.openFileDescriptor(configuration.getUri(), "r"); + } + + private ZipInputStream buildInputStream(ParcelFileDescriptor inputFileDescriptor) throws FileNotFoundException { + FileInputStream fileInputStream = new FileInputStream(inputFileDescriptor.getFileDescriptor()); + return new ZipInputStream(fileInputStream); + } + + private Optional seekToEntry(ZipInputStream inputStream, String entryPath) throws IOException { + ZipEntry zipEntry; + while ((zipEntry = inputStream.getNextEntry()) != null) { + + if (zipEntry.getName().startsWith(entryPath)) { + return Optional.of(zipEntry); + } + inputStream.closeEntry(); + } + + return Optional.empty(); + } + private byte[] readBackupData(ZipInputStream inputStream) throws Exception { byte[] backupData = Streams.readFullyNoClose(inputStream); + SecretKey secretKey = restoreState.getSecretKey(); + byte[] initializationVector = restoreState.getSalt(); - String password = configuration.getPassword(); - byte[] salt = restoreState.getSalt(); - - if (password != null && !password.isEmpty() && salt != null) { - backupData = CipherUtil.decrypt(backupData, password, salt); + if (secretKey != null) { + backupData = CipherUtil.decrypt(backupData, secretKey, initializationVector); } return backupData; diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java index cca36ddf..80dc3c6d 100644 --- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java +++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreState.java @@ -3,6 +3,9 @@ package com.stevesoltys.backup.transport.component.provider; import android.content.pm.PackageInfo; import android.os.ParcelFileDescriptor; +import javax.crypto.SecretKey; +import java.util.List; +import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** @@ -22,6 +25,10 @@ class ContentProviderRestoreState { private byte[] salt; + private SecretKey secretKey; + + private List zipEntries; + ParcelFileDescriptor getInputFileDescriptor() { return inputFileDescriptor; } @@ -69,4 +76,20 @@ class ContentProviderRestoreState { void setSalt(byte[] salt) { this.salt = salt; } + + public SecretKey getSecretKey() { + return secretKey; + } + + public void setSecretKey(SecretKey secretKey) { + this.secretKey = secretKey; + } + + public List getZipEntries() { + return zipEntries; + } + + public void setZipEntries(List zipEntries) { + this.zipEntries = zipEntries; + } }