Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 603bcb7b authored by Artem Iglikov's avatar Artem Iglikov
Browse files

Move password-related methods to their own class.

Automated change.

Bug: 37519282
Test: it compiles
Change-Id: I09b1fe2854c32abbeed953b83804bc8c976d9fdb
parent 2c2c856b
Loading
Loading
Loading
Loading
+10 −77
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ import com.android.server.backup.restore.PerformUnifiedRestoreTask;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
import com.android.server.backup.utils.PasswordUtils;
import com.android.server.power.BatterySaverPolicy.ServiceType;

import libcore.io.IoUtils;
@@ -121,11 +122,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -139,10 +136,6 @@ import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class RefactoredBackupManagerService implements BackupManagerServiceInterface {

    public static final String TAG = "BackupManagerService";
@@ -669,12 +662,6 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
    private File mPasswordVersionFile;
    private byte[] mPasswordSalt;

    // Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys
    public static final int PBKDF2_HASH_ROUNDS = 10000;
    private static final int PBKDF2_KEY_SIZE = 256;     // bits
    public static final int PBKDF2_SALT_SIZE = 512;    // bits
    public static final String ENCRYPTION_ALGORITHM_NAME = "AES-256";

    // Keep a log of all the apps we've ever backed up, and what the
    // dataset tokens are for both the current backup dataset and
    // the ancestral dataset.
@@ -1169,62 +1156,6 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
        }
    }

    public SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) {
        return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
    }

    private SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt, int rounds) {
        try {
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
            KeySpec ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
            return keyFactory.generateSecret(ks);
        } catch (InvalidKeySpecException e) {
            Slog.e(TAG, "Invalid key spec for PBKDF2!");
        } catch (NoSuchAlgorithmException e) {
            Slog.e(TAG, "PBKDF2 unavailable!");
        }
        return null;
    }

    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());
        }
        return null;
    }

    public String byteArrayToHex(byte[] data) {
        StringBuilder buf = new StringBuilder(data.length * 2);
        for (int i = 0; i < data.length; i++) {
            buf.append(Byte.toHexString(data[i], true));
        }
        return buf.toString();
    }

    public byte[] hexToByteArray(String digits) {
        final int bytes = digits.length() / 2;
        if (2 * bytes != digits.length()) {
            throw new IllegalArgumentException("Hex string must have an even number of digits");
        }

        byte[] result = new byte[bytes];
        for (int i = 0; i < digits.length(); i += 2) {
            result[i / 2] = (byte) Integer.parseInt(digits.substring(i, i + 2), 16);
        }
        return result;
    }

    public 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(algorithm, mkAsChar, salt, rounds);
        return checksum.getEncoded();
    }

    // Used for generating random salts or passwords
    public byte[] randomBytes(int bits) {
        byte[] array = new byte[bits / 8];
@@ -1241,7 +1172,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
        } else {
            // hash the stated current pw and compare to the stored one
            if (candidatePw != null && candidatePw.length() > 0) {
                String currentPwHash = buildPasswordHash(algorithm, candidatePw, mPasswordSalt,
                String currentPwHash = PasswordUtils.buildPasswordHash(algorithm, candidatePw,
                        mPasswordSalt,
                        rounds);
                if (mPasswordHash.equalsIgnoreCase(currentPwHash)) {
                    // candidate hash matches the stored hash -- the password matches
@@ -1262,9 +1194,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter

        // 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)
        if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS)
                && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
                currentPw, PBKDF2_HASH_ROUNDS))) {
                currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS))) {
            return false;
        }

@@ -1304,8 +1236,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter

        try {
            // Okay, build the hash of the new backup password
            byte[] salt = randomBytes(PBKDF2_SALT_SIZE);
            String newPwHash = buildPasswordHash(PBKDF_CURRENT, newPw, salt, PBKDF2_HASH_ROUNDS);
            byte[] salt = randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
            String newPwHash = PasswordUtils.buildPasswordHash(PBKDF_CURRENT, newPw, salt,
                    PasswordUtils.PBKDF2_HASH_ROUNDS);

            OutputStream pwf = null, buffer = null;
            DataOutputStream out = null;
@@ -1344,9 +1277,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
    public boolean backupPasswordMatches(String currentPw) {
        if (hasBackupPassword()) {
            final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
            if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
            if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS)
                    && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
                    currentPw, PBKDF2_HASH_ROUNDS))) {
                    currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS))) {
                if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
                return false;
            }
