【鸿蒙NEXT】鸿蒙里面类似iOS的Keychain——关键资产(@ohos.security.asset)实现设备唯一标识

前言

在iOS开发中Keychain 是一个非常安全的存储系统,用于保存敏感信息,如密码、证书、密钥等。与 NSUserDefaults 或文件系统不同,Keychain 提供了更高的安全性,因为它对数据进行了加密,并且只有经过授权的应用程序才能访问存储的数据。那么在鸿蒙里面对应的是什么呢?

1、关键资产(@ohos.security.asset)

在鸿蒙里面也有类似的东西,叫做关键资产(@ohos.security.asset),关键资产存储服务提供了用户短敏感数据的安全存储及管理能力。其中,短敏感数据可以是密码类(账号/密码)、Token类(应用凭据)、其他关键明文(如银行卡号)等长度较短的用户敏感数据。

从API version 11 开始支持

使用关键资产需要导入模块AssetStoreKit

import { asset } from '@kit.AssetStoreKit';

2、asset常用操作

version 11 开始支持,异步方法,如下

  1. asset.add:add(attributes: AssetMap): Promise,新增一条关键资产,使用Promise方式异步返回结果。

  2. asset.remove:removeSync(query: AssetMap): void,删除符合条件的一条或多条关键资产,使用异步方式。

  3. asset.update:update(query: AssetMap, attributesToUpdate: AssetMap): Promise,更新符合条件的一条关键资产,使用Promise方式异步返回结果。

  4. asset.query:query(query: AssetMap): Promise<Array>,查询一条或多条符合条件的关键资产。若查询需要用户认证的关键资产,则需要在本函数前调用asset.preQuery,在本函数后调用asset.postQuery,使用Promise回调异步返回结果。

  5. asset.preQuery:preQuery(query: AssetMap): Promise,查询的预处理,用于需要用户认证的关键资产。在用户认证成功后,应当随后调用asset.query、asset.postQuery。使用Promise方式异步返回结果。

  6. asset.postQuery:postQuery(handle: AssetMap): Promise,查询的后置处理,用于需要用户认证的关键资产。需与asset.preQuery函数成对出现。使用Promise方式异步返回结果。

    version 12 开始支持,同步方法,如下

  7. asset.addSync:新增一条关键资产,使用Promise方式同步步返回结果。

  8. asset.removeSync:removeSync(query: AssetMap): void,删除符合条件的一条或多条关键资产,使用同步方式。

  9. asset.addSync:新增一条关键资产,使用Promise方式同步步返回结果。

  10. asset.removeSync:removeSync(query: AssetMap): void,删除符合条件的一条或多条关键资产,使用同步方式。

  11. asset.updateSync:updateSync(query: AssetMap, attributesToUpdate: AssetMap): void,更新符合条件的一条关键资产,使用同步方式返回结果。

  12. asset.querySync:querySync(query: AssetMap): Array,查询一条或多条符合条件的关键资产。若查询需要用户认证的关键资产,则需要在本函数前调用asset.preQuerySync,在本函数后调用asset.postQuerySync,使用同步方式返回结果。

  13. asset.preQuerySync:preQuerySync(query: AssetMap): Uint8Array,查询的预处理,用于需要用户认证的关键资产。在用户认证成功后,应当随后调用asset.querySync、asset.postQuerySync。使用同步方式返回结果。

  14. asset.postQuerySync:postQuerySync(handle: AssetMap): void,查询的后置处理,用于需要用户认证的关键资产。需与asset.preQuerySync函数成对出现。使用同步方式返回结果。

关键资产需要使用到的系统能力: SystemCapability.Security.Asset

3、asset的封装使用

在iOS中使用Keychain 比较常见的功能是存储一个值作为设备唯一标识,那么asset也以此作为示例封装一个,刚好前阵子项目里面也使用了。我也封装了一个工具类hmDeviceTools

3.1 导入需要的头文件

import { util } from '@kit.ArkTS'
import { asset } from '@kit.AssetStoreKit';
import { BusinessError } from '@kit.BasicServicesKit';

3.2 封装工具类

hmDeviceTools类内容

export class hmDeviceTools {

  private static deviceIdCacheKey = "testdevice_id_cache_key" //testkey
  private static deviceId = ""

