1.1 鎖屏密碼方式
Android 中現(xiàn)在支持的鎖屏密碼主要有兩種:一種是手勢解鎖呼伸,也就是常見的九宮格密碼圖;一種是輸入密碼搂根,分為PIN
密碼和復(fù)雜數(shù)字密碼,而PIN
密碼就是數(shù)字密碼铃辖,比較簡單剩愧。當(dāng)然現(xiàn)在還有一個高級的指紋密碼娇斩,這不是本文分析的范圍穴翩,本章只分析手勢密碼和輸入密碼锦积。
1.2 密碼算法分析
在設(shè)置鎖屏密碼界面丰介,用Android SDK目錄\tools\bin\uiautomatorviewer.bat
工具分析獲取當(dāng)前的鎖屏View
類LockPattern
,然后一步步跟入,最終會跟到一個包名為com.android.internal.widget
鎖屏密碼工具類:LockPatternUtils.java
带膀,如下圖橙垢,這里以9.0為例:
1.2.1 輸入密碼算法分析
找到源碼之后钢悲,首先來分析一下輸入密碼算法:
可以看到又一個方法
legacyPasswordToHash
,參數(shù)為用戶輸入的密碼和當(dāng)前用戶對應(yīng)的id,一般設(shè)備不會有多個用戶莺琳,所以這里的userId
是默認值0
,下面就是最為核心的加密算法:原文密碼+設(shè)備的salt
值载慈,然后分別進行MD5和SHA-1操作,然后復(fù)制到一個數(shù)組中辞做,再進行Hex編碼寡具,得到的值就是本地的加密密碼內(nèi)容童叠。而這里最重要的是如何獲取設(shè)備對應(yīng)的salt值,這可以一步一步跟蹤代碼:getsalt
方法五垮,首先根據(jù)字段key
為lockscreen.password_salt
從一個地方獲取salt
值放仗,如果發(fā)現(xiàn)這個值為0
,就調(diào)用SecureRandom.getInstance("SHA1PRNG").nextLong()
隨機生成一個撬碟,然后將其保存到一個地方莉撇,最后將salt
轉(zhuǎn)化為hex
值即可〖诠常現(xiàn)在需要找到這個地方达罗,繼續(xù)跟蹤代碼:感覺應(yīng)該保存到數(shù)據(jù)庫中粮揉,繼續(xù)跟蹤代碼;
通過在
ServiceManager
中獲取一個服務(wù)來進行操作扶认,在Android中,像這種獲取服務(wù)的方式狱从,最終實現(xiàn)邏輯都是在XXXService
類中叠纹,這里是在包名為com.android.server.locksettings
下的LockSettingsService.java
類誉察,找到這個類,查看它的getLong
方法這里調(diào)用了
getStringUnchecked
方法繼續(xù)跟蹤代碼:分析這個方法
- 第一個條件:
Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)
中,Settings.Secure.LOCK_PATTERN_ENABLED
為lock_pattern_autolock
key為lockscreen.password_salt
不相等 - 第二個條件:在
LockPatternUtils
中能看到USER_FRP
為UserHandle
中的USER_NULL
如下圖:
第二個條件也不滿足 - 第三個條件分別為:
key
在上面已經(jīng)說過為lockscreen.password_salt
,也不滿足
其實到這里就已經(jīng)可以看出肯定是保存在數(shù)據(jù)庫中的了鸿秆,繼續(xù)跟蹤代碼來驗證我們的猜想
看下LockSettingsService
這個類的構(gòu)造方法
用到了Injector
類卿叽,如下:
果然發(fā)現(xiàn)是保存在一個數(shù)據(jù)庫中附帽,繼續(xù)查看LockSettingsStorage
類
看到了數(shù)據(jù)庫名字叫作locksettings.db
蕉扮,那么它保存在哪里呢,繼續(xù)看這個類
可以看到有兩個key
文件屁使,他們就是用來保存加密之后的手勢密碼和輸入密碼的蛮寂,將信息保存到本地,下次開機解鎖需要讀取這個文件內(nèi)容進行密碼對比及老》蹲ィ看到一個目錄是system
,所以數(shù)據(jù)庫和這兩個key
文件很有可能保存到目錄/data/system/
下類僧鲁,為類再證實一下象泵,用find
命令去根目錄下搜索這個數(shù)據(jù)庫文件也是可以的偶惠。最終確定是該目錄:
由于中間輸出的其它目錄較多洲鸠,這里我就截取了有效的幾行
如果這里提示找不到
find
命令,這時需要安裝busybox
工具馋缅,才能使用這個命令。
找到這個數(shù)據(jù)庫文件就好辦了瘾腰,直接取出來覆履,然后用SQLite工具進行查看即可硝全,當(dāng)然也可以直接在手機中查看,為了方便析藕,還是取出來凳厢,如下圖:
首先給設(shè)備設(shè)置一個簡單的密碼,如
1234
治泥,然后會在/data/system
目錄下生成一個密碼加密key文件/data/system/password.key
.下面就用簡單的java代碼手動實現(xiàn)這個算法居夹,驗證分析是否正確,加密算法不用自己寫变屁,直接從上面的源碼中拷貝出來即可意狠。
private static final String SALT = Long.toHexString(7465477524050644896L);
public static String legacyPasswordToHash(String password, int userId) {
if (password == null) {
return null;
}
try {
byte[] saltedPassword = (password + SALT).getBytes();
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
byte[] combined = new byte[sha1.length + md5.length];
System.arraycopy(sha1, 0, combined, 0, sha1.length);
System.arraycopy(md5, 0, combined, sha1.length, md5.length);
final char[] hexEncoded = HexEncoding.encode(combined);
return new String(hexEncoded);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
}
}
HexEncoding.java
類如下:
/**
* Hexadecimal encoding where each byte is represented by two hexadecimal digits.
*/
public class HexEncoding {
/** Hidden constructor to prevent instantiation. */
private HexEncoding() {}
private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
/**
* Encodes the provided data as a sequence of hexadecimal characters.
*/
public static char[] encode(byte[] data) {
return encode(data, 0, data.length);
}
/**
* Encodes the provided data as a sequence of hexadecimal characters.
*/
public static char[] encode(byte[] data, int offset, int len) {
char[] result = new char[len * 2];
for (int i = 0; i < len; i++) {
byte b = data[offset + i];
int resultIndex = 2 * i;
result[resultIndex] = (HEX_DIGITS[(b >>> 4) & 0x0f]);
result[resultIndex + 1] = (HEX_DIGITS[b & 0x0f]);
}
return result;
}
/**
* Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
* is {@code true} odd-length inputs are allowed and the first character is interpreted
* as the lower bits of the first result byte.
*
* Throws an {@code IllegalArgumentException} if the input is malformed.
*/
public static byte[] decode(char[] encoded, boolean allowSingleChar) throws IllegalArgumentException {
int resultLengthBytes = (encoded.length + 1) / 2;
byte[] result = new byte[resultLengthBytes];
int resultOffset = 0;
int i = 0;
if (allowSingleChar) {
if ((encoded.length % 2) != 0) {
// Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
result[resultOffset++] = (byte) toDigit(encoded, i);
i++;
}
} else {
if ((encoded.length % 2) != 0) {
throw new IllegalArgumentException("Invalid input length: " + encoded.length);
}
}
for (int len = encoded.length; i < len; i += 2) {
result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
}
return result;
}
private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
// NOTE: that this isn't really a code point in the traditional sense, since we're
// just rejecting surrogate pairs outright.
int pseudoCodePoint = str[offset];
if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
return pseudoCodePoint - '0';
} else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
return 10 + (pseudoCodePoint - 'a');
} else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
return 10 + (pseudoCodePoint - 'A');
}
throw new IllegalArgumentException("Illegal char: " + str[offset] +
" at offset " + offset);
}
}
需要注意的是,這里的SALT
值是我們從數(shù)據(jù)庫中得到的遮晚,不過要記得進行hex
轉(zhuǎn)化
然后用1234
原文密碼去生成加密之后的信息拦止,結(jié)果如下:
得到的結(jié)果是:
1D8403875F321EE86FE44D545DC409C7A24584DE6415458CC8923D8832E8ED23A610E3FC
另外一種獲取SALT
的方法是利用反射機制汹族,代碼如下:
private void getSalt() {
try {
Class<?> classz = Class.forName("com.android.internal.widget.LockPatternUtils");
Object locakUtils = classz.getConstructor(Context.class).newInstance(this);
Class<?> locakPatternUtils = locakUtils.getClass();
Method getSaltM = locakPatternUtils.getDeclaredMethod("getsalt",int.class);
getSaltM.setAccessible(true);
Object saltObj = getSaltM.invoke(locakUtils,0);
Log.i("sallt","salt:"+saltObj);
} catch (Exception e) {
Log.i("ZQ","error:"+Log.getStackTraceString(e));
}
}
到這里就分析完了鎖屏密碼算法,總結(jié)一點就是:md5(輸入明文密碼+設(shè)備的salt的值) + SHA1(輸入明文密碼+設(shè)備的salt值)夸政,拷貝到一個數(shù)組中后對數(shù)組進行HexEncoding.encode
運算榴徐,轉(zhuǎn)成String
就是最終加密后的內(nèi)容坑资,手勢密碼算法分析,請看下一個篇袱贮。