package com.seafile.seadroid2.gesturelock; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.FileObserver; import android.util.Log; public class LockPasswordUtils { private static final String TAG = "LockPasswordUtils"; private final static String LOCK_PASSWORD_SALT_FILE = "password_salt"; private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt"; private static final String LOCK_PASSWORD_FILE = "password.key"; private static SharedPreferences mSharedPreferences; private static Editor mEditor; private static File sLockPasswordFilename; private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean( false); private static FileObserver sPasswordObserver; private static class PasswordFileObserver extends FileObserver { public PasswordFileObserver(String path, int mask) { super(path, mask); } @Override public void onEvent(int event, String path) { if (LOCK_PASSWORD_FILE.equals(path)) { Log.d(TAG, "lock password file changed"); sHaveNonZeroPasswordFile .set(sLockPasswordFilename.length() > 0); } } } public LockPasswordUtils(Context context) { mSharedPreferences = context.getSharedPreferences( LOCK_PASSWORD_SALT_FILE, Context.MODE_PRIVATE); mEditor = mSharedPreferences.edit(); if (sLockPasswordFilename == null) { String dataSystemDirectory = context.getCacheDir() .getAbsolutePath(); sLockPasswordFilename = new File(dataSystemDirectory, LOCK_PASSWORD_FILE); sHaveNonZeroPasswordFile.set(sLockPasswordFilename.length() > 0); int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_TO | FileObserver.CREATE; sPasswordObserver = new PasswordFileObserver(dataSystemDirectory, fileObserverMask); sPasswordObserver.startWatching(); } } /** * Check to see if the user has stored a lock pattern. * * @return Whether a saved pattern exists. */ public static boolean savedPasswordExists() { return sHaveNonZeroPasswordFile.get(); } /** * Compute the password quality from the given password string. */ public static int computePasswordQuality(String password) { boolean hasDigit = false; boolean hasNonDigit = false; final int len = password.length(); for (int i = 0; i < len; i++) { if (Character.isDigit(password.charAt(i))) { hasDigit = true; } else { hasNonDigit = true; } } if (hasNonDigit && hasDigit) { return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; } if (hasNonDigit) { return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; } if (hasDigit) { return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; } return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } /** * Save a lock password. Does not ensure that the password is as good as the * requested mode, but will adjust the mode to be as good as the pattern. * * @param password * The password to save * @param quality * {@see * DevicePolicyManager#getPasswordQuality(android.content.ComponentName * )} * @param isFallback * Specifies if this is a fallback to biometric weak */ public static void saveLockPassword(String password, int quality, boolean isFallback) { // Compute the hash final byte[] hash = passwordToHash(password); try { // Write the hash to file RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rwd"); // Truncate the file if pattern is null, to clear the lo try { if (password == null) { raf.setLength(0); } else { raf.write(hash, 0, hash.length); } } finally { if (raf != null) raf.close(); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings // provider Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); } catch (IOException ioe) { // Cant do much Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); } } /** * Check to see if a password matches the saved password. If no password * exists, always returns true. * * @param password * The password to check. * @return Whether the password matches the stored one. */ public static boolean checkPassword(String password) { try { // Read all the bytes from the file RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r"); final byte[] stored = new byte[(int) raf.length()]; int got = raf.read(stored, 0, stored.length); raf.close(); if (got <= 0) { return true; } // Compare the hash from the file with the entered password's hash return Arrays.equals(stored, passwordToHash(password)); } catch (FileNotFoundException fnfe) { return true; } catch (IOException ioe) { return true; } } /* * 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. * * @param password the gesture pattern. * * @return the hash of the pattern in a byte array. */ public static byte[] passwordToHash(String password) { if (password == null) { return null; } String algo = null; byte[] hashed = null; try { byte[] saltedPassword = (password + getSalt()).getBytes(); byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest( saltedPassword); byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest( saltedPassword); hashed = (toHex(sha1) + toHex(md5)).getBytes(); } catch (NoSuchAlgorithmException e) { Log.w(TAG, "Failed to encode string because of missing algorithm: " + algo); } return hashed; } private static String getSalt() { long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0); if (salt == 0) { try { salt = SecureRandom.getInstance("SHA1PRNG").nextLong(); setLong(LOCK_PASSWORD_SALT_KEY, salt); Log.v(TAG, "Initialized lock password salt"); } catch (NoSuchAlgorithmException e) { // Throw an exception rather than storing a password we'll never // be able to recover throw new IllegalStateException( "Couldn't get SecureRandom number", e); } } return Long.toHexString(salt); } private static String toHex(byte[] ary) { final String hex = "0123456789ABCDEF"; String ret = ""; for (int i = 0; i < ary.length; i++) { ret += hex.charAt((ary[i] >> 4) & 0xf); ret += hex.charAt(ary[i] & 0xf); } return ret; } private static long getLong(String secureSettingKey, long def) { return mSharedPreferences.getLong(LOCK_PASSWORD_SALT_KEY, def); } private static void setLong(String secureSettingKey, long value) { mEditor.putLong(LOCK_PASSWORD_SALT_KEY, value); mEditor.commit(); } }