Android锁屏密码算法分析

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    }
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