安卓圖形解鎖使用了 SHA1 加密演算法,這個信息是怎麼被知道的?
最近被密碼學折磨的不成樣子了,答題慾望不高… 看到這個自己曾經研究過的問題,就稍微回答一下。
不過有點不太明白,題主是想知道「如何獲知Android圖形解鎖使用了SHA1演算法」?,還是想知道「怎麼使用SHA1加密演算法實現Android圖形解鎖呢」?不過,無論哪個問題,都需要去找一下Android有關圖形解鎖的源代碼。
比較巧合的是,在2011年,也就是Android剛出來沒多久的時候(那時候我記得還是Android 2.3時代),為了在自己的項目中實現圖形解鎖,我去源代碼尋找並找到了圖形解鎖的源代碼,並且把這段代碼用在了自己的程序中。因此,在這裡我也重溫一下這段代碼,並解釋一下Android是如何實現的圖形解鎖。
如果只想了解Android圖形密碼的存儲和表示原理,可以只閱讀第1部分和第2部分。如果想獲得相關源代碼,請參考第4部分。如果想了解進一步的信息,歡迎和我進行知乎私信交流。
這個答案也給廣大Android用戶提個醒,最好不要把手機Root,否則第三方軟體很可能惡意修改你的圖形密碼(當然了,其他密碼也能清除和修改),這就帶來了嚴重的安全問題。這方面的討論放在第3部分。
==============================1. 有關Android圖形解鎖的Java類名稱和位置一共有4個Java文件涉及到Android圖形解鎖,均在package lockpattern下面。它們分別為:- LinearLayoutWithDefaultTouchRecepient.java。看到LinearLayout,就知道這是有關九宮格的布局類。
- Lists.java。這是存儲圖形密碼的數據結構。
- LockPatternUtils.java。有關圖形密碼的工具函數類。
- LockPatternView.java。看到View就知道這是圖形密碼在屏幕上顯示的相關類。
2. Android圖形密碼的表示與驗證
實際上,屏幕上面顯示的Android圖形密碼是Android操作系統即時繪製上去的。底層存儲不需要存那些線啊,點啊什麼的。我們知道,九宮格一共有9個點,如下圖所示:Cell(0, 0) Cell(0, 1) Cell(0, 2)
Cell(1, 0) Cell(1, 1) Cell(1, 2)
Cell(2, 0) Cell(2, 1) Cell(2, 2)
當用戶按照順序繪製好圖形密碼時,LockPatternView會按照用戶繪製的順序將Cell存放在Lists數據結構中。我們知道Lists是一個有序數據結構,這種數據結構會保留Cell的存入順序,表示了用戶的圖形密碼。
我們舉個例子,假設用戶按照如圖所示的順序輸入密碼:Cell(0, 0) -&> Cell(0, 1) -&> Cell(0, 2) -&> Cell(1, 1) -&> Cell(2, 0) -&> Cell(2, 1) -&> Cell(2, 2)
在使用圖形密碼時,LockPatternView同樣會按照用戶繪製的順序形成一個Cell的Lists。隨後,系統會把這個Lists和已經存儲的Lists進行對比。如果兩個Lists的信息一樣,意味著用戶輸入的密碼是正確的,解鎖;否則拒絕解鎖。
同樣舉個例子,如果用戶輸入的密碼也是按照上面的順序,那麼LockPatternView中生成的Lists也為:Cell(0, 0) -&> Cell(0, 1) -&> Cell(0, 2) -&> Cell(1, 1) -&> Cell(2, 0) -&> Cell(2, 1) -&> Cell(2, 2)
兩個一樣,即認為圖形密碼是正確的,允許解鎖。
==============================
2. Android圖形密碼的存儲我們自然而然會想到一個問題,Android圖形密碼如何存儲呢?如果把Lists直接存儲,我們知道即使Java中兩個類的內容完全一樣,如果還是兩個類,他們就不equals,無法進行對比。因此,如果直接存儲Lists,不管用戶怎麼輸入,正確也好錯誤也罷,系統會認為用戶輸入所生成的Lists與系統存儲的Lists是兩個不同的Class Instance,會認為他們不同。如何解決這個問題呢?前面已經知道,我們只需要存儲Cell的位置信息就可以了。也就是說,我們不需要存儲Cell(0, 0) -&> Cell(0, 1) -&> Cell(0, 2) -&> Cell(1, 1) -&> Cell(2, 0) -&> Cell(2, 1) -&> Cell(2, 2)
我們只需要存儲:
(0, 0) -&> (0, 1) -&> (0, 2) -&> (1, 1) -&> (2, 0) -&> (2, 1) -&> (2, 2)
LockPatternUtils使用的方法是這樣的:
- 將Lists轉化成一個String;
- 將String做SHA1運算;
- 存儲SHA1的結果;
Lists如何轉化成一個String呢?既然一共有9個點,我們就把這九個點編成0,1,2,3,4,5,6,7,8這8個號,每個點編號計算公式為:
cell.Row * 3 + cell.Column
然後,將編碼順序轉換成String進行存儲。
舉個例子,如果我們要存儲Cell(0, 0) -&> Cell(0, 1) -&> Cell(0, 2) -&> Cell(1, 1) -&> Cell(2, 0) -&> Cell(2, 1) -&> Cell(2, 2)
我們就按照上面的公式,把每一個Cell的編號轉換出來,即為:
0 -&> 1 -&> 2 -&> 4 -&> 6 -&> 7 -&> 8
然後轉換為String:
0124678
隨後,系統再調用SHA1函數,計算這個函數的SHA1值即可。
在驗證的時候,當獲得了用戶的圖形密碼輸入,LockPatternUtils也會進行同樣的轉化,得到String,然後計算SHA1結果,並把結果與存儲的結果進行對比。如果兩個值相同,那麼就認為用戶輸入的圖形密碼是正確的,允許解鎖。
所以我們可以知道,Android圖形密碼確實是用SHA1進行存儲的,而非存儲原始的密碼。這樣,當用戶手機丟失或者被惡意盜取後,攻擊者可以得到SHA1結果,但是依然無法反向推出用戶的圖形密碼。
==============================
3. 圖形密碼的存儲位置這裡我們要說明一下,圖形密碼存儲的File叫做sLockPatternFilename。是一個固定的位置。如果大家翻看源代碼的話,就知道這個位置也存放在LockPatternUtil中,存放的變數叫做LOCK_PATTERN_FILE(具體在哪裡我就不說啦,大家查查看)。這個File是一個系統文件。如果你將同樣的代碼在自己的程序中運行,那麼這個文件是無法寫入的,只能讀取。
這也說明了許可權管理的重要性,當然也說明了Android被Root的一個風險。如果手機被Root,那麼任意第三方軟體都可以通過修改許可權的方式使得自身程序可以訪問這個sLockPatternFilename文件。也就是說,第三方軟體可以隨時清除你的圖形密碼。剩下的我也就不用多說了吧…
==============================4. 相關源代碼4.1 Cell的定義Cell為內部類,定義在LockPatternView中。/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
public static class Cell {
int row; //表示Cell在第幾行
int column; //表示Cell在第幾列
// keep # objects limited to 9
static Cell[][] sCells = new Cell[3][3]; //九宮格,所以一共有3行3列共9個Cell
static {
for (int i = 0; i &< 3; i++) {
for (int j = 0; j &< 3; j++) {
sCells[i][j] = new Cell(i, j);
}
}
}
/**
* @param row The row of the cell.
* @param column The column of the cell.
*/
private Cell(int row, int column) {
checkRange(row, column);
this.row = row;
this.column = column;
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
/**
* @param row The row of the cell.
* @param column The column of the cell.
*/
public static synchronized Cell of(int row, int column) {
checkRange(row, column);
return sCells[row][column];
}
private static void checkRange(int row, int column) {
if (row &< 0 || row &> 2) {
throw new IllegalArgumentException("row must be in range 0-2");
}
if (column &< 0 || column &> 2) {
throw new IllegalArgumentException("column must be in range 0-2");
}
}
public String toString() {
return "(row=" + row + ",clmn=" + column + ")";
}
}
/**
* Provides static methods for creating {@code List} instances easily, and other
* utility methods for working with lists.
*/
public class Lists {
/**
* Creates an empty {@code ArrayList} instance.
*
* &
&Note:& if you only need an &immutable& empty List, use
* {@link Collections#emptyList} instead.
*
* @return a newly-created, initially-empty {@code ArrayList}
*/
public static &
return new ArrayList&
}
/**
* Creates a resizable {@code ArrayList} instance containing the given
* elements.
*
* &
&Note:& due to a bug in javac 1.5.0_06, we cannot support the
* following:
*
* &
{@code List&
*
* &
where {@code sub1} and {@code sub2} are references to subtypes of
* {@code Base}, not of {@code Base} itself. To get around this, you must
* use:
*
* &
{@code List&
*
* @param elements the elements that the list should contain, in order
* @return a newly-created {@code ArrayList} containing those elements
*/
public static &
int capacity = (elements.length * 110) / 100 + 5;
ArrayList&
Collections.addAll(list, elements);
return list;
}
}
4.3 Lists存儲相關函數
Lists存儲函數全部位於LockPatternUtil.java中。首先我們來看看將Lists轉換成String的函數。這個函數叫patternToString,實現的功能就是我在第2部分介紹的功能。/**
* Serialize a pattern.
* @param pattern The pattern.
* @return The pattern in string form.
*/
public static String patternToString(List&
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);
}
/*
* 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&
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 {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(res); //這裡就是SHA1的調用了
return hash;
} catch (NoSuchAlgorithmException nsa) {
return res;
}
}
/**
* Save a lock pattern.
* @param pattern The new pattern to save.
*/
public void saveLockPattern(List&
// Compute the hash
final byte[] hash = LockPatternUtils.patternToHash(pattern);
try {
// Write the hash to file
File file = new File(sLockPatternFilename);
if (!file.exists()){
file.createNewFile();
}
FileOutputStream fileStream = new FileOutputStream(file);
// Truncate the file if pattern is null, to clear the lock
DataOutputStream stream = new DataOutputStream(fileStream);
if (pattern == null) {
stream.writeInt(0);
} else {
stream.writeInt(hash.length);
stream.write(hash); //將SHA1的結果寫入文件
stream.flush();
}
fileStream.close();
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to find file " + sLockPatternFilename);
} catch (IOException ioe) {
// Cant do much
Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
}
}
/**
* 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&
try {
// Read all the bytes from the file
FileInputStream fileStream = new FileInputStream(sLockPatternFilename);
DataInputStream stream = new DataInputStream(fileStream);
int length = stream.readInt();
final byte[] stored = new byte[length];
int got = stream.read(stored, 0, length);
stream.close();
if (got &<= 0) {
return true;
}
// Compare the hash from the file with the entered pattern"s hash
// 檢查存儲的SHA1結果和用戶輸入轉換後的SHA1結果是否一致
return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern));
} catch (FileNotFoundException fnfe) {
return true;
} catch (IOException ioe) {
return true;
}
}
==============================
亂七八糟答了一大堆,以上。sha1嚴格的說不是加密演算法,加密是相對於解密而言,它只是一種哈希(散列)演算法,單向不可逆,給你一個sha1運算後的結果你很難得到原文。
這信息是Google公開在網上的
源代碼都在網上放著呢。。。。。
-------------------我像分割線嗎-----------------------------------------貼個Github上android 分析的圖吧,我只是大自然的搬運工。。用彩虹表暴力破解
源代碼
推薦閱讀:
※android中handle和線程的關係是什麼?
※helio x20和高通650哪個更強?
※為何安卓沒有整合Chrome?
※Android 下什麼中文字體最好?
※不能拆卸電池的的安卓手機死機了怎麼辦?