Loading core/java/com/android/internal/widget/ILockSettings.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/com/android/internal/widget/LockPatternUtils.java +81 −24 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; Loading Loading @@ -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; } /** Loading Loading @@ -830,6 +852,7 @@ public class LockPatternUtils { updateEncryptionPasswordIfNeeded(password, PasswordMetrics.computeForPassword(password).quality, userHandle); updatePasswordHistory(password, userHandle); onAfterChangingPassword(userHandle); } /** Loading @@ -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); Loading @@ -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); } /** Loading Loading @@ -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. Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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"); Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +29 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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) { Loading services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +6 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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( Loading Loading
core/java/com/android/internal/widget/ILockSettings.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/com/android/internal/widget/LockPatternUtils.java +81 −24 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; Loading Loading @@ -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; } /** Loading Loading @@ -830,6 +852,7 @@ public class LockPatternUtils { updateEncryptionPasswordIfNeeded(password, PasswordMetrics.computeForPassword(password).quality, userHandle); updatePasswordHistory(password, userHandle); onAfterChangingPassword(userHandle); } /** Loading @@ -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); Loading @@ -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); } /** Loading Loading @@ -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. Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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"); Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +29 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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) { Loading
services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +6 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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( Loading