+15 −13
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.KeyValueAdbBackupEngine;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.PasswordUtils;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
@@ -119,7 +120,8 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
        for (String pkgName : pkgNames) {
            if (!set.containsKey(pkgName)) {
                try {
                    PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(pkgName,
                    PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
                            pkgName,
                            PackageManager.GET_SIGNATURES);
                    set.put(pkgName, info);
                } catch (NameNotFoundException e) {
@@ -134,17 +136,17 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
            OutputStream ofstream) throws Exception {
        // User key will be used to encrypt the master key.
        byte[] newUserSalt = backupManagerService
                .randomBytes(RefactoredBackupManagerService.PBKDF2_SALT_SIZE);
        SecretKey userKey = backupManagerService
                .randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
        SecretKey userKey = PasswordUtils
                .buildPasswordKey(RefactoredBackupManagerService.PBKDF_CURRENT, mEncryptPassword,
                        newUserSalt,
                        RefactoredBackupManagerService.PBKDF2_HASH_ROUNDS);
                        PasswordUtils.PBKDF2_HASH_ROUNDS);

        // the master key is random for each backup
        byte[] masterPw = new byte[256 / 8];
        backupManagerService.getRng().nextBytes(masterPw);
        byte[] checksumSalt = backupManagerService
                .randomBytes(RefactoredBackupManagerService.PBKDF2_SALT_SIZE);
                .randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);

        // primary encryption of the datastream with the random key
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
@@ -153,16 +155,16 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
        OutputStream finalOutput = new CipherOutputStream(ofstream, c);

        // line 4: name of encryption algorithm
        headerbuf.append(RefactoredBackupManagerService.ENCRYPTION_ALGORITHM_NAME);
        headerbuf.append(PasswordUtils.ENCRYPTION_ALGORITHM_NAME);
        headerbuf.append('\n');
        // line 5: user password salt [hex]
        headerbuf.append(backupManagerService.byteArrayToHex(newUserSalt));
        headerbuf.append(PasswordUtils.byteArrayToHex(newUserSalt));
        headerbuf.append('\n');
        // line 6: master key checksum salt [hex]
        headerbuf.append(backupManagerService.byteArrayToHex(checksumSalt));
        headerbuf.append(PasswordUtils.byteArrayToHex(checksumSalt));
        headerbuf.append('\n');
        // line 7: number of PBKDF2 rounds used [decimal]
        headerbuf.append(RefactoredBackupManagerService.PBKDF2_HASH_ROUNDS);
        headerbuf.append(PasswordUtils.PBKDF2_HASH_ROUNDS);
        headerbuf.append('\n');

        // line 8: IV of the user key [hex]
@@ -170,7 +172,7 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
        mkC.init(Cipher.ENCRYPT_MODE, userKey);

        byte[] IV = mkC.getIV();
        headerbuf.append(backupManagerService.byteArrayToHex(IV));
        headerbuf.append(PasswordUtils.byteArrayToHex(IV));
        headerbuf.append('\n');

        // line 9: master IV + key blob, encrypted by the user key [hex].  Blob format:
@@ -185,10 +187,10 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
        // stated number of PBKDF2 rounds
        IV = c.getIV();
        byte[] mk = masterKeySpec.getEncoded();
        byte[] checksum = backupManagerService
        byte[] checksum = PasswordUtils
                .makeKeyChecksum(RefactoredBackupManagerService.PBKDF_CURRENT,
                        masterKeySpec.getEncoded(),
                        checksumSalt, RefactoredBackupManagerService.PBKDF2_HASH_ROUNDS);
                        checksumSalt, PasswordUtils.PBKDF2_HASH_ROUNDS);

        ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
                + checksum.length + 3);
@@ -201,7 +203,7 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
        mkOut.write(checksum);
        mkOut.flush();
        byte[] encryptedMk = mkC.doFinal(blob.toByteArray());
        headerbuf.append(backupManagerService.byteArrayToHex(encryptedMk));
        headerbuf.append(PasswordUtils.byteArrayToHex(encryptedMk));
        headerbuf.append('\n');

        return finalOutput;
