Merge "Adapt to underlying changes in the PBKDF2 implementation" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
1d13f13dea
@ -82,7 +82,7 @@ import android.util.StringBuilderPrinter;
|
||||
import com.android.internal.backup.BackupConstants;
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.internal.backup.IObbBackupService;
|
||||
import com.android.internal.backup.LocalTransport;
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.server.PackageManagerBackupAgent.Metadata;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
@ -140,11 +140,16 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
private static final boolean DEBUG = true;
|
||||
private static final boolean MORE_DEBUG = false;
|
||||
|
||||
// Historical and current algorithm names
|
||||
static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1";
|
||||
static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit";
|
||||
|
||||
// Name and current contents version of the full-backup manifest file
|
||||
static final String BACKUP_MANIFEST_FILENAME = "_manifest";
|
||||
static final int BACKUP_MANIFEST_VERSION = 1;
|
||||
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
|
||||
static final int BACKUP_FILE_VERSION = 1;
|
||||
static final int BACKUP_FILE_VERSION = 2;
|
||||
static final int BACKUP_PW_FILE_VERSION = 2;
|
||||
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
|
||||
|
||||
static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
|
||||
@ -450,6 +455,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
private final SecureRandom mRng = new SecureRandom();
|
||||
private String mPasswordHash;
|
||||
private File mPasswordHashFile;
|
||||
private int mPasswordVersion;
|
||||
private File mPasswordVersionFile;
|
||||
private byte[] mPasswordSalt;
|
||||
|
||||
// Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys
|
||||
@ -810,6 +817,27 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
}
|
||||
mDataDir = Environment.getDownloadCacheDirectory();
|
||||
|
||||
mPasswordVersion = 1; // unless we hear otherwise
|
||||
mPasswordVersionFile = new File(mBaseStateDir, "pwversion");
|
||||
if (mPasswordVersionFile.exists()) {
|
||||
FileInputStream fin = null;
|
||||
DataInputStream in = null;
|
||||
try {
|
||||
fin = new FileInputStream(mPasswordVersionFile);
|
||||
in = new DataInputStream(fin);
|
||||
mPasswordVersion = in.readInt();
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Unable to read backup pw version");
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (fin != null) fin.close();
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Error closing pw version files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPasswordHashFile = new File(mBaseStateDir, "pwhash");
|
||||
if (mPasswordHashFile.exists()) {
|
||||
FileInputStream fin = null;
|
||||
@ -1110,13 +1138,13 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey buildPasswordKey(String pw, byte[] salt, int rounds) {
|
||||
return buildCharArrayKey(pw.toCharArray(), salt, rounds);
|
||||
private SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) {
|
||||
return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
|
||||
}
|
||||
|
||||
private SecretKey buildCharArrayKey(char[] pwArray, byte[] salt, int rounds) {
|
||||
private SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt, int rounds) {
|
||||
try {
|
||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
|
||||
KeySpec ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
|
||||
return keyFactory.generateSecret(ks);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
@ -1127,8 +1155,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String buildPasswordHash(String pw, byte[] salt, int rounds) {
|
||||
SecretKey key = buildPasswordKey(pw, salt, rounds);
|
||||
private String buildPasswordHash(String algorithm, String pw, byte[] salt, int rounds) {
|
||||
SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds);
|
||||
if (key != null) {
|
||||
return byteArrayToHex(key.getEncoded());
|
||||
}
|
||||
@ -1156,13 +1184,13 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] makeKeyChecksum(byte[] pwBytes, byte[] salt, int rounds) {
|
||||
private byte[] makeKeyChecksum(String algorithm, byte[] pwBytes, byte[] salt, int rounds) {
|
||||
char[] mkAsChar = new char[pwBytes.length];
|
||||
for (int i = 0; i < pwBytes.length; i++) {
|
||||
mkAsChar[i] = (char) pwBytes[i];
|
||||
}
|
||||
|
||||
Key checksum = buildCharArrayKey(mkAsChar, salt, rounds);
|
||||
Key checksum = buildCharArrayKey(algorithm, mkAsChar, salt, rounds);
|
||||
return checksum.getEncoded();
|
||||
}
|
||||
|
||||
@ -1174,7 +1202,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
}
|
||||
|
||||
// Backup password management
|
||||
boolean passwordMatchesSaved(String candidatePw, int rounds) {
|
||||
boolean passwordMatchesSaved(String algorithm, String candidatePw, int rounds) {
|
||||
// First, on an encrypted device we require matching the device pw
|
||||
final boolean isEncrypted;
|
||||
try {
|
||||
@ -1217,7 +1245,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
} else {
|
||||
// hash the stated current pw and compare to the stored one
|
||||
if (candidatePw != null && candidatePw.length() > 0) {
|
||||
String currentPwHash = buildPasswordHash(candidatePw, mPasswordSalt, rounds);
|
||||
String currentPwHash = buildPasswordHash(algorithm, candidatePw, mPasswordSalt, rounds);
|
||||
if (mPasswordHash.equalsIgnoreCase(currentPwHash)) {
|
||||
// candidate hash matches the stored hash -- the password matches
|
||||
return true;
|
||||
@ -1232,11 +1260,37 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
|
||||
"setBackupPassword");
|
||||
|
||||
// If the supplied pw doesn't hash to the the saved one, fail
|
||||
if (!passwordMatchesSaved(currentPw, PBKDF2_HASH_ROUNDS)) {
|
||||
// When processing v1 passwords we may need to try two different PBKDF2 checksum regimes
|
||||
final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
|
||||
|
||||
// If the supplied pw doesn't hash to the the saved one, fail. The password
|
||||
// might be caught in the legacy crypto mismatch; verify that too.
|
||||
if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
|
||||
&& !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
|
||||
currentPw, PBKDF2_HASH_ROUNDS))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Snap up to current on the pw file version
|
||||
mPasswordVersion = BACKUP_PW_FILE_VERSION;
|
||||
FileOutputStream pwFout = null;
|
||||
DataOutputStream pwOut = null;
|
||||
try {
|
||||
pwFout = new FileOutputStream(mPasswordVersionFile);
|
||||
pwOut = new DataOutputStream(pwFout);
|
||||
pwOut.writeInt(mPasswordVersion);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Unable to write backup pw version; password not changed");
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (pwOut != null) pwOut.close();
|
||||
if (pwFout != null) pwFout.close();
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Unable to close pw version record");
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing the password is okay
|
||||
if (newPw == null || newPw.isEmpty()) {
|
||||
if (mPasswordHashFile.exists()) {
|
||||
@ -1254,7 +1308,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
try {
|
||||
// Okay, build the hash of the new backup password
|
||||
byte[] salt = randomBytes(PBKDF2_SALT_SIZE);
|
||||
String newPwHash = buildPasswordHash(newPw, salt, PBKDF2_HASH_ROUNDS);
|
||||
String newPwHash = buildPasswordHash(PBKDF_CURRENT, newPw, salt, PBKDF2_HASH_ROUNDS);
|
||||
|
||||
OutputStream pwf = null, buffer = null;
|
||||
DataOutputStream out = null;
|
||||
@ -1297,6 +1351,19 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean backupPasswordMatches(String currentPw) {
|
||||
if (hasBackupPassword()) {
|
||||
final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
|
||||
if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
|
||||
&& !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
|
||||
currentPw, PBKDF2_HASH_ROUNDS))) {
|
||||
if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Maintain persistent state around whether need to do an initialize operation.
|
||||
// Must be called with the queue lock held.
|
||||
void recordInitPendingLocked(boolean isPending, String transportName) {
|
||||
@ -2717,11 +2784,9 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
|
||||
// Verify that the given password matches the currently-active
|
||||
// backup password, if any
|
||||
if (hasBackupPassword()) {
|
||||
if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) {
|
||||
if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
|
||||
return;
|
||||
}
|
||||
if (!backupPasswordMatches(mCurrentPassword)) {
|
||||
if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the global file header. All strings are UTF-8 encoded; lines end
|
||||
@ -2729,7 +2794,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
// final '\n'.
|
||||
//
|
||||
// line 1: "ANDROID BACKUP"
|
||||
// line 2: backup file format version, currently "1"
|
||||
// line 2: backup file format version, currently "2"
|
||||
// line 3: compressed? "0" if not compressed, "1" if compressed.
|
||||
// line 4: name of encryption algorithm [currently only "none" or "AES-256"]
|
||||
//
|
||||
@ -2837,7 +2902,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
OutputStream ofstream) throws Exception {
|
||||
// User key will be used to encrypt the master key.
|
||||
byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE);
|
||||
SecretKey userKey = buildPasswordKey(mEncryptPassword, newUserSalt,
|
||||
SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt,
|
||||
PBKDF2_HASH_ROUNDS);
|
||||
|
||||
// the master key is random for each backup
|
||||
@ -2884,7 +2949,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
// stated number of PBKDF2 rounds
|
||||
IV = c.getIV();
|
||||
byte[] mk = masterKeySpec.getEncoded();
|
||||
byte[] checksum = makeKeyChecksum(masterKeySpec.getEncoded(),
|
||||
byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(),
|
||||
checksumSalt, PBKDF2_HASH_ROUNDS);
|
||||
|
||||
ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
|
||||
@ -3227,11 +3292,9 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
FileInputStream rawInStream = null;
|
||||
DataInputStream rawDataIn = null;
|
||||
try {
|
||||
if (hasBackupPassword()) {
|
||||
if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) {
|
||||
if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
|
||||
return;
|
||||
}
|
||||
if (!backupPasswordMatches(mCurrentPassword)) {
|
||||
if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
mBytes = 0;
|
||||
@ -3252,8 +3315,12 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
if (Arrays.equals(magicBytes, streamHeader)) {
|
||||
// okay, header looks good. now parse out the rest of the fields.
|
||||
String s = readHeaderLine(rawInStream);
|
||||
if (Integer.parseInt(s) == BACKUP_FILE_VERSION) {
|
||||
// okay, it's a version we recognize
|
||||
final int archiveVersion = Integer.parseInt(s);
|
||||
if (archiveVersion <= BACKUP_FILE_VERSION) {
|
||||
// okay, it's a version we recognize. if it's version 1, we may need
|
||||
// to try two different PBKDF2 regimes to compare checksums.
|
||||
final boolean pbkdf2Fallback = (archiveVersion == 1);
|
||||
|
||||
s = readHeaderLine(rawInStream);
|
||||
compressed = (Integer.parseInt(s) != 0);
|
||||
s = readHeaderLine(rawInStream);
|
||||
@ -3261,7 +3328,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
// no more header to parse; we're good to go
|
||||
okay = true;
|
||||
} else if (mDecryptPassword != null && mDecryptPassword.length() > 0) {
|
||||
preCompressStream = decodeAesHeaderAndInitialize(s, rawInStream);
|
||||
preCompressStream = decodeAesHeaderAndInitialize(s, pbkdf2Fallback,
|
||||
rawInStream);
|
||||
if (preCompressStream != null) {
|
||||
okay = true;
|
||||
}
|
||||
@ -3321,7 +3389,71 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
InputStream decodeAesHeaderAndInitialize(String encryptionName, InputStream rawInStream) {
|
||||
InputStream attemptMasterKeyDecryption(String algorithm, byte[] userSalt, byte[] ckSalt,
|
||||
int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream,
|
||||
boolean doLog) {
|
||||
InputStream result = null;
|
||||
|
||||
try {
|
||||
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKey userKey = buildPasswordKey(algorithm, mDecryptPassword, userSalt,
|
||||
rounds);
|
||||
byte[] IV = hexToByteArray(userIvHex);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
c.init(Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(userKey.getEncoded(), "AES"),
|
||||
ivSpec);
|
||||
byte[] mkCipher = hexToByteArray(masterKeyBlobHex);
|
||||
byte[] mkBlob = c.doFinal(mkCipher);
|
||||
|
||||
// first, the master key IV
|
||||
int offset = 0;
|
||||
int len = mkBlob[offset++];
|
||||
IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
|
||||
offset += len;
|
||||
// then the master key itself
|
||||
len = mkBlob[offset++];
|
||||
byte[] mk = Arrays.copyOfRange(mkBlob,
|
||||
offset, offset + len);
|
||||
offset += len;
|
||||
// and finally the master key checksum hash
|
||||
len = mkBlob[offset++];
|
||||
byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
|
||||
offset, offset + len);
|
||||
|
||||
// now validate the decrypted master key against the checksum
|
||||
byte[] calculatedCk = makeKeyChecksum(algorithm, mk, ckSalt, rounds);
|
||||
if (Arrays.equals(calculatedCk, mkChecksum)) {
|
||||
ivSpec = new IvParameterSpec(IV);
|
||||
c.init(Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(mk, "AES"),
|
||||
ivSpec);
|
||||
// Only if all of the above worked properly will 'result' be assigned
|
||||
result = new CipherInputStream(rawInStream, c);
|
||||
} else if (doLog) Slog.w(TAG, "Incorrect password");
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
if (doLog) Slog.e(TAG, "Needed parameter spec unavailable!", e);
|
||||
} catch (BadPaddingException e) {
|
||||
// This case frequently occurs when the wrong password is used to decrypt
|
||||
// the master key. Use the identical "incorrect password" log text as is
|
||||
// used in the checksum failure log in order to avoid providing additional
|
||||
// information to an attacker.
|
||||
if (doLog) Slog.w(TAG, "Incorrect password");
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
if (doLog) Slog.w(TAG, "Invalid block size in master key");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
if (doLog) Slog.e(TAG, "Needed decryption algorithm unavailable!");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
if (doLog) Slog.e(TAG, "Needed padding mechanism unavailable!");
|
||||
} catch (InvalidKeyException e) {
|
||||
if (doLog) Slog.w(TAG, "Illegal password; aborting");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
InputStream decodeAesHeaderAndInitialize(String encryptionName, boolean pbkdf2Fallback,
|
||||
InputStream rawInStream) {
|
||||
InputStream result = null;
|
||||
try {
|
||||
if (encryptionName.equals(ENCRYPTION_ALGORITHM_NAME)) {
|
||||
@ -3338,59 +3470,13 @@ class BackupManagerService extends IBackupManager.Stub {
|
||||
String masterKeyBlobHex = readHeaderLine(rawInStream); // 9
|
||||
|
||||
// decrypt the master key blob
|
||||
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKey userKey = buildPasswordKey(mDecryptPassword, userSalt,
|
||||
rounds);
|
||||
byte[] IV = hexToByteArray(userIvHex);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
c.init(Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(userKey.getEncoded(), "AES"),
|
||||
ivSpec);
|
||||
byte[] mkCipher = hexToByteArray(masterKeyBlobHex);
|
||||
byte[] mkBlob = c.doFinal(mkCipher);
|
||||
|
||||
// first, the master key IV
|
||||
int offset = 0;
|
||||
int len = mkBlob[offset++];
|
||||
IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
|
||||
offset += len;
|
||||
// then the master key itself
|
||||
len = mkBlob[offset++];
|
||||
byte[] mk = Arrays.copyOfRange(mkBlob,
|
||||
offset, offset + len);
|
||||
offset += len;
|
||||
// and finally the master key checksum hash
|
||||
len = mkBlob[offset++];
|
||||
byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
|
||||
offset, offset + len);
|
||||
|
||||
// now validate the decrypted master key against the checksum
|
||||
byte[] calculatedCk = makeKeyChecksum(mk, ckSalt, rounds);
|
||||
if (Arrays.equals(calculatedCk, mkChecksum)) {
|
||||
ivSpec = new IvParameterSpec(IV);
|
||||
c.init(Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(mk, "AES"),
|
||||
ivSpec);
|
||||
// Only if all of the above worked properly will 'result' be assigned
|
||||
result = new CipherInputStream(rawInStream, c);
|
||||
} else Slog.w(TAG, "Incorrect password");
|
||||
result = attemptMasterKeyDecryption(PBKDF_CURRENT, userSalt, ckSalt,
|
||||
rounds, userIvHex, masterKeyBlobHex, rawInStream, false);
|
||||
if (result == null && pbkdf2Fallback) {
|
||||
result = attemptMasterKeyDecryption(PBKDF_FALLBACK, userSalt, ckSalt,
|
||||
rounds, userIvHex, masterKeyBlobHex, rawInStream, true);
|
||||
}
|
||||
} else Slog.w(TAG, "Unsupported encryption method: " + encryptionName);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Slog.e(TAG, "Needed parameter spec unavailable!", e);
|
||||
} catch (BadPaddingException e) {
|
||||
// This case frequently occurs when the wrong password is used to decrypt
|
||||
// the master key. Use the identical "incorrect password" log text as is
|
||||
// used in the checksum failure log in order to avoid providing additional
|
||||
// information to an attacker.
|
||||
Slog.w(TAG, "Incorrect password");
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
Slog.w(TAG, "Invalid block size in master key");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Slog.e(TAG, "Needed decryption algorithm unavailable!");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Slog.e(TAG, "Needed padding mechanism unavailable!");
|
||||
} catch (InvalidKeyException e) {
|
||||
Slog.w(TAG, "Illegal password; aborting");
|
||||
} catch (NumberFormatException e) {
|
||||
Slog.w(TAG, "Can't parse restore data header");
|
||||
} catch (IOException e) {
|
||||
|
18
tests/LegacyRestoreTest/README
Normal file
18
tests/LegacyRestoreTest/README
Normal file
@ -0,0 +1,18 @@
|
||||
The file "jbmr2-encrypted-settings-abcd.ab" in this directory is an encrypted
|
||||
"adb backup" archive of the settings provider package. It was generated on a
|
||||
Nexus 4 running Android 4.3 (API 18), and so predates the Android 4.4 changes
|
||||
to the PBKDF2 implementation. The archive's encryption password, entered on-screen,
|
||||
is "abcd" (with no quotation marks).
|
||||
|
||||
'adb restore' decrypts and applies the restored archive successfully on a device
|
||||
running Android 4.3, but fails to restore correctly on a device running Android 4.4,
|
||||
reporting an invalid password in logcat. This is the situation reported in bug
|
||||
<https://code.google.com/p/android/issues/detail?id=63880>.
|
||||
|
||||
The file "kk-fixed-encrypted-settings-abcd.ab" is a similar encrypted "adb backup"
|
||||
archive, using the same key, generated on a Nexus 4 running Android 4.4 with a fix
|
||||
to this bug in place. This archive should be successfully restorable on any
|
||||
version of Android which incorporates the fix.
|
||||
|
||||
These archives can be used as an ongoing test to verify that historical encrypted
|
||||
archives from various points in Android's history can be successfully restored.
|
BIN
tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab
Normal file
BIN
tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab
Normal file
Binary file not shown.
BIN
tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab
Normal file
BIN
tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab
Normal file
Binary file not shown.
Reference in New Issue
Block a user