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

Commit 1e90f013 authored by Rubin Xu's avatar Rubin Xu Committed by android-build-merger
Browse files

Merge "Make password history hashing more secure" into pi-dev

am: 66b6d1c3

Change-Id: Iae4f9ca93a193d24b23410c92659d35a50506b4b
parents c5e42dc1 66b6d1c3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ interface ILockSettings {
    boolean checkVoldPassword(int userId);
    boolean havePattern(int userId);
    boolean havePassword(int userId);
    byte[] getHashFactor(String currentCredential, int userId);
    void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword);
    boolean getSeparateProfileChallengeEnabled(int userId);
    void registerStrongAuthTracker(in IStrongAuthTracker tracker);
+81 −24
Original line number Diff line number Diff line
@@ -66,8 +66,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;
/**
 * Utilities for the lock pattern and its settings.
 */
@@ -165,6 +167,7 @@ public class LockPatternUtils {

    public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
    public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
    private static final String HISTORY_DELIMITER = ",";

    private final Context mContext;
    private final ContentResolver mContentResolver;
@@ -506,32 +509,51 @@ public class LockPatternUtils {
        }
    }

    /**
     * Returns the password history hash factor, needed to check new password against password
     * history with {@link #checkPasswordHistory(String, byte[], int)}
     */
    public byte[] getPasswordHistoryHashFactor(String currentPassword, int userId) {
        try {
            return getLockSettings().getHashFactor(currentPassword, userId);
        } catch (RemoteException e) {
            Log.e(TAG, "failed to get hash factor", e);
            return null;
        }
    }

    /**
     * Check to see if a password matches any of the passwords stored in the
     * password history.
     *
     * @param password The password to check.
     * @param passwordToCheck The password to check.
     * @param hashFactor Hash factor of the current user returned from
     *        {@link ILockSettings#getHashFactor}
     * @return Whether the password matches any in the history.
     */
    public boolean checkPasswordHistory(String password, int userId) {
        String passwordHashString = new String(
                passwordToHash(password, userId), StandardCharsets.UTF_8);
    public boolean checkPasswordHistory(String passwordToCheck, byte[] hashFactor, int userId) {
        if (TextUtils.isEmpty(passwordToCheck)) {
            Log.e(TAG, "checkPasswordHistory: empty password");
            return false;
        }
        String passwordHistory = getString(PASSWORD_HISTORY_KEY, userId);
        if (passwordHistory == null) {
        if (TextUtils.isEmpty(passwordHistory)) {
            return false;
        }
        // Password History may be too long...
        int passwordHashLength = passwordHashString.length();
        int passwordHistoryLength = getRequestedPasswordHistoryLength(userId);
        if(passwordHistoryLength == 0) {
            return false;
        }
        int neededPasswordHistoryLength = passwordHashLength * passwordHistoryLength
                + passwordHistoryLength - 1;
        if (passwordHistory.length() > neededPasswordHistoryLength) {
            passwordHistory = passwordHistory.substring(0, neededPasswordHistoryLength);
        String legacyHash = legacyPasswordToHash(passwordToCheck, userId);
        String passwordHash = passwordToHistoryHash(passwordToCheck, hashFactor, userId);
        String[] history = passwordHistory.split(HISTORY_DELIMITER);
        // Password History may be too long...
        for (int i = 0; i < Math.min(passwordHistoryLength, history.length); i++) {
            if (history[i].equals(legacyHash) || history[i].equals(passwordHash)) {
                return true;
            }
        }
        return passwordHistory.contains(passwordHashString);
        return false;
    }

    /**
@@ -830,6 +852,7 @@ public class LockPatternUtils {
        updateEncryptionPasswordIfNeeded(password,
                PasswordMetrics.computeForPassword(password).quality, userHandle);
        updatePasswordHistory(password, userHandle);
        onAfterChangingPassword(userHandle);
    }

    /**
@@ -852,8 +875,15 @@ public class LockPatternUtils {
        }
    }

    /**
     * Store the hash of the *current* password in the password history list, if device policy
     * enforces password history requirement.
     */
    private void updatePasswordHistory(String password, int userHandle) {

        if (TextUtils.isEmpty(password)) {
            Log.e(TAG, "checkPasswordHistory: empty password");
            return;
        }
        // Add the password to the password history. We assume all
        // password hashes have the same length for simplicity of implementation.
        String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
@@ -864,16 +894,25 @@ public class LockPatternUtils {
        if (passwordHistoryLength == 0) {
            passwordHistory = "";
        } else {
            byte[] hash = passwordToHash(password, userHandle);
            passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
            // Cut it to contain passwordHistoryLength hashes
            // and passwordHistoryLength -1 commas.
            passwordHistory = passwordHistory.substring(0, Math.min(hash.length
                    * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
                    .length()));
            final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
            String hash = passwordToHistoryHash(password, hashFactor, userHandle);
            if (hash == null) {
                Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
                hash = legacyPasswordToHash(password, userHandle);
            }
            if (TextUtils.isEmpty(passwordHistory)) {
                passwordHistory = hash;
            } else {
                String[] history = passwordHistory.split(HISTORY_DELIMITER);
                StringJoiner joiner = new StringJoiner(HISTORY_DELIMITER);
                joiner.add(hash);
                for (int i = 0; i < passwordHistoryLength - 1 && i < history.length; i++) {
                    joiner.add(history[i]);
                }
                passwordHistory = joiner.toString();
            }
        }
        setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
        onAfterChangingPassword(userHandle);
    }

    /**
@@ -1098,7 +1137,7 @@ public class LockPatternUtils {
        return Long.toHexString(salt);
    }

    /*
    /**
     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
     * Not the most secure, but it is at least a second level of protection. First level is that
     * the file is in a location only readable by the system process.
@@ -1107,7 +1146,7 @@ public class LockPatternUtils {
     *
     * @return the hash of the pattern in a byte array.
     */
    public byte[] passwordToHash(String password, int userId) {
    public String legacyPasswordToHash(String password, int userId) {
        if (password == null) {
            return null;
        }
@@ -1122,7 +1161,24 @@ public class LockPatternUtils {
            System.arraycopy(md5, 0, combined, sha1.length, md5.length);

            final char[] hexEncoded = HexEncoding.encode(combined);
            return new String(hexEncoded).getBytes(StandardCharsets.UTF_8);
            return new String(hexEncoded);
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("Missing digest algorithm: ", e);
        }
    }

    /**
     * Hash the password for password history check purpose.
     */
    private String passwordToHistoryHash(String passwordToHash, byte[] hashFactor, int userId) {
        if (TextUtils.isEmpty(passwordToHash) || hashFactor == null) {
            return null;
        }
        try {
            MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
            sha256.update(hashFactor);
            sha256.update((passwordToHash + getSalt(userId)).getBytes());
            return new String(HexEncoding.encode(sha256.digest()));
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("Missing digest algorithm: ", e);
        }
@@ -1571,6 +1627,7 @@ public class LockPatternUtils {

            updateEncryptionPasswordIfNeeded(credential, quality, userId);
            updatePasswordHistory(credential, userId);
            onAfterChangingPassword(userId);
        } else {
            if (!TextUtils.isEmpty(credential)) {
                throw new IllegalArgumentException("password must be emtpy for NONE type");
+29 −1
Original line number Diff line number Diff line
@@ -1742,7 +1742,8 @@ public class LockSettingsService extends ILockSettings.Stub {
            if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
                hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(credential));
            } else {
                hash = mLockPatternUtils.passwordToHash(credential, userId);
                hash = mLockPatternUtils.legacyPasswordToHash(credential, userId)
                        .getBytes(StandardCharsets.UTF_8);
            }
            if (Arrays.equals(hash, storedHash.hash)) {
                if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
@@ -2532,6 +2533,33 @@ public class LockSettingsService extends ILockSettings.Stub {
        mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
    }

    /**
     * Returns a fixed pseudorandom byte string derived from the user's synthetic password.
     * This is used to salt the password history hash to protect the hash against offline
     * bruteforcing, since rederiving this value requires a successful authentication.
     */
    @Override
    public byte[] getHashFactor(String currentCredential, int userId) throws RemoteException {
        checkPasswordReadPermission(userId);
        if (TextUtils.isEmpty(currentCredential)) {
            currentCredential = null;
        }
        synchronized (mSpManager) {
            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                Slog.w(TAG, "Synthetic password not enabled");
                return null;
            }
            long handle = getSyntheticPasswordHandleLocked(userId);
            AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
                    getGateKeeperService(), handle, currentCredential, userId, null);
            if (auth.authToken == null) {
                Slog.w(TAG, "Current credential is incorrect");
                return null;
            }
            return auth.authToken.derivePasswordHashFactor();
        }
    }

    private long addEscrowToken(byte[] token, int userId) throws RemoteException {
        if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
        synchronized (mSpManager) {
+6 −0
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@ public class SyntheticPasswordManager {
    private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
    private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes();
    private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
    private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
    private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
    private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
    private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
@@ -165,6 +166,11 @@ public class SyntheticPasswordManager {
                    syntheticPassword.getBytes());
        }

        public byte[] derivePasswordHashFactor() {
            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_PASSWORD_HASH,
                    syntheticPassword.getBytes());
        }

        private void initialize(byte[] P0, byte[] P1) {
            this.P1 = P1;
            this.syntheticPassword = String.valueOf(HexEncoding.encode(