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.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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> 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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue