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

Commit 66b6d1c3 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 4a7913c2 f01e9078
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(