  /**
   * * 判断字符串是否为空
   * @param property 被检测的字符串
   * @return Boolean
   */
  static isEmpty(property?: string | null): Boolean {
    if (property == '' || property == null || property == undefined || property == 'undefined' ||
      property.length == 0) {
      return true
    }
    return false
  }

  /**
   * 获取设备id
   */
  static getDeviceId() {
    let deviceId = hmDeviceTools.deviceId
    //如果内存缓存为空,则从AssetStore中读取
    if (hmDeviceTools.isEmpty(deviceId)) {
      deviceId = getAssetMap(hmDeviceTools.deviceIdCacheKey)
    }
    //如果AssetStore中未读取到,则随机生成32位随机码,然后缓存到AssetStore中
    if (hmDeviceTools.isEmpty(deviceId)) {
      deviceId = util.generateRandomUUID(true).replace(new RegExp('-', "gm"), '')
      deviceId = deviceId.slice(0,Math.min(10,deviceId.length))//可以确保不会超出字符串的长度。
      setAssetMap(hmDeviceTools.deviceIdCacheKey, deviceId)
    }
    hmDeviceTools.deviceId = deviceId
    return deviceId
  }
}

getDeviceId函数里面,我是截取的10位,大家可以工具自己的具体业务来自行截取,或者使用使用generateRandomUUID返回的32位。

3.3 addSync 设置数据

既然有异步和同步可选,我当然是使用addSync同步来写了,后面的方法都是使用同步来实现。

    
/**
 * 设置数据
 * @param key  要查找的索引
 * @param value 需要存的值
 */
function setAssetMap(key: string, value: string) {

  let attr: asset.AssetMap = new Map();
  let result: Boolean
  if (canIUse("SystemCapability.Security.Asset")) {
    // 关键资产别名,每条关键资产的唯一索引。
    // 类型为Uint8Array,长度为1-256字节。
    attr.set(asset.Tag.ALIAS, stringToArray(key));
    // 关键资产明文。
    // 类型为Uint8Array,长度为1-1024字节
    attr.set(asset.Tag.SECRET, stringToArray(value));

    // 关键资产同步类型>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 (isHasKey(key)) {
    result = updateAssetMap(attr, attr);
  } else {
    try {
      asset.addSync(attr);
      result = true
    } catch (error) {
      let err = error as BusinessError;
      console.error(`Failed to add Asset. Code is ${err.code}, message is ${err.message}`);
      result = false
    }
  }

}

3.4 querySync 获取数据

/**
 * 获取数据
 * @param key  要查找的索引
 * @returns string 表示操作的结果
 */
function getAssetMap(key: string): string {

  if (canIUse("SystemCapability.Security.Asset")) {

    let query: asset.AssetMap = new Map();
    // 关键资产别名,每条关键资产的唯一索引。
    // 类型为Uint8Array,长度为1-256字节。
    query.set(asset.Tag.ALIAS, stringToArray(key));
    //  关键资产查询返回的结果类型。
    query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL);
    // query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ATTRIBUTES); // 此处表示仅返回关键资产属性,不包含关键资产明文

    try {

      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 string
          let secretStr: string = arrayToString(secret);

          return secretStr;
        }

      }

    } catch (error) {
      let err = error as BusinessError;
      console.error(`Failed to query Asset. Code is ${err.code}, message is ${err.message}`);
      return "";
    }
  }

  return "";
}

3.4 querySync 查询key

/**
 * 判断key是否存在
 * @param key 要查找的索引
 * @returns Boolean 表示添加操作的结果
 */
function isHasKey(key: string): Boolean {

  if (canIUse("SystemCapability.Security.Asset")) {

    let query: asset.AssetMap = new Map();

    // 关键资产别名,每条关键资产的唯一索引。
    // 类型为Uint8Array,长度为1-256字节。
    query.set(asset.Tag.ALIAS, stringToArray(key));

    //  关键资产查询返回的结果类型。
    query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL);

    const res = queryAssetMap(query);

    if (!res || res.length < 1) {
      return false;
    }

    return true;
  }

  return false;
}

3.5 querySync 查询数据

/**
* 查找数据
* @param key  要查找的索引
* @returns Array<asset.AssetMap> 表示添加操作的结果
*/
function 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;
   console.error(`Failed to query Asset. Code is ${err.code}, message is ${err.message}`);
   return assetMaps;

 }
}