+13 −10
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.fullbackup.FullBackupObbConnection;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.PasswordUtils;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
@@ -315,15 +316,15 @@ public class PerformAdbRestoreTask implements Runnable {

        try {
            Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey userKey = backupManagerService
            SecretKey userKey = PasswordUtils
                    .buildPasswordKey(algorithm, mDecryptPassword, userSalt,
                            rounds);
            byte[] IV = backupManagerService.hexToByteArray(userIvHex);
            byte[] IV = PasswordUtils.hexToByteArray(userIvHex);
            IvParameterSpec ivSpec = new IvParameterSpec(IV);
            c.init(Cipher.DECRYPT_MODE,
                    new SecretKeySpec(userKey.getEncoded(), "AES"),
                    ivSpec);
            byte[] mkCipher = backupManagerService.hexToByteArray(masterKeyBlobHex);
            byte[] mkCipher = PasswordUtils.hexToByteArray(masterKeyBlobHex);
            byte[] mkBlob = c.doFinal(mkCipher);

            // first, the master key IV
@@ -342,7 +343,7 @@ public class PerformAdbRestoreTask implements Runnable {
                    offset, offset + len);

            // now validate the decrypted master key against the checksum
            byte[] calculatedCk = backupManagerService.makeKeyChecksum(algorithm, mk, ckSalt,
            byte[] calculatedCk = PasswordUtils.makeKeyChecksum(algorithm, mk, ckSalt,
                    rounds);
            if (Arrays.equals(calculatedCk, mkChecksum)) {
                ivSpec = new IvParameterSpec(IV);
@@ -392,13 +393,13 @@ public class PerformAdbRestoreTask implements Runnable {
            InputStream rawInStream) {
        InputStream result = null;
        try {
            if (encryptionName.equals(RefactoredBackupManagerService.ENCRYPTION_ALGORITHM_NAME)) {
            if (encryptionName.equals(PasswordUtils.ENCRYPTION_ALGORITHM_NAME)) {

                String userSaltHex = readHeaderLine(rawInStream); // 5
                byte[] userSalt = backupManagerService.hexToByteArray(userSaltHex);
                byte[] userSalt = PasswordUtils.hexToByteArray(userSaltHex);

                String ckSaltHex = readHeaderLine(rawInStream); // 6
                byte[] ckSalt = backupManagerService.hexToByteArray(ckSaltHex);
                byte[] ckSalt = PasswordUtils.hexToByteArray(ckSaltHex);

                int rounds = Integer.parseInt(readHeaderLine(rawInStream)); // 7
                String userIvHex = readHeaderLine(rawInStream); // 8
@@ -557,7 +558,8 @@ public class PerformAdbRestoreTask implements Runnable {
                        }

                        try {
                            mTargetApp = backupManagerService.getPackageManager().getApplicationInfo(
                            mTargetApp =
                                    backupManagerService.getPackageManager().getApplicationInfo(
                                            pkg, 0);

                            // If we haven't sent any data to this app yet, we probably
@@ -836,7 +838,8 @@ public class PerformAdbRestoreTask implements Runnable {
                    if (RefactoredBackupManagerService.DEBUG) {
                        Slog.d(RefactoredBackupManagerService.TAG, "Killing host process");
                    }
                    backupManagerService.getActivityManager().killApplicationProcess(app.processName,
                    backupManagerService.getActivityManager().killApplicationProcess(
                            app.processName,
                            app.uid);
                } else {
                    if (RefactoredBackupManagerService.DEBUG) {
+120 −0
Original line number Diff line number Diff line
package com.android.server.backup.utils;

import android.util.Slog;

import com.android.server.backup.RefactoredBackupManagerService;

import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

/**
 * Passwords related utility methods.
 */
public class PasswordUtils {
    // Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys
    public static final int PBKDF2_HASH_ROUNDS = 10000;
    private static final int PBKDF2_KEY_SIZE = 256;     // bits
    public static final int PBKDF2_SALT_SIZE = 512;    // bits
    public static final String ENCRYPTION_ALGORITHM_NAME = "AES-256";

    /**
     * Creates {@link SecretKey} instance from given parameters.
     *
     * @param algorithm - key generation algorithm.
     * @param pw - password.
     * @param salt - salt.
     * @param rounds - number of rounds to run in key generation.
     * @return {@link SecretKey} instance or null in case of an error.
     */
    public static SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) {
        return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
    }

    /**
     * Generates {@link SecretKey} instance from given parameters and returns it's hex
     * representation.
     *
     * @param algorithm - key generation algorithm.
     * @param pw - password.
     * @param salt - salt.
     * @param rounds - number of rounds to run in key generation.
     * @return Hex representation of the generated key, or null if generation failed.
     */
    public static String buildPasswordHash(String algorithm, String pw, byte[] salt, int rounds) {
        SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds);
        if (key != null) {
            return byteArrayToHex(key.getEncoded());
        }
        return null;
    }

    /**
     * Creates hex string representation of the byte array.
     */
    public static String byteArrayToHex(byte[] data) {
        StringBuilder buf = new StringBuilder(data.length * 2);
        for (int i = 0; i < data.length; i++) {
            buf.append(Byte.toHexString(data[i], true));
        }
        return buf.toString();
    }

    /**
     * Creates byte array from it's hex string representation.
     */
    public static byte[] hexToByteArray(String digits) {
        final int bytes = digits.length() / 2;
        if (2 * bytes != digits.length()) {
            throw new IllegalArgumentException("Hex string must have an even number of digits");
        }

        byte[] result = new byte[bytes];
        for (int i = 0; i < digits.length(); i += 2) {
            result[i / 2] = (byte) Integer.parseInt(digits.substring(i, i + 2), 16);
        }
        return result;
    }

    /**
     * Generates {@link SecretKey} instance from given parameters and returns it's checksum.
     *
     * Current implementation returns the key in its primary encoding format.
     *
     * @param algorithm - key generation algorithm.
     * @param pwBytes - password.
     * @param salt - salt.
     * @param rounds - number of rounds to run in key generation.
     * @return Hex representation of the generated key, or null if generation failed.
     */
    public static 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(algorithm, mkAsChar, salt, rounds);
        return checksum.getEncoded();
    }

    private static SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt,
            int rounds) {
        try {
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
            KeySpec
                    ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
            return keyFactory.generateSecret(ks);
        } catch (InvalidKeySpecException e) {
            Slog.e(RefactoredBackupManagerService.TAG, "Invalid key spec for PBKDF2!");
        } catch (NoSuchAlgorithmException e) {
            Slog.e(RefactoredBackupManagerService.TAG, "PBKDF2 unavailable!");
        }
        return null;
    }
}