Stop regenerating secret key for each package

This commit is contained in:
Steve Soltys 2019-02-22 01:02:06 -05:00
parent 1519580a36
commit bd0c41c2d3
5 changed files with 125 additions and 79 deletions

View file

@ -5,7 +5,6 @@ import javax.crypto.spec.IvParameterSpec;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
/** /**
* A utility class for encrypting and decrypting data using a {@link Cipher}. * 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"; 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 payload The payload.
* @param password The password. * @param secretKey The secret key.
* @param salt The salt. * @param iv The initialization vector.
*/ */
public static byte[] encrypt(byte[] payload, String password, byte[] salt) throws NoSuchPaddingException, public static byte[] encrypt(byte[] payload, SecretKey secretKey, byte[] iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException,
InvalidAlgorithmParameterException, InvalidKeyException { InvalidAlgorithmParameterException, InvalidKeyException {
SecretKey secretKey = KeyGenerator.generate(password, salt);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 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); 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 payload The payload.
* @param password The password. * @param secretKey The secret key.
* @param salt The salt. * @param iv The initialization vector.
*/ */
public static byte[] decrypt(byte[] payload, String password, byte[] salt) throws NoSuchPaddingException, public static byte[] decrypt(byte[] payload, SecretKey secretKey, byte[] iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException,
InvalidAlgorithmParameterException, InvalidKeyException { InvalidAlgorithmParameterException, InvalidKeyException {
SecretKey secretKey = KeyGenerator.generate(password, salt);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 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); return cipher.doFinal(payload);
} }
} }

View file

