在 iOS 開發中?Keychain 是一個非常安全的存儲系統,用于保存敏感信息,如密碼、證書、密鑰等。與文件系統不同,Keychain 提供了更高的安全性,因為它對數據進行了加密,并且只有經過授權的應用程序才能訪問存儲的數據。那么在鴻蒙里面對應的是什么呢?
1、關鍵資產(@ohos.security.asset)
在鴻蒙里面也有類似的東西,叫做關鍵資產(@ohos.security.asset
),關鍵資產存儲服務提供了用戶短敏感數據的安全存儲及管理能力。其中,短敏感數據可以是密碼類(賬號/密碼)、Token類(應用憑據)、其他關鍵明文(如銀行卡號)等長度較短的用戶敏感數據。
關鍵資產的安全存儲,依賴底層的通用密鑰庫系統。具體來說,關鍵資產的加/解密操作以及訪問控制校驗,都由通用密鑰庫系統在安全環境(如可信執行環境)中完成,即使系統被攻破,也能保證用戶敏感數據不發生泄露。其中,關鍵資產的加/解密使用AES256-GCM算法。
2、關鍵資產使用與 asset 的常用操作
關鍵資產的訪問可分為 4 類(可查看本文第4章節),基于屬主的訪問控制、基于鎖屏狀態的訪問控制、基于鎖屏密碼設置狀態的訪問控制、基于用戶認證的訪問控制,業務可根據實際情況決定是否開啟(如掃臉驗證、解鎖驗證、密碼驗證等),本次文章舉例的開發案例均采用默認保護等級。
?2.1?使用關鍵資產需要導入模塊?AssetStoreKit:
import { asset } from '@kit.AssetStoreKit';
2.2 常用操作:
方法 | 描述 |
---|---|
asset.add | 新增一條關鍵資產。 |
asset.remove | 刪除符合條件的一條或多條關鍵資產。 |
asset.update | 更新符合條件的一條關鍵資產。 |
asset.query | 查詢一條或多條符合條件的關鍵資產。若查詢需要用戶認證的關鍵資產,則需要在本函數前調用asset.preQuery,在本函數后調用asset.postQuery。 |
asset.preQuery | 查詢的預處理,用于需要用戶認證的關鍵資產。在用戶認證成功后,應當隨后調用asset.query、asset.postQuery。 |
asset.postQuery | 查詢的后置處理,用于需要用戶認證的關鍵資產。需與asset.preQuery函數成對出現。 |
3、常規方法封裝
3.1 addSync 設置數據
/*** 新增一條關鍵資產* @param key* @param value* @returns*/add(key: string, value: string) {let result: Booleanlet attr: asset.AssetMap = new Map();// 關鍵資產別名,每條關鍵資產的唯一索引。// 類型為Uint8Array,長度為1-256字節。attr.set(asset.Tag.SECRET, this.string2Array(value));// 關鍵資產明文。// 類型為Uint8Array,長度為1-1024字節attr.set(asset.Tag.ALIAS, this.string2Array(key))// 關鍵資產同步類型>THIS_DEVICE只在本設備進行同步,如僅在本設備還原的備份場景。attr.set(asset.Tag.SYNC_TYPE, asset.SyncType.THIS_DEVICE);//枚舉,新增關鍵資產時的沖突(如:別名相同)處理策略。OVERWRITE》拋出異常,由業務進行后續處理。// attr.set(asset.Tag.CONFLICT_RESOLUTION,asset.ConflictResolution.THROW_ERROR)// 在應用卸載時是否需要保留關鍵資產。// 需要權限: ohos.permission.STORE_PERSISTENT_DATA。// 類型為bool。// attr.set(asset.Tag.IS_PERSISTENT, true);//我項目里面沒有使用就先注釋了,后續有需要這個再打開,并且要設置對應權限if (this.isHasKey(key)) {result = this.updateAssetMap(attr);} else {try {if (canIUse("SystemCapability.Security.Asset")) {asset.addSync(attr);result = true}result = false} catch (error) {let err = error as BusinessError;LogUtil.e(`Failed to add Asset. Code is ${err.code}, message is ${err.message}`);result = false}}return result}
3.2?querySync 獲取數據
/*** 查詢* @param key* @returns*/query(key: string) {let query: asset.AssetMap = new Map();// 關鍵資產別名,每條關鍵資產的唯一索引。// 類型為Uint8Array,長度為1-256字節。query.set(asset.Tag.ALIAS, this.string2Array(key));// 關鍵資產查詢返回的結果類型。query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL);// query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ATTRIBUTES); // 此處表示僅返回關鍵資產屬性,不包含關鍵資產明文try {if (canIUse("SystemCapability.Security.Asset")) {let res: Array<asset.AssetMap> = asset.querySync(query);for (let i = 0; i < res.length; i++) {// parse the attribute.if (res[i] != null) {// parse the secret.let secret: Uint8Array = res[0].get(asset.Tag.SECRET) as Uint8Array;// parse uint8array to stringlet secretStr: string = this.array2String(secret);return secretStr;}}}} catch (error) {let err = error as BusinessError;LogUtil.e(TAG, `Failed to query Asset. Code is ${err.code}, message is ${err.message}`);return "";}return ""}
3.3?querySync 查詢 key 數據是否存在
/*** 查詢資產key是否存在* @param key* @returns*/isHasKey(key: string): Boolean {if (canIUse("SystemCapability.Security.Asset")) {let query: asset.AssetMap = new Map();// 關鍵資產別名,每條關鍵資產的唯一索引。// 類型為Uint8Array,長度為1-256字節。query.set(asset.Tag.ALIAS, this.string2Array(key));// 關鍵資產查詢返回的結果類型。query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL);const res = this.queryAssetMap(query);if (!res || res.length < 1) {return false;}return true;}return false;}
/*** 查詢資產 key 的 assetMaps 數據* @param query* @returns*/queryAssetMap(query: asset.AssetMap): Array<asset.AssetMap> {const assetMaps: asset.AssetMap[] = [];try {if (canIUse("SystemCapability.Security.Asset")) {const res: asset.AssetMap[] = asset.querySync(query);return res;}return assetMaps;} catch (error) {const err = error as BusinessError;LogUtil.e(TAG, `Failed to query Asset. Code is ${err.code}, message is ${err.message}`);return assetMaps;}}
3.4 updateSync 修改資產數據
/*** 修改資產數據* @param q* @returns*/updateAssetMap(q: asset.AssetMap): Boolean {try {if (canIUse("SystemCapability.Security.Asset")) {let query: asset.AssetMap = new Map();query.set(asset.Tag.ALIAS, q.get(asset.Tag.ALIAS)!);let attrsToUpdate: asset.AssetMap = new Map();attrsToUpdate.set(asset.Tag.SECRET, q.get(asset.Tag.SECRET)!);asset.updateSync(query, attrsToUpdate);return true}return false} catch (error) {const err = error as BusinessError;LogUtil.e(TAG, `Failed to update Asset. Code is ${err.code}, message is ${err.message}`);return false;}}
3.5 removeSync 刪除資產數據
/*** 刪除一條關鍵資產* @param key*/remove(key: string) {let query: asset.AssetMap = new Map();// 關鍵資產別名,每條關鍵資產的唯一索引。query.set(asset.Tag.ALIAS, this.string2Array(key));try {if (canIUse("SystemCapability.Security.Asset")) {asset.removeSync(query);return true;}return false;} catch (error) {let err = error as BusinessError;LogUtil.e(TAG, `Failed to remove Asset. Code is ${err.code}, message is ${err.message}`);return false;}}
3.6 string 與?Uint8Array 數據互轉換
string2Array(str: string): Uint8Array {let textEncoder = new util.TextEncoder();return textEncoder.encodeInto(str);}array2String(str: Uint8Array): string {let textDecoder = new util.TextDecoder();return textDecoder.decodeToString(str);}
4、關鍵資產的訪問控制
-
基于屬主的訪問控制:?所有的關鍵資產都受屬主訪問控制保護,業務無需設置。
- 只允許關鍵資產被其屬主(寫入該關鍵資產的業務)訪問。
- 關鍵資產屬主身份由ASSET從系統服務中獲取,即使業務身份被仿冒,仿冒者也無法獲取到其他業務的數據。
- 關鍵資產加/解密時,其屬主身份參與了完整性保護,即使關鍵資產屬主身份被篡改,攻擊者也無法獲取到其他業務的數據。
-
基于鎖屏狀態的訪問控制:?分為以下三種保護等級(安全性依次遞增),業務可根據實際情況設置任意一種,若不設置,則默認保護等級為“首次解鎖后可訪問”。
- 開機后可訪問:關鍵資產在開機后被允許訪問。
- 首次解鎖后可訪問:關鍵資產在首次解鎖后被允許訪問。
- 解鎖時可訪問:關鍵資產僅在處于解鎖狀態時被允許訪問。
-
基于鎖屏密碼設置狀態的訪問控制:?該訪問控制默認不開啟,業務可根據實際情況決定是否開啟。
- 在用戶設置了鎖屏密碼后,關鍵資產才被允許訪問。
-
基于用戶認證的訪問控制:?該訪問控制默認不開啟,業務可根據實際情況決定是否開啟。
- 關鍵資產在用戶身份認證通過后被允許訪問。
- 任意一種認證方式(指紋、人臉、PIN碼)通過,均可授權本次關鍵資產的訪問。
- 業務可通過設置認證有效期,達成一次用戶認證、授權多個關鍵資產訪問的效果。認證有效期最長可設置10分鐘。
5、參考
華為官網:@ohos.security.asset (關鍵資產存儲服務)