UI工具
在Android SDK的\tools\bin目录下有个工具uiautomatorviewer,它通过截屏并分析XML布局文件的方式,为用户提供控件信息查看服务。左上角四个按钮依次用于打开已保存的布局,获取详细布局,获取简洁布局,保存布局。将测试机通过usb连接到电脑,打开调试模式。打开图案锁屏界面,点击第二个按钮,获取当前的view类。如图,可以找到一个锁屏密码工具类LockPatternUtils.jjava。不同Android版本的实现逻辑有差异,这里分析4.3版本。
输入密码算法分析
通过在线Android源码网站进行分析,搜索LockPatternUtils找到源码,可以看到输入密码算法,分析得知,算法将原文密码和盐值分别进行SHA-1加密和MD5加密,转换为Hex后再次进行拼接。
/* /frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java */
public byte[] passwordToHash(String password) {
840 if (password == null) {
841 return null;
842 }
843 String algo = null;
844 byte[] hashed = null;
845 try {
846 byte[] saltedPassword = (password + getSalt()).getBytes();
847 byte[] sha1 = MessageDigest.getInstance(algo = "SHA-
1").digest(saltedPassword);
848 byte[] md5 = MessageDigest.getInstance(algo =
"MD5").digest(saltedPassword);
849 hashed = (toHex(sha1) + toHex(md5)).getBytes();
850 } catch (NoSuchAlgorithmException e) {
851 Log.w(TAG, "Failed to encode string because of missing algorithm: " +
algo);
852 }
853 return hashed;
854 }
问题转化为如何获取盐值,查看getSalt()源码:首先调用getLong()为lockscreen.password_salt从某处获取一个盐值,如果盐值为0,便随机生成一个,并调用setLong()保存到该处。
/* /frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java */
private String getSalt() {
818 long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0);
819 if (salt == 0) {
820 try {
821 salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
822 setLong(LOCK_PASSWORD_SALT_KEY, salt);
823 Log.v(TAG, "Initialized lock password salt");
824 } catch (NoSuchAlgorithmException e) {
825 // Throw an exception rather than storing a password we'll never be able to recover
826 throw new IllegalStateException("Couldn't get SecureRandom number", e);
827 }
828 }
829 return Long.toHexString(salt);
830 }
/*常量命名CONSTANT_CASE,一般采用全部大写(作为方法参数时除外),单词间用下划线分割,用final修饰*/
131 public final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
查看getLong()函数,调用了getLockSettings()的getLong函数
/* /frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java */
private long getLong(String secureSettingKey, long defaultValue) {
1198 try {
1199 return getLockSettings().getLong(secureSettingKey, defaultValue,
1200 getCurrentOrCallingUserId());
1201 } catch (RemoteException re) {
1202 return defaultValue;
1203 }
1204 }
getLockSettings通过ServiceManager获取一个服务,在Android中,获取服务的方式最终实现逻辑都在XXXServices类中。
/* /frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java */
private ILockSettings getLockSettings() {
176 if (mLockSettingsService == null) {
177 mLockSettingsService = ILockSettings.Stub.asInterface(
178 (IBinder) ServiceManager.getService("lock_settings"));
179 }
180 return mLockSettingsService;
181 }
ILockSettings接口的定义,setLong()也在其中
/*/frameworks/base/core/java/com/android/internal/widget/ILockSettings.aidl*/
0 interface ILockSettings {
21 void setBoolean(in String key, in boolean value, in int userId);
22 void setLong(in String key, in long value, in int userId);
23 void setString(in String key, in String value, in int userId);
24 boolean getBoolean(in String key, in boolean defaultValue, in int userId);
25 long getLong(in String key, in long defaultValue, in int userId);
26 String getString(in String key, in String defaultValue, in int userId);
27 void setLockPattern(in byte[] hash, int userId);
28 boolean checkPattern(in byte[] hash, int userId);
29 void setLockPassword(in byte[] hash, int userId);
30 boolean checkPassword(in byte[] hash, int userId);
31 boolean havePattern(int userId);
32 boolean havePassword(int userId);
33 void removeUser(int userId);
34}
找到LockSettingsService.java
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
@Override
206 public long getLong(String key, long defaultValue, int userId) throws RemoteException {
207 checkReadPermission(key, userId);
208
209 String value = readFromDb(key, null, userId);
210 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
211 }
找到readFromDb()函数,可以看到,通过mOpenHelper.getReadableDatabase(),得到一个SQLiteDatabase类型的只读的数据库连接,接着根据传入的key进行对locksettings进行查询,判断,返回结果.
TABLE为表名,查询语句等价于SELECT COLUMN_KRY from TABLE WHERE COLUMN_USERID=userid and COLUMN_KEY=key 查询到的cursor是指向第一行记录之前的,使用moveToFirst()或moveToNext()指向第一行记录,再用getString(0)取出第一列的值。
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
private String readFromDb(String key, String defaultValue, int userId) {
382 Cursor cursor;
383 String result = defaultValue;
384 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
385 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
386 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
387 new String[] { Integer.toString(userId), key },
388 null, null, null)) != null) {
389 if (cursor.moveToFirst()) {
390 result = cursor.getString(0);
391 }
392 cursor.close();
393 }
394 return result;
395 }
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
63 private static final String TABLE = "locksettings";
64 private static final String COLUMN_KEY = "name";
65 private static final String COLUMN_USERID = "user";
66 private static final String COLUMN_VALUE = "value";
private static final String[] COLUMNS_FOR_QUERY = {
69 COLUMN_VALUE
70 };
接着查看setLong方法
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
182 @Override
183 public void setLong(String key, long value, int userId) throws RemoteException {
184 checkWritePermission(userId);
185
186 writeToDb(key, Long.toString(value), userId);
187 }
调用了writeToDb方法,建立一个读写数据库连接,将生成的盐值写入数据库。
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
360 private void writeToDb(String key, String value, int userId) {
361 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
362 }
363
364 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
365 ContentValues cv = new ContentValues();
366 cv.put(COLUMN_KEY, key);
367 cv.put(COLUMN_USERID, userId);
368 cv.put(COLUMN_VALUE, value);
369
370 db.beginTransaction();
371 try {
372 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
373 new String[] {key, Integer.toString(userId)});
374 db.insert(TABLE, null, cv);
375 db.setTransactionSuccessful();
376 } finally {
377 db.endTransaction();
378 }
379 }
查看locksettings文件的保存位置,两个key文件分别用来保存手势密码和输入密码。
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
72 private static final String SYSTEM_DIRECTORY = "/system/";
73 private static final String LOCK_PATTERN_FILE = "gesture.key";
74 private static final String LOCK_PASSWORD_FILE = "password.key";
使用adb pull /data/system/locksettings.db D:\xxx命令将文件导出,若报权限错误,将文件权限改为777,导出后使用SQLite查看。在第一张图中没有“lockscreen.password_salt“,在给手机设置输入密码后,数据表中出现”lockscreen.password_salt“。
再看密码比对逻辑,将密码hash后用checkPassword()函数比对
/* /frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java */
302 public boolean checkPassword(String password) {
303 final int userId = getCurrentOrCallingUserId();
304 try {
305 final boolean matched = getLockSettings().checkPassword(passwordToHash(password),
306 userId);
307 if (matched && (userId == UserHandle.USER_OWNER)) {
308 KeyStore.getInstance().password(password);
309 }
310 return matched;
311 } catch (RemoteException re) {
312 return true;
313 }
314 }
根据userId使用getLockPasswordFilename函数获取存储的密码,与传入的密码使用Arrays.equals进行比对
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
@Override
298 public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
299 checkPasswordReadPermission(userId);
300
301 try {
302 // Read all the bytes from the file
303 RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
304 final byte[] stored = new byte[(int) raf.length()];
305 int got = raf.read(stored, 0, stored.length);
306 raf.close();
307 if (got <= 0) {
308 return true;
309 }
310 // Compare the hash from the file with the entered password's hash
311 return Arrays.equals(stored, hash);
312 } catch (FileNotFoundException fnfe) {
313 Slog.e(TAG, "Cannot read file " + fnfe);
314 return true;
315 } catch (IOException ioe) {
316 Slog.e(TAG, "Cannot read file " + ioe);
317 return true;
318 }
319 }
使用getLockPasswordFilename从/SYSTEM_DIRECTORY/LOCK_PASSWORD_FILE,也就是/system/password.key获取存储的密码
/*/frameworks/base/services/java/com/android/server/LockSettingsService.java*/
233 private String getLockPasswordFilename(int userId) {
234 String dataSystemDirectory =
235 android.os.Environment.getDataDirectory().getAbsolutePath() +
236 SYSTEM_DIRECTORY;
237 if (userId == 0) {
238 // Leave it in the same place for user 0
239 return dataSystemDirectory + LOCK_PASSWORD_FILE;
240 } else {
241 return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
242 .getAbsolutePath();
243 }
244 }
使用uiautomatorviewer工具获取输入密码界面的View类,发现passwordEntry字段。
跟踪发现getPasswordTextViewId()将view赋给mPasswordEntry,再用getText()将值赋给entry,之后调用checkpassword函数检查。
/**/frameworks/base/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java/
66 @Override
67 protected int getPasswordTextViewId() {
68 return R.id.passwordEntry;
69 }
/*/frameworks/base/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java*/
98 mLockPatternUtils = new LockPatternUtils(mContext);
100 mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
101 mPasswordEntry.setOnEditorActionListener(this);
102 mPasswordEntry.addTextChangedListener(this);
/*/frameworks/base/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java*/
150 protected void verifyPasswordAndUnlock() {
151 String entry = mPasswordEntry.getText().toString();
152 if (mLockPatternUtils.checkPassword(entry)) {
153 mCallback.reportSuccessfulUnlockAttempt();
154 mCallback.dismiss(true);
155 } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) {
156 // to avoid accidental lockout, only count attempts that are long enough to be a
157 // real password. This may require some tweaking.
158 mCallback.reportFailedUnlockAttempt();
159 if (0 == (mCallback.getFailedAttempts()
160 % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
161 long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
162 handleAttemptLockout(deadline);
163 }
164 mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true);
165 }
166 mPasswordEntry.setText("");
167 }