1. Android中鎖屏密碼加密算法分析

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)前的鎖屏ViewLockPattern,然后一步步跟入,最終會跟到一個包名為com.android.internal.widget鎖屏密碼工具類:LockPatternUtils.java带膀,如下圖橙垢,這里以9.0為例:

image.png

1.2.1 輸入密碼算法分析

找到源碼之后钢悲,首先來分析一下輸入密碼算法:

legacyPasswordToHash

可以看到又一個方法legacyPasswordToHash,參數(shù)為用戶輸入的密碼和當(dāng)前用戶對應(yīng)的id,一般設(shè)備不會有多個用戶莺琳,所以這里的userId是默認值0,下面就是最為核心的加密算法:原文密碼+設(shè)備的salt值载慈,然后分別進行MD5和SHA-1操作,然后復(fù)制到一個數(shù)組中辞做,再進行Hex編碼寡具,得到的值就是本地的加密密碼內(nèi)容童叠。而這里最重要的是如何獲取設(shè)備對應(yīng)的salt值,這可以一步一步跟蹤代碼:
salt
查看getsalt方法五垮,首先根據(jù)字段keylockscreen.password_salt從一個地方獲取salt值放仗,如果發(fā)現(xiàn)這個值為0,就調(diào)用SecureRandom.getInstance("SHA1PRNG").nextLong()隨機生成一個撬碟,然后將其保存到一個地方莉撇,最后將salt轉(zhuǎn)化為hex值即可〖诠常現(xiàn)在需要找到這個地方达罗,繼續(xù)跟蹤代碼:
getLong

感覺應(yīng)該保存到數(shù)據(jù)庫中粮揉,繼續(xù)跟蹤代碼;
getLockSettings

通過在ServiceManager中獲取一個服務(wù)來進行操作扶认,在Android中,像這種獲取服務(wù)的方式狱从,最終實現(xiàn)邏輯都是在XXXService類中叠纹,這里是在包名為com.android.server.locksettings下的LockSettingsService.java類誉察,找到這個類,查看它的getLong方法
LockSettingsService-----getLong

這里調(diào)用了getStringUnchecked方法繼續(xù)跟蹤代碼:
getStringUnchecked

分析這個方法

  • 第一個條件:Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)中,Settings.Secure.LOCK_PATTERN_ENABLEDlock_pattern_autolockkey為lockscreen.password_salt不相等
  • 第二個條件:在LockPatternUtils中能看到USER_FRP
    USER_FRP
    UserHandle中的USER_NULL如下圖:
    UserHandle-USER_NULL

    第二個條件也不滿足
  • 第三個條件分別為:
    image.png
    ,key在上面已經(jīng)說過為lockscreen.password_salt,也不滿足
    其實到這里就已經(jīng)可以看出肯定是保存在數(shù)據(jù)庫中的了鸿秆,繼續(xù)跟蹤代碼來驗證我們的猜想
    看下LockSettingsService這個類的構(gòu)造方法
    image.png

    用到了Injector類卿叽,如下:
    Injector

    果然發(fā)現(xiàn)是保存在一個數(shù)據(jù)庫中附帽,繼續(xù)查看LockSettingsStorage
    LockSettingsStorage

    LockSettingsStorage-readKeyValue

    DatabaseHelper

    看到了數(shù)據(jù)庫名字叫作locksettings.db蕉扮,那么它保存在哪里呢,繼續(xù)看這個類
    image.png

    可以看到有兩個key文件屁使,他們就是用來保存加密之后的手勢密碼和輸入密碼的蛮寂,將信息保存到本地,下次開機解鎖需要讀取這個文件內(nèi)容進行密碼對比及老》蹲ィ看到一個目錄是system,所以數(shù)據(jù)庫和這兩個key文件很有可能保存到目錄/data/system/下類僧鲁,為類再證實一下象泵,用find命令去根目錄下搜索這個數(shù)據(jù)庫文件也是可以的偶惠。最終確定是該目錄:
    image.png

    image.png

    由于中間輸出的其它目錄較多洲鸠,這里我就截取了有效的幾行

如果這里提示找不到find命令,這時需要安裝busybox工具馋缅,才能使用這個命令。

找到這個數(shù)據(jù)庫文件就好辦了瘾腰,直接取出來覆履,然后用SQLite工具進行查看即可硝全,當(dāng)然也可以直接在手機中查看,為了方便析藕,還是取出來凳厢,如下圖:

數(shù)據(jù)庫信息

首先給設(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é)果如下:

image.png

得到的結(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)容坑资,手勢密碼算法分析,請看下一個篇袱贮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市狡赐,隨后出現(xiàn)的幾起案子钦幔,更是在濱河造成了極大的恐慌,老刑警劉巖搀擂,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哨颂,死亡現(xiàn)場離奇詭異相种,居然都是意外死亡,警方通過查閱死者的電腦和手機箫措,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門斤蔓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镀岛,“玉大人,你說我怎么就攤上這事驾锰〔τ耄” “怎么了艾猜?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵匆赃,是天一觀的道長。 經(jīng)常有香客問我低淡,道長,這世上最難降的妖魔是什么何荚? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任餐塘,我火速辦了婚禮皂吮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘需纳。我一直安慰自己艺挪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布慌盯。 她就那樣靜靜地躺著亚皂,像睡著了一般国瓮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上禁漓,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天孵睬,我揣著相機與錄音掰读,去河邊找鬼。 笑死蹈集,一個胖子當(dāng)著我的面吹牛拢肆,可吹牛的內(nèi)容都是我干的靖诗。 我是一名探鬼主播支示,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼颂鸿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绞愚?” 一聲冷哼從身側(cè)響起颖医,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤熔萧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贮缕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俺榆,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡罐脊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宵溅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片上炎。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡藕施,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出润绵,到底是詐尸還是另有隱情胞谈,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站径密,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏底桂。R本人自食惡果不足惜惧眠,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一氛魁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捶码,春花似錦或链、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旬蟋。三九已至,卻和暖如春冕碟,著一層夾襖步出監(jiān)牢的瞬間匆浙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工挑庶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迎捺,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓抄沮,卻偏偏與公主長得像岖瑰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子率挣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容