@ -7,10 +7,12 @@ import android.os.ParcelFileDescriptor;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import com.stevesoltys.backup.security.CipherUtil; import com.stevesoltys.backup.security.CipherUtil;
import com.stevesoltys.backup.security.KeyGenerator;
import com.stevesoltys.backup.transport.component.BackupComponent; import com.stevesoltys.backup.transport.component.BackupComponent;
import libcore.io.IoUtils; import libcore.io.IoUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import javax.crypto.SecretKey;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -209,13 +211,23 @@ public class ContentProviderBackupComponent implements BackupComponent {
clearBackupState(false); clearBackupState(false);
} }
private void initializeBackupState() throws IOException { private void initializeBackupState() throws Exception {
if (backupState == null) { if (backupState == null) {
backupState = new ContentProviderBackupState(); backupState = new ContentProviderBackupState();
} }
if (backupState.getOutputStream() == null) { if (backupState.getOutputStream() == null) {
initializeOutputStream(); 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()); FileOutputStream fileOutputStream = new FileOutputStream(outputFileDescriptor.getFileDescriptor());
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream); ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
backupState.setOutputStream(zipOutputStream); 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 { private int transferIncrementalBackupData(BackupDataInput backupDataInput) throws IOException {
@ -257,13 +264,12 @@ public class ContentProviderBackupComponent implements BackupComponent {
backupDataInput.readEntityData(buffer, 0, dataSize); backupDataInput.readEntityData(buffer, 0, dataSize);
try { try {
if (configuration.getPassword() != null && !configuration.getPassword().isEmpty()) { if (backupState.getSecretKey() != null) {
byte[] payload = Arrays.copyOfRange(buffer, 0, dataSize); byte[] payload = Arrays.copyOfRange(buffer, 0, dataSize);
String password = configuration.getPassword(); SecretKey secretKey = backupState.getSecretKey();
byte[] salt = backupState.getSalt(); byte[] salt = backupState.getSalt();
outputStream.write(CipherUtil.encrypt(payload, password, salt)); outputStream.write(CipherUtil.encrypt(payload, secretKey, salt));
} else { } else {
outputStream.write(buffer, 0, dataSize); outputStream.write(buffer, 0, dataSize);

View file

@ -2,6 +2,7 @@ package com.stevesoltys.backup.transport.component.provider;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import javax.crypto.SecretKey;
import java.io.InputStream; import java.io.InputStream;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -29,6 +30,8 @@ class ContentProviderBackupState {
private byte[] salt; private byte[] salt;
private SecretKey secretKey;
public ContentProviderBackupState() { public ContentProviderBackupState() {
salt = new byte[16]; salt = new byte[16];
SECURE_RANDOM.nextBytes(salt); SECURE_RANDOM.nextBytes(salt);
@ -93,4 +96,12 @@ class ContentProviderBackupState {
byte[] getSalt() { byte[] getSalt() {
return salt; return salt;
} }
public SecretKey getSecretKey() {
return secretKey;
}
public void setSecretKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
} }

View file

@ -15,10 +15,10 @@ import com.stevesoltys.backup.transport.component.RestoreComponent;
import libcore.io.IoUtils; import libcore.io.IoUtils;
import libcore.io.Streams; import libcore.io.Streams;
import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.io.*; import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -59,15 +59,37 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
seekToEntry(inputStream, ContentProviderBackupConstants.SALT_FILE_PATH); seekToEntry(inputStream, ContentProviderBackupConstants.SALT_FILE_PATH);
restoreState.setSalt(Streams.readFullyNoClose(inputStream)); restoreState.setSalt(Streams.readFullyNoClose(inputStream));
restoreState.setSecretKey(KeyGenerator.generate(configuration.getPassword(), restoreState.getSalt()));
IoUtils.closeQuietly(inputFileDescriptor); IoUtils.closeQuietly(inputFileDescriptor);
IoUtils.closeQuietly(inputStream); IoUtils.closeQuietly(inputStream);
} catch (IOException ex) { } catch (Exception ex) {
Log.e(TAG, "Salt not found", ex); Log.e(TAG, "Salt not found", ex);
} }
} }
try {
List<ZipEntry> 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; return TRANSPORT_OK;
} }
@ -82,54 +104,21 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
restoreState.setPackageIndex(packageIndex); restoreState.setPackageIndex(packageIndex);
String name = packages[packageIndex].packageName; String name = packages[packageIndex].packageName;
try { if (containsPackageFile(configuration.getIncrementalBackupDirectory() + name)) {
if (containsPackageFile(configuration.getIncrementalBackupDirectory() + name)) { restoreState.setRestoreType(TYPE_KEY_VALUE);
restoreState.setRestoreType(TYPE_KEY_VALUE); return new RestoreDescription(name, restoreState.getRestoreType());
return new RestoreDescription(name, restoreState.getRestoreType());
} else if (containsPackageFile(configuration.getFullBackupDirectory() + name)) { } else if (containsPackageFile(configuration.getFullBackupDirectory() + name)) {
restoreState.setRestoreType(TYPE_FULL_STREAM); restoreState.setRestoreType(TYPE_FULL_STREAM);
return new RestoreDescription(name, restoreState.getRestoreType()); return new RestoreDescription(name, restoreState.getRestoreType());
}
} catch (IOException ex) {
Log.e(TAG, "Error choosing package " + name + " at index " + packageIndex + "failed selection:", ex);
} }
} }
return RestoreDescription.NO_MORE_PACKAGES; return RestoreDescription.NO_MORE_PACKAGES;
} }
private boolean containsPackageFile(String fileName) throws IOException { private boolean containsPackageFile(String fileName) {
ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor(); return restoreState.getZipEntries().stream()
ZipInputStream inputStream = buildInputStream(inputFileDescriptor); .anyMatch(zipEntry -> zipEntry.getName().startsWith(fileName));
Optional<ZipEntry> 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<ZipEntry> 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();
} }
@Override @Override
@ -177,14 +166,36 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
return TRANSPORT_OK; 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<ZipEntry> 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 { private byte[] readBackupData(ZipInputStream inputStream) throws Exception {
byte[] backupData = Streams.readFullyNoClose(inputStream); byte[] backupData = Streams.readFullyNoClose(inputStream);
SecretKey secretKey = restoreState.getSecretKey();
byte[] initializationVector = restoreState.getSalt();
String password = configuration.getPassword(); if (secretKey != null) {
byte[] salt = restoreState.getSalt(); backupData = CipherUtil.decrypt(backupData, secretKey, initializationVector);
if (password != null && !password.isEmpty() && salt != null) {
backupData = CipherUtil.decrypt(backupData, password, salt);
} }
return backupData; return backupData;

View file

@ -3,6 +3,9 @@ package com.stevesoltys.backup.transport.component.provider;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import javax.crypto.SecretKey;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
/** /**
@ -22,6 +25,10 @@ class ContentProviderRestoreState {
private byte[] salt; private byte[] salt;
private SecretKey secretKey;
private List<ZipEntry> zipEntries;
ParcelFileDescriptor getInputFileDescriptor() { ParcelFileDescriptor getInputFileDescriptor() {
return inputFileDescriptor; return inputFileDescriptor;
} }
@ -69,4 +76,20 @@ class ContentProviderRestoreState {
void setSalt(byte[] salt) { void setSalt(byte[] salt) {
this.salt = salt; this.salt = salt;
} }
public SecretKey getSecretKey() {
return secretKey;
}
public void setSecretKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
public List<ZipEntry> getZipEntries() {
return zipEntries;
}
public void setZipEntries(List<ZipEntry> zipEntries) {
this.zipEntries = zipEntries;
}
} }