/******************************************************************************* * Copyright (c) 2015 btows.com. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package com.lzx.lock.utils; import android.content.Context; import android.os.FileObserver; import android.util.Log; import com.lzx.lock.widget.LockPatternView; 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.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** * 图案解锁加密、解密工具类 * * @author way * */ public class LockPatternUtils { private static final String TAG = "LockPatternUtils"; private static final String LOCK_PATTERN_FILE = "gesture.key"; /** * The minimum number of dots in a valid pattern. */ public static final int MIN_LOCK_PATTERN_SIZE = 4; /** * The maximum number of incorrect attempts before the user is prevented * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}. */ public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5; /** * The minimum number of dots the user must include in a wrong pattern * attempt for it to be counted against the counts that affect * {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and * {@link #FAILED_ATTEMPTS_BEFORE_RESET} */ public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE; /** * How long the user is prevented from trying again after entering the wrong * pattern too many times. */ public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L; private static File sLockPatternFilename; private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean( false); private static FileObserver sPasswordObserver; private static class LockPatternFileObserver extends FileObserver { public LockPatternFileObserver(String path, int mask) { super(path, mask); } @Override public void onEvent(int event, String path) { // Logger.d(TAG, "file path" + path); if (LOCK_PATTERN_FILE.equals(path)) { // Logger.d(TAG, "lock pattern file changed"); sHaveNonZeroPatternFile.set(sLockPatternFilename.length() > 0); } } } public LockPatternUtils(Context context) { if (sLockPatternFilename == null) { String dataSystemDirectory = context.getFilesDir() .getAbsolutePath(); sLockPatternFilename = new File(dataSystemDirectory , LOCK_PATTERN_FILE); sHaveNonZeroPatternFile.set(sLockPatternFilename.length() > 0); int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_TO | FileObserver.CREATE; sPasswordObserver = new LockPatternFileObserver( dataSystemDirectory, fileObserverMask); sPasswordObserver.startWatching(); } } /** * Check to see if the user has stored a lock pattern. * * @return Whether a saved pattern exists. */ public boolean savedPatternExists() { return sHaveNonZeroPatternFile.get(); } public void clearLock() { saveLockPattern(null); } /** * Deserialize a pattern. 解密,用于保存状态 * * @param string * The pattern serialized with {@link #patternToString} * @return The pattern. */ public static List<LockPatternView.Cell> stringToPattern(String string) { List<LockPatternView.Cell> result = new ArrayList<LockPatternView.Cell>(); final byte[] bytes = string.getBytes(); for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; result.add(LockPatternView.Cell.of(b / 3, b % 3)); } return result; } /** * Serialize a pattern. 加密 * * @param pattern * The pattern. * @return The pattern in string form. */ public static String patternToString(List<LockPatternView.Cell> pattern) { if (pattern == null) { return ""; } final int patternSize = pattern.size(); byte[] res = new byte[patternSize]; for (int i = 0; i < patternSize; i++) { LockPatternView.Cell cell = pattern.get(i); res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); } return new String(res); } /** * Save a lock pattern. * * @param pattern The new pattern to save. * @param isFallback Specifies if this is a fallback to biometric weak */ public void saveLockPattern(List<LockPatternView.Cell> pattern) { // Compute the hash final byte[] hash = LockPatternUtils.patternToHash(pattern); try { // Write the hash to file RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rwd"); // Truncate the file if pattern is null, to clear the lock if (pattern == null) { raf.setLength(0); } else { raf.write(hash, 0, hash.length); } 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 " + sLockPatternFilename); } catch (IOException ioe) { // Cant do much // Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); } } /* * Generate an SHA-1 hash for the pattern. 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 pattern the gesture pattern. * * @return the hash of the pattern in a byte array. */ private static byte[] patternToHash(List<LockPatternView.Cell> pattern) { if (pattern == null) { return null; } final int patternSize = pattern.size(); byte[] res = new byte[patternSize]; for (int i = 0; i < patternSize; i++) { LockPatternView.Cell cell = pattern.get(i); res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); } try { MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] hash = md.digest(res); return hash; } catch (NoSuchAlgorithmException nsa) { return res; } } /** * Check to see if a pattern matches the saved pattern. If no pattern * exists, always returns true. * * @param pattern * The pattern to check. * @return Whether the pattern matches the stored one. */ public boolean checkPattern(List<LockPatternView.Cell> pattern) { try { // Read all the bytes from the file RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "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 pattern's hash return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern)); } catch (FileNotFoundException fnfe) { return true; } catch (IOException ioe) { return true; } } }