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

View file

@ -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);

View file

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

View file

@ -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<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;
}
@ -82,7 +104,6 @@ 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());
@ -91,45 +112,13 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
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);
}
}
return RestoreDescription.NO_MORE_PACKAGES;
}
private boolean containsPackageFile(String fileName) throws IOException {
ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor();
ZipInputStream inputStream = buildInputStream(inputFileDescriptor);
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();
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<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 {
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;

View file

@ -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<ZipEntry> 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<ZipEntry> getZipEntries() {
return zipEntries;
}
public void setZipEntries(List<ZipEntry> zipEntries) {
this.zipEntries = zipEntries;
}
}