3.6 updateSync 更新数据

/**
 * 查找数据
 * @param key  要查找的索引
 * @returns Array<asset.AssetMap> 表示添加操作的结果
 */
function 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;
    console.error(`Failed to query Asset. Code is ${err.code}, message is ${err.message}`);
    return assetMaps;

  }
}

使用到的其他函数

function stringToArray(str: string): Uint8Array {
  let textEncoder = new util.TextEncoder();
  return textEncoder.encodeInto(str);
}

function arrayToString(arr: Uint8Array): string {

  let textDecoder = util.TextDecoder.create('utf-8', { fatal: false, ignoreBOM: true });
  let decodeToStringOptions: util.DecodeToStringOptions = {
    stream: false
  }

  let str = textDecoder.decodeToString(arr, decodeToStringOptions);

  return str;
}

4、特别说明

如果需要卸载之后获取的值不变,需要设置IS_PERSISTENT属性,需要申请ohos.permission.STORE_PERSISTENT_DATA权限。

在这里插入图片描述

完整项目的结构如下:
在这里插入图片描述

5、参考

1、华为官网:@ohos.security.asset (关键资产存储服务)

2、冉冉同学:【HarmonyOS NEXT】获取卸载APP后不变的设备ID

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/945183.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VBA批量插入图片到PPT,一页一图

Sub InsertPicturesIntoSlides()Dim pptApp As ObjectDim pptPres As ObjectDim pptSlide As ObjectDim strFolderPath As StringDim strFileName As StringDim i As Integer 设置图片文件夹路径strFolderPath "C:\您的图片文件夹路径\" 请替换为您的图片文件夹路径…

【Gitlab】详细介绍与安装配置指南

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《未来已来&#xff1a;云原生之旅》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是Gitlab 2、Gitlab起源 二、GitLab的核心功能 …

Mcnemar‘s exact test

与卡方检验的区别 与fisher exact test区别

从手术到诊断:Tekceleo超声波压电电机的全面医疗应用

在当今医疗领域&#xff0c;技术的不断创新正在推动传统医疗模式向更精准、更高效的方向转变。Tekceleo公司凭借其超声波压电电机技术&#xff0c;在医疗行业中逐步占据重要地位&#xff0c;为医疗操作的精准化与高效化做出了显著贡献。 Tekceleo超声波压电电机的技术特点 Tek…

Postman[3] 创建Get和Post请求

1.创建Get请求 以打开百度页面为例 链接&#xff1a;https://www.baidu.com/ 步骤&#xff1a; 1.1新建一个Collection 1.2Add Request 1.3填充请求的url 1.4 Send 1.5检查返回结果 注意&#xff1a;这里和我们打开网页看到的页面不一样&#xff0c;是因为缺少请求头&…

C# OpenCV机器视觉:姿态估计

在一个阴沉沉的下午&#xff0c;天空仿佛被一块巨大的灰色抹布盖住&#xff0c;细雨淅淅沥沥地洒着&#xff0c;阿强正在实验室里捣鼓他那些宝贝仪器&#xff0c;活像一个正在摆弄玩具的大孩子。突然&#xff0c;同事小杨像只没头的苍蝇一样冲了进来&#xff0c;脸上写满了困惑…

自动化测试模型(一)

8.8.1 自动化测试模型概述 在自动化测试运用于测试工作的过程中&#xff0c;测试人员根据不同自动化测试工具、测试框架等所进行的测试活动进行了抽象&#xff0c;总结出线性测试、模块化驱动测试、数据驱动测试和关键字驱动测试这4种自动化测试模型。 线性测试 首先&#…

语音识别基础算法——动态时间规整算法

前言 动态时间规整算法&#xff0c;Dynamic Time Wraping&#xff0c;缩写为DTW&#xff0c;是语音识别领域的一个基础算法。 算法的提出 DTW 的提出是为了解决或尽量解决在语音识别当中的孤立词识别不正确的问题。该问题简单描述为&#xff1a;在识别阶段&#xff0c;将输入…

Word论文交叉引用一键上标

