Stop regenerating secret key for each package
This commit is contained in:
parent
1519580a36
commit
bd0c41c2d3
5 changed files with 125 additions and 79 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue