Android 智能手机在全球市场有着极高的市场占有率,越来越受到广大消费者的青睐。但 Android 作为开源操作系统,且很容易可以获得系统 root 权限,Android 系统的安全问题也是用户和开发者最关心的问题之一。

手势密码作为手机上方便的一种安全保护措施,受到了众多 APP 开发者的青睐,市场上一些金融类 APP 基本都配有手势密码,如下图即为手势绘制过程的一个状态。

目前大多数 Android 手机都具有手势锁屏功能,Android 系统自身是带了手势密码功能的,不同的 ROM 厂商做了不同的定制。本文通过Android自身的源码简单介绍手势密码的原理。

Android手势相关类

回忆或者尝试一下用手势解锁 Android 手机的过程:首先用户通过点击九宫格的点连接出一条路径,当手指抬起时,会判断此次连接的点路径是否和设置的相匹配。

在这个过程中,涉及到两个方面(不考虑设置手势时的存储):

  • 手势的绘制
  • 手势的验证/匹配

针对这两个过程,通过 AOSP 查找源码,我们可以发现两个相关类:

  • LockPatternView.java:View类,九宫格手势图形显示的类。
  • LockPatternUtils.java:手势转换、匹配工具类。

本篇文章通过分析这两个类中重要的部分来说明手势表示和绘制的原理。

LockPatternView

该类是 View 的子类,其中定义了整个手势绘制区相关的 View,比如九宫格的点、绘制的路径、View 的状态模式、以及手势监听等。类中覆写了 View 父类的 onDraw 方法,点的选中状态、绘制线条都是实时绘制的。

九宫格中的每个「宫」作为静态内部类定义为 Cell,每个 Cell 包括两个坐标,即行(row)和列(column),row 和 column 的范围均在 [0, 3) 内。这样定义的好处,一是利用矩阵的思想来表示九宫格,二是可以把「row 3 + column*」作为 Cell 的值,用 0~8 共 9 个数字来表示九宫格。比如,绘制的路径是 「L」 型,就可以用「03678」来表示这个路径。

public static final class Cell {final int row;final int column;// keep # objects limited to 9private static final Cell[][] sCells = createCells();private static Cell[][] createCells() {Cell[][] res = new Cell[3][3];for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {res[i][j] = new Cell(i, j);}}return res;}/*** @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;}public static 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");}}@Overridepublic String toString() {return "(row=" + row + ",clmn=" + column + ")";}
}

手势绘制过程中,一般有三种状态:绘制正确、正在绘制、绘制错误(实际开发可以设置为四种,第四种即锁定状态)。

手势九宫格用「DisplayMode」表示三种显示模式:

public enum DisplayMode {/*** The pattern drawn is correct (i.e draw it in a friendly color)*/Correct,/*** Animate the pattern (for demo, and help).*/Animate,/*** The pattern is wrong (i.e draw a foreboding color)*/Wrong}

通过三种模式,可以更改绘制手势过程中及结束后手势状态。比如,更改颜色以表示状态:让绘制的过程中,选中的 Cell 和线条用蓝色表示,绘制错误时用红色表示,绘制正确时用绿色表示。

手势绘制过程中通过接口OnPatternListener中的4个监听函数来监听手势开始、结束、清除、添加等操作。接口的定义如下:

public static interface OnPatternListener {/*** A new pattern has begun.*/void onPatternStart();/*** The pattern was cleared.*/void onPatternCleared();/*** The user extended the pattern currently being drawn by one cell.** @param pattern The pattern with newly added cell.*/void onPatternCellAdded(List<Cell> pattern);/*** A pattern was detected from the user.** @param pattern The pattern.*/void onPatternDetected(List<Cell> pattern);}

从方法名和注释就可以看出每个方法的含义,在此不再赘述。

接下来看下,手势在绘制手势的过程中,View是如何判断手指当前位置是否选中某个 Cell ,以及是否应该把该 Cell 连接入手势。这里需要了解几个函数:

  • getRowHit ( float y )

    用来确定手指当前坐标 (x, y) 位于九宫格的第几

  • getColumnHit (float x )

    用来确定手指当前坐标 (x, y) 位于九宫格的第几

  • checkForNewHit (float x, float y)

    private Cell checkForNewHit(float x, float y) {final int rowHit = getRowHit(y);if (rowHit < 0) {return null;}final int columnHit = getColumnHit(x);if (columnHit < 0) {return null;}if (mPatternDrawLookup[rowHit][columnHit]) {return null;}return Cell.of(rowHit, columnHit);}

    函数代码很好理解,mPatternDrawLookup 是个全局变量,同样采用矩阵的形式,用于标记九宫格中哪个 Cell 被连接。从 checkForNewHit 中可以看出,已经被连接的 Cell,是不会再被选中的,这也是目前手势密码普遍的做法。如果你需要实现“每个点可以被连接多次”的需求,这部分就需要改动了。

  • detectAndAddHit (float x, float y)

    用来检测并判断手指当前坐标 (x, y) 是否需要添加添加进当前手势中。

    private Cell detectAndAddHit(float x, float y) {final Cell cell = checkForNewHit(x, y);if (cell != null) {// check for gaps in existing patternCell fillInGapCell = null;final ArrayList<Cell> pattern = mPattern;if (!pattern.isEmpty()) {final Cell lastCell = pattern.get(pattern.size() - 1);int dRow = cell.row - lastCell.row;int dColumn = cell.column - lastCell.column;int fillInRow = lastCell.row;int fillInColumn = lastCell.column;if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);}if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);}fillInGapCell = Cell.of(fillInRow, fillInColumn);}if (fillInGapCell != null &&!mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {addCellToPattern(fillInGapCell);}addCellToPattern(cell);if (mEnableHapticFeedback) {performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);}return cell;}return null;}

    首先通过 checkForNewHit 获得当前位置的的 Cell,计算当前Cell 与手势中最后一个 Cell 的行列差值。看其中一段代码

    if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
    }if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);
    }fillInGapCell = Cell.of(fillInRow, fillInColumn);

    判断条件是:当前 Cell 与手势中最后一个 Cell 的或者的绝对差值为 2,且其的绝对差值不为1,即两个 Cell 不相邻(包括水平、竖直、45°方向的相邻),获得当前 Cell 与手势中最后一个 Cell 之间的 Cell,如果该 Cell 没有被添加进去过,则添加进手势。

    意思就是说,绘制的手势不会跨过没有添加的点。


前面说到,绘制过程中选中的点和未选中的点是通过覆写 View 的 onDraw 方法实时绘制的。onDraw代码如下:

@Overrideprotected void onDraw(Canvas canvas) {final ArrayList<Cell> pattern = mPattern;final int count = pattern.size();final boolean[][] drawLookup = mPatternDrawLookup;if (mPatternDisplayMode == DisplayMode.Animate) {// figure out which circles to draw// + 1 so we pause on complete patternfinal int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING;final int spotInCycle = (int) (SystemClock.elapsedRealtime() -mAnimatingPeriodStart) % oneCycle;final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;clearPatternDrawLookup();for (int i = 0; i < numCircles; i++) {final Cell cell = pattern.get(i);drawLookup[cell.getRow()][cell.getColumn()] = true;}// figure out in progress portion of ghosting linefinal boolean needToUpdateInProgressPoint = numCircles > 0&& numCircles < count;if (needToUpdateInProgressPoint) {final float percentageOfNextCircle =((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) /MILLIS_PER_CIRCLE_ANIMATING;final Cell currentCell = pattern.get(numCircles - 1);final float centerX = getCenterXForColumn(currentCell.column);final float centerY = getCenterYForRow(currentCell.row);final Cell nextCell = pattern.get(numCircles);final float dx = percentageOfNextCircle *(getCenterXForColumn(nextCell.column) - centerX);final float dy = percentageOfNextCircle *(getCenterYForRow(nextCell.row) - centerY);mInProgressX = centerX + dx;mInProgressY = centerY + dy;}// TODO: Infinite loop here...invalidate();}final Path currentPath = mCurrentPath;currentPath.rewind();// draw the circlesfor (int i = 0; i < 3; i++) {float centerY = getCenterYForRow(i);for (int j = 0; j < 3; j++) {CellState cellState = mCellStates[i][j];float centerX = getCenterXForColumn(j);float translationY = cellState.translationY;if (isHardwareAccelerated() && cellState.hwAnimating) {DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;displayListCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,cellState.hwRadius, cellState.hwPaint);} else {drawCircle(canvas, (int) centerX, (int) centerY + translationY,cellState.radius, drawLookup[i][j], cellState.alpha);}}}// TODO: the path should be created and cached every time we hit-detect a cell// only the last segment of the path should be computed here// draw the path of the pattern (unless we are in stealth mode)final boolean drawPath = !mInStealthMode;if (drawPath) {mPathPaint.setColor(getCurrentColor(true /* partOfPattern */));boolean anyCircles = false;float lastX = 0f;float lastY = 0f;for (int i = 0; i < count; i++) {Cell cell = pattern.get(i);// only draw the part of the pattern stored in// the lookup table (this is only different in the case// of animation).if (!drawLookup[cell.row][cell.column]) {break;}anyCircles = true;float centerX = getCenterXForColumn(cell.column);float centerY = getCenterYForRow(cell.row);if (i != 0) {CellState state = mCellStates[cell.row][cell.column];currentPath.rewind();currentPath.moveTo(lastX, lastY);if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) {currentPath.lineTo(state.lineEndX, state.lineEndY);} else {currentPath.lineTo(centerX, centerY);}canvas.drawPath(currentPath, mPathPaint);}lastX = centerX;lastY = centerY;}// draw last in progress sectionif ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate)&& anyCircles) {currentPath.rewind();currentPath.moveTo(lastX, lastY);currentPath.lineTo(mInProgressX, mInProgressY);mPathPaint.setAlpha((int) (calculateLastSegmentAlpha(mInProgressX, mInProgressY, lastX, lastY) * 255f));canvas.drawPath(currentPath, mPathPaint);}}}

这部分代码比较长,这里就不细细分析了,主要流程就是:

  1. 判断当前显示模式是否是正在绘制。如果是,保存连接的点的状态,计算手指当前所在的点坐标;如果不是,进入第2步。

  2. 根据1中保存的状态,绘制选中的点,已更改选中的点的样式。

    选中的点和未选中的点的状态都是在这部分实时完成的,通过遍历9个点,根据1中保存的状态改变画笔属性绘制不同的样式。

  3. 绘制连接线(path)。主要是获得路径,然后drawPath。

最后就是onTouchEvent处理手指ACTION事件,包括ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL事件。每种事件,判断手势绘制是否结束、改变显示模式、刷新View、回调方法。

LockPatternUtils

LockPatternUtils是处理手势的工具类,主要看下两个方法patternToString、patternToHash两个方法。

  • patternToString
/*** Serialize a pattern.* @param pattern The pattern.* @return The pattern in string form.*/public static String patternToString(List<LockPatternView.Cell> pattern) {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);}

从方法定义可以看到,将手势用0~8数字,转换成byte数组来表示。

  • patternToHash
/** 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.*/public static byte[] patternToHash(List<LockPatternView.Cell> pattern) {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 {MessageDigest md = MessageDigest.getInstance("SHA-1");byte[] hash = md.digest(res);return hash;} catch (NoSuchAlgorithmException nsa) {return res;}}

patternToHash的作用是,在patternToString的基础上,采用「SHA-1」算法对byte数组进行hash散列。

值得一提的是,SHA-1虽然不可逆,但算法并不安全。如果采用暴力破解的方式,自己写个程序很快就能撞对。

也许Android的开发者也明白,Android作为开源系统,无法做到真正意义上的绝对安全,除了每个人都能获得源码外,获得系统root权限就能拿到系统所有数据,因此并没有花较大的力气来处理手势的安全问题。当然,这也是作者的猜想。

实际开发中,需要根据APP及手势需求的加密等级,对手势信息进行不同程度的加密。如果需要存储到本地,还涉及到数据的本地存储安全。


通过上面的简单介绍,相信大家大致了解了手势密码的原理,上面分析的内容主要是用户可以修改的,即如果你需要自定义不同的手势样式,可以更改上面分析的对应部分。

我个人基于Android自己的LockPatternView进行了简单的修改,绘制的样式如文章开始的图所示,修改的地方如要是drawCircle、图层、画笔。相关代码可到youngmeng/LockPatternView查看。

Android手势密码探索相关推荐

  1. android手势密码源码,Android自定义UI手势密码改进版源码下载

    在之前文章的铺垫下,再为大家分享一篇:Android手势密码,附源码下载,不要错过. 先看第一张图片的布局文件 activity_main.xml xmlns:tools="http://s ...

  2. Android手势密码view笔记(一)

    前言:不知不觉已经在这座陌生又熟悉的城市呆了一年多了,说不出什么感觉,可是即使是自己感觉自己没什么变化,但是周围的事物却不断的在变,不知道自己选择的路未来如何,但是当下我还是会努力.努力.再努力的,加 ...

  3. android手势密码csdn,Android手势密码LockPatternView、LockPasswordUtils、LockPatternUtils等分析...

    Android手势密码LockPatternView.LockPasswordUtils.LockPatternUtils 在使用别人写的这个手势密码的时候,我们通常是有自己的需求,可能这里的代码很多 ...

  4. Android 手势密码

    最近看了慕课网一老师的视频,关于手势密码的研究,挺不错的,不过没上传源码,还有就是旋转角度的计算个人感觉不太好,于是整理出源代码如下: import java.util.ArrayList; impo ...

  5. android 手势密码逻辑,[Android开发实战]Android手势密码(支付宝手势密码)实现(支持2.x)...

    原创文章,转载请注明出处:http://blog.csdn.net/ruils/article/details/17081207 在很多安全性比较高的应用程序中,每次打开程序,都会有让用户输入密码,这 ...

  6. android 手势密码功能sdk,利用ActivityLifecycleCallBack监控app前后台状态切换,实现手势密码即九宫格解锁...

    转载注明出处:http://blog..net/coderder/article/details/51063493 利用ActivityLifecycleCallbacks监控app前后台状态切换,实 ...

  7. 招财进宝手势锁,Android手势密码的实现

    这几个月都是在做招财进宝项目,招财进宝是盛大网络旗下,盛付通支付服务有限公司最新推出的,一款高收益低风险的理财APP,有兴趣的可以下载玩玩,收益不错哦!!! 招财进宝下载地址:http://8.she ...

  8. android手势第一次设置密码_[Android开发实战]Android手势密码(支付宝手势密码)实现(支持2.x) | 学步园...

    在很多安全性比较高的应用程序中,每次打开程序,都会有让用户输入密码,这些安全性比较高的程序,密码也会相对复杂,在手机上输入起来,就会不大方便,至少要切换一次输入法,对用户体验造成不好的影响,在移动互联 ...

  9. Android手势密码

    一.简介 1.本篇博文给大家介绍手势密码的绘制,首先看一下效果,如下:分为设置手势密码与验证手势密码: 二.结构分析: 如上图所示,我们在创建View之前,需将基本思路理清,我大致介绍一下我的理解: ...

最新文章

  1. wamp配置虚拟域名
  2. pythonweb开发-Python Web开发从入门到精通
  3. Linux 链接详解----动态链接库
  4. 项目管理杂感(2)──继续说项目管理的目标
  5. 程序员关机代码python_Python实现远程开关机【高薪必备技术】
  6. 快速构建Windows 8风格应用33-构建锁屏提醒
  7. 【渝粤教育】 国家开放大学2020年春季 1050金融理论前沿课题 参考试题
  8. Java集合框架练习-计算表达式的值
  9. Java自动化测试框架-11 - TestNG之annotation与并发测试篇 (详细教程)
  10. html中div的居中
  11. 使用netsh interface ip set 命令实现快速切换IP地址及DNS地址
  12. 学习 Hybrid Beamforming for Millimeter Wave Systems Using the MMSE Criterion
  13. 知乎App产品体检报告
  14. Word文档生成神器:开源项目poi.tl使用介绍
  15. 10个我经常逛的“小网站”
  16. 中南大学计算机学院张伟,张伟(数学家)_百度百科
  17. scp:Mac使用方法(文件上传与下载服务器)
  18. Alibaba大牛常读的10本Java实战书籍,(Java开发进阶必备书单),可以白嫖了
  19. 网工必须了解的华为华三设备基础命令行与WEB界面
  20. 【macOS自带VNC远程】——Windows在外远程桌面控制macOS

热门文章

  1. wap端使用a标签跳转不刷新问题
  2. 那些年,我开发过的软件
  3. Python软件的下载安装教程
  4. 2019领克车展 Max Co币机诞生记
  5. 图像融合论文及代码网址整理总结(1)——多聚焦图像融合
  6. mysql ansi unicode_ANSI与Unicode编码
  7. 关于Java八种原始数据类型
  8. 将多个EXCEL表中部分固定单元格的内容整理提取到一张工作表中
  9. android 加花工具下载,Android 代码混淆并加花
  10. 终于把泰山OFFICE的MAC本地库问题都解决了