日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android手势密码探索

發(fā)布時間:2024/1/8 Android 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android手势密码探索 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Android 智能手機在全球市場有著極高的市場占有率,越來越受到廣大消費者的青睞。但 Android 作為開源操作系統(tǒng),且很容易可以獲得系統(tǒng) root 權(quán)限,Android 系統(tǒng)的安全問題也是用戶和開發(fā)者最關(guān)心的問題之一。

手勢密碼作為手機上方便的一種安全保護措施,受到了眾多 APP 開發(fā)者的青睞,市場上一些金融類 APP 基本都配有手勢密碼,如下圖即為手勢繪制過程的一個狀態(tài)。

目前大多數(shù) Android 手機都具有手勢鎖屏功能,Android 系統(tǒng)自身是帶了手勢密碼功能的,不同的 ROM 廠商做了不同的定制。本文通過Android自身的源碼簡單介紹手勢密碼的原理。

Android手勢相關(guān)類

回憶或者嘗試一下用手勢解鎖 Android 手機的過程:首先用戶通過點擊九宮格的點連接出一條路徑,當手指抬起時,會判斷此次連接的點路徑是否和設(shè)置的相匹配。

在這個過程中,涉及到兩個方面(不考慮設(shè)置手勢時的存儲):

  • 手勢的繪制
  • 手勢的驗證/匹配

針對這兩個過程,通過 AOSP 查找源碼,我們可以發(fā)現(xiàn)兩個相關(guān)類:

  • LockPatternView.java:View類,九宮格手勢圖形顯示的類。
  • LockPatternUtils.java:手勢轉(zhuǎn)換、匹配工具類。

本篇文章通過分析這兩個類中重要的部分來說明手勢表示和繪制的原理。

LockPatternView

該類是 View 的子類,其中定義了整個手勢繪制區(qū)相關(guān)的 View,比如九宮格的點、繪制的路徑、View 的狀態(tài)模式、以及手勢監(jiān)聽等。類中覆寫了 View 父類的 onDraw 方法,點的選中狀態(tài)、繪制線條都是實時繪制的。

九宮格中的每個「宮」作為靜態(tài)內(nèi)部類定義為 Cell,每個 Cell 包括兩個坐標,即行(row)和列(column),row 和 column 的范圍均在 [0, 3) 內(nèi)。這樣定義的好處,一是利用矩陣的思想來表示九宮格,二是可以把「row 3 + column*」作為 Cell 的值,用 0~8 共 9 個數(shù)字來表示九宮格。比如,繪制的路徑是 「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 + ")";} }

手勢繪制過程中,一般有三種狀態(tài):繪制正確、正在繪制、繪制錯誤(實際開發(fā)可以設(shè)置為四種,第四種即鎖定狀態(tài))。

手勢九宮格用「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}

通過三種模式,可以更改繪制手勢過程中及結(jié)束后手勢狀態(tài)。比如,更改顏色以表示狀態(tài):讓繪制的過程中,選中的 Cell 和線條用藍色表示,繪制錯誤時用紅色表示,繪制正確時用綠色表示。

手勢繪制過程中通過接口OnPatternListener中的4個監(jiān)聽函數(shù)來監(jiān)聽手勢開始、結(jié)束、清除、添加等操作。接口的定義如下:

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 ,以及是否應(yīng)該把該 Cell 連接入手勢。這里需要了解幾個函數(shù):

  • 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);}

    函數(shù)代碼很好理解,mPatternDrawLookup 是個全局變量,同樣采用矩陣的形式,用于標記九宮格中哪個 Cell 被連接。從 checkForNewHit 中可以看出,已經(jīng)被連接的 Cell,是不會再被選中的,這也是目前手勢密碼普遍的做法。如果你需要實現(xiàn)“每個點可以被連接多次”的需求,這部分就需要改動了。

  • 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);}}}

這部分代碼比較長,這里就不細細分析了,主要流程就是:

  • 判斷當前顯示模式是否是正在繪制。如果是,保存連接的點的狀態(tài),計算手指當前所在的點坐標;如果不是,進入第2步。

  • 根據(jù)1中保存的狀態(tài),繪制選中的點,已更改選中的點的樣式。

    選中的點和未選中的點的狀態(tài)都是在這部分實時完成的,通過遍歷9個點,根據(jù)1中保存的狀態(tài)改變畫筆屬性繪制不同的樣式。

  • 繪制連接線(path)。主要是獲得路徑,然后drawPath。

  • 最后就是onTouchEvent處理手指ACTION事件,包括ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL事件。每種事件,判斷手勢繪制是否結(jié)束、改變顯示模式、刷新View、回調(diào)方法。

    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數(shù)字,轉(zhuǎn)換成byte數(shù)組來表示。

    • 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的基礎(chǔ)上,采用「SHA-1」算法對byte數(shù)組進行hash散列。

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

    也許Android的開發(fā)者也明白,Android作為開源系統(tǒng),無法做到真正意義上的絕對安全,除了每個人都能獲得源碼外,獲得系統(tǒng)root權(quán)限就能拿到系統(tǒng)所有數(shù)據(jù),因此并沒有花較大的力氣來處理手勢的安全問題。當然,這也是作者的猜想。

    實際開發(fā)中,需要根據(jù)APP及手勢需求的加密等級,對手勢信息進行不同程度的加密。如果需要存儲到本地,還涉及到數(shù)據(jù)的本地存儲安全。


    通過上面的簡單介紹,相信大家大致了解了手勢密碼的原理,上面分析的內(nèi)容主要是用戶可以修改的,即如果你需要自定義不同的手勢樣式,可以更改上面分析的對應(yīng)部分。

    我個人基于Android自己的LockPatternView進行了簡單的修改,繪制的樣式如文章開始的圖所示,修改的地方如要是drawCircle、圖層、畫筆。相關(guān)代碼可到y(tǒng)oungmeng/LockPatternView查看。

    總結(jié)

    以上是生活随笔為你收集整理的Android手势密码探索的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。