Word论文交叉引用一键上标 1.进入Microsoft word使用CtrlH快捷键或单击替换按钮 2.在查找内容中输入[^#] 3.鼠标点击&#xff0c;标签为“替换为&#xff1a;”的文本框&#xff0c;注意光标一定要打在图红色方框圈中的文本框中&#xff01; 4.点击格式选择字体 5.勾选上标…

BLIP论文笔记

论文地址 BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 论文思想 其实Clip就相当于只用了ITC

适用于项目经理的跨团队协作实践:Atlassian Jira与Confluence集成

适用于项目经理的跨团队协作实践&#xff1a;Atlassian Jira与Confluence集成 现代项目经理的核心职责是提供可视性、保持团队一致&#xff0c;并确保团队拥有交付出色工作所需的资源。在过去几年中&#xff0c;由于分布式团队的需求不断增加&#xff0c;项目经理这一角色已迅速…

【交叉编译】sysstat 离线编译

1、下载源码 首先从下载&#xff1a; https://github.com/sysstat/sysstat/tags &#xff0c;我直接下载最新的 2、配置交叉编译链 快速的方法就是把整个编译包全部放在Linux &#xff0c;然后编辑~/.zshrc或者~/.bashrc,在最后加入&#xff1a; export PATH$PATH:/opt/arm-so…

算法题(20):买卖股票的最佳时机

审题&#xff1a; 需要返回最大利润值 思路&#xff1a; 首先我们需要看看股票走势图 我们看到股票走势图是把数据图像化了&#xff0c;那么我们观察这个股票图的时候发现他在某一段区间呈大体上升&#xff0c;而大体上升的前提就是没有出现比最低点更低的数据值。 根据这一点我…

IDEA XML 文件 SQL 提示

首先连接到对应的数据库。Database 里面要填写对应的数据库名称 配置当前项目的 SQL 方言&#xff0c;例如我这里是 MySQL 数据库管理系统&#xff0c;那么就选择 MySQL 此时就有 SQL 语法、表名、字段名等提示信息了

【STM32项目】基于STM32单片机温湿度PM2.5粉尘甲醛环境质量监测系统wifi【完整工程资料源码】

演示视频&#xff1a; 基于STM32单片机温湿度PM2.5粉尘甲醛环境质量监测系统 目录 演示视频&#xff1a; 一、项目简介&#xff1a; 1.1 功能介绍&#xff1a; 1.2 设计背景&#xff1a; 1.3 设计意义&#xff1a; 1.4 设计目的 二、硬件设计&#xff1a; 2.1 整体原理图设计&…

优化站群SEO:使用苹果CMS泛目录插件实现泛目录页面刷新不变

优化站群SEO&#xff1a;使用苹果CMS泛目录插件实现泛目录页面刷新不变 在当今数字营销环境中&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;是提升网站流量和可见性的关键策略。苹果CMS作为一款灵活的内容管理系统&#xff0c;提供了丰富的插件功能&#xff0c;尤其是…

SAP PP bom历史导出 ALV 及XLSX 带ECN号

bom总数 104W PS超过XLSX上限 &#xff0c;那就分文件 *&---------------------------------------------------------------------* *& Report ZRPT_PP_BOM_HIS_ECN *&---------------------------------------------------------------------* *& tcode:zpp0…

HAL 库句柄

一、命名方式&#xff1a;句柄是h为首字母&#xff0c;后面接协议名称 比如&#xff1a;huart、hadc、hi2c等 二、句柄类型&#xff1a; 这里拿huart举例&#xff0c;它的类型是UART_HandleTypeDef 进去stm32f1xx_hal_uart.h之后发现句柄的结构定义有部分是灰色的 灰色的当U…

JVM 性能监控工具之命令行篇

在 Java 开发过程中&#xff0c;性能监控和问题排查是开发者经常面临的任务。JDK 提供了一系列命令行工具&#xff0c;帮助开发者监控 JVM 运行状态、诊断内存泄漏、线程死锁等问题。本文将详细介绍这些工具的使用方法及其应用场景。 1 JDK性能监控工具 1.1 jps&#xff1a;查…

使用IDEA远程debug服务器上的jar包

仅用于测试环境调试&#xff0c;debug会阻塞 如生产jar包叫 test.jar &#xff0c;部署的IP为10.184.136.18&#xff0c;端口为9999&#xff0c;idea的项目为local-network&#xff0c;照如下配置即可&#xff0c;仅红色部分需替换 弄完之后&#xff0c;打开debug&#xff0c;…