鸿蒙静态资源HAR开发日志

鸿蒙静态资源HAR开发日志

前言

此日志仅记录开卡鸿蒙SDK即鸿蒙静态资源包HAR时遇到的一些问题,仅供可能同样遇到问题同样不知所措的你参考。先说一下我的开发环境

xml 复制代码
API版本:API12
"targetSdkVersion": "5.0.0(12)",
"compatibleSdkVersion": "5.0.0(12)",

DevEco Studio版本:6.0.2
Build Version: 6.0.2.642, built on March 5, 2026

DevEco Studio版本也是一边做一边升级,从6.0.0升级到6.0.2了已经。

1.HAR的网络请求配置

HAR的新建无需多言,不会的可以直接翻阅官方文档构建HAR或者可以谷歌一下。HAR的网络配置请求因为跟随主App包所以静态资源包无需额外配置网络权限

json 复制代码
"requestPermissions": [
 	{
     "name": "ohos.permission.INTERNET"
   }
 ]

但是记得检查一下主App是否有相关配置,位置:App根目录->entry->src->main->module.json5在该文件下module下添加上面的代码。

2.RSASHA256私钥签名

这么复杂的代码当然不能自己写啦!千问...没问出满意的答案,最终还是chatgpt给出了看上去比较合适的,经过几轮与它关于API版本、兼容性还有一些比较低级类型问题激烈的讨论与修改以后。经过验证的最终版本如下:

typescript 复制代码
// RsaSignUtil.ts
import cryptoFramework from '@ohos.security.cryptoFramework';
import util from '@ohos.util';

export class RsaSignUtil {

  /**
   * Base64 -> Uint8Array
   */
  private static base64ToUint8Array(base64: string): Uint8Array {
    const helper = new util.Base64Helper();
    return helper.decodeSync(base64);
  }

  /**
   * Uint8Array -> Base64
   */
  private static uint8ArrayToBase64(bytes: Uint8Array): string {
    const helper = new util.Base64Helper();
    return helper.encodeToStringSync(bytes);
  }

  /**
   * Uint8Array -> Hex
   */
  private static uint8ArrayToHex(bytes: Uint8Array): string {
    return Array.from(bytes)
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }

  private static hexToUint8Array(hex: string): Uint8Array {
    const bytes = new Uint8Array(hex.length / 2);
    for (let i = 0; i < bytes.length; i++) {
      bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
    }
    return bytes;
  }

  /**
   * string -> Uint8Array
   */
  private static strToUint8Array(data: string): Uint8Array {
    const encoder = new util.TextEncoder();
    return encoder.encodeInto(data);
  }

  /**
   * 加载私钥(PKCS8)
   */
  private static async loadPrivateKey(privateKeyBase64: string) {
    try {
      let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024');
      const keyPair = await asyKeyGenerator.convertPemKey(null, privateKeyBase64)
      console.log('keyPair', keyPair.priKey.getEncoded().data)
      return keyPair.priKey
    } catch (error) {
      console.log(error)
      return null;
    }
  }

  /**
   * 加载公钥(X509)
   */
  private static async loadPublicKey(publicKeyBase64: string) {
    try {
      const keyGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024");
      const keyPair = await keyGenerator.convertPemKey(publicKeyBase64, null);
      console.log('keyPair', keyPair.pubKey.getEncoded().data)
      return keyPair.pubKey;
    } catch (error) {
      return null
    }
  }

  /**
   * 签名
   * @param data 原文
   * @param privateKeyBase64 PKCS8私钥
   * @param outputType 输出格式 base64 | hex
   */
  static async sign(
    data: string,
    privateKeyBase64: string,
    outputType: 'base64' | 'hex' = 'base64'
  ): Promise<string> {

    try {
      const input = RsaSignUtil.strToUint8Array(data);
      const privateKey = await RsaSignUtil.loadPrivateKey(privateKeyBase64);
      // 参数格式: 'RSA1024|PKCS1|SHA256' 或 'RSA2048|PKCS1|SHA256'
      // 根据你的私钥长度选择 RSA1024 或 RSA2048
      // PKCS1 是常用的填充模式,如果是 PSS 模式则改为 'PSS'
      let signAlg = 'RSA1024|PKCS1|SHA256';
      const signer = cryptoFramework.createSign(signAlg);
      await signer.init(privateKey);

      // ✅ 关键:直接传入 data
      const signature = await signer.sign({
        data: input
      });
      const signBytes = new Uint8Array(signature.data);
      console.log('signBytes:', signBytes)
      if (outputType === 'hex') {
        return RsaSignUtil.uint8ArrayToHex(signBytes);
      }

      return RsaSignUtil.uint8ArrayToBase64(signBytes);

    } catch (e) {
      console.error("RSA签名失败:", e);
      throw new Error("RSA签名失败");
    }
  }

  /**
   * 验签
   * @param data 原文
   * @param signatureBase64 签名(Base64)
   * @param publicKeyBase64 公钥(X509)
   */
  // ===== 核心:验签 =====

  static async verify(
    data: string,
    signature: string,
    publicKeyBase64: string,
    signType: 'base64' | 'hex' = 'base64'
  ): Promise<boolean> {

    try {

      // 1️⃣ 原文
      const input = RsaSignUtil.strToUint8Array(data);

      // 2️⃣ 签名(Base64 → Uint8Array)
      let signatureBytes: Uint8Array;

      if (signType === 'hex') {
        signatureBytes = RsaSignUtil.hexToUint8Array(signature);
      } else {
        signatureBytes = RsaSignUtil.base64ToUint8Array(signature);
      }
      // 3️⃣ 公钥
      const publicKey = await RsaSignUtil.loadPublicKey(publicKeyBase64);

      // 4️⃣ 创建 verifier
      let verifyAlg = 'RSA1024|PKCS1|SHA256';
      const verifier = cryptoFramework.createVerify(verifyAlg);

      await verifier.init(publicKey);

      // 5️⃣ 验签(⚠️ 必须传 DataBlob)
      const result = await verifier.verify(
        { data: input },
        { data: signatureBytes }
      );

      return result;

    } catch (e) {
      console.error("RSA验签失败:", e);
      return false;
    }
  }
}

将我们生成的私钥数据尝试跟安卓iOS一样,使用PEM 转 DER掐头去尾的中间数据进行私钥解析签名。但每次运行必报错:

bash 复制代码
Failed to parse params!
create c generator fail.

再次陷入跟千问与chatgpt的水深火热的探讨中...

1.他们认为我的密钥是pkcs1格式,但鸿蒙仅支持pkcs8(其实都支持)

2.密钥加载格式DER还是PEM的格式化问题

...

AI给的createSign和createVerify参数是不对的要注意,这不得不再次提到官方文档的重要性验签(全局搜createVerify就能看到),该参数需要根据你的密钥签名算法格式来敲定,比如我的密钥长度是1024的pkcs1,切记!!!确定一下你使用的密钥格式,如果你不确定就找谁给的密钥问一下,不然真的会shit...

之前使用convertKey转换一直受挫,直到看到这里convertpemkey,走头无路之下只能转换一下思路。后来发现签名确实可以了。

签名验签调用如下

typescript 复制代码
const data = '101300000131011773990659305'
// appCode + userId + timeStamp;
console.log("签名源数据:", data);
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\n' +
  'MIICXQIBAAKBgQDACLTwa3NJFQ+GjaCyuMkhDSGtXdgb905qCq0INfa2jyxWtBPa\n' +
  'kcj1TtCWmjx193FmIl7XlCbll7DRucKdxuczicreUeCtrApD+27Sh45MlAiHEVlW\n' +
  '45tpVvkpcDkGvh8z16Z+2hteeYKS3LvKqoXI2mpu9iozA4+DRb0/6mBlLQIDAQAB\n' +
  'AoGBAKAZ4jibbOdeTq5gf7zhgJY0q4ItvlI6kj6onfA9hW5Y2Z+DzRWp+8BBMHiP\n' +
  'KJ81aG69cxMKqo86M6n21IHUZcZJgdrm95kncOdrTFLJgmMtIpftfNwPilQe5Vp8\n' +
  'G337e3iFup1bIPZnOR1UYp+76Qhp6m59t9P48Wy2KAGR2MghAkEA3m/3Rh9XU6XY\n' +
  'bYKpx+8LngbQhrMDwn8PjVU8iBsbnupg01bfz1T4XBDkpDfuDddtp/EITb6OgrWk\n' +
  'osezun2QdQJBAN0CW5uLb+J48A29Fxvx78pYcTtKhx2TgkIaEDyDZGfYV1aNKYk4\n' +
  'Z1xFgBKq3vnBcBzRyrwzR6flftovpFOO6tkCQQCWmFNfRETJOxUmPzpXRD4nRRHU\n' +
  'wEohWgjbdQPAWin/E0tuifiI1Ew5eK5zh/JBqMJy2zr197dgQz4tfurtrakpAkAE\n' +
  'nP/8hzSWrZ+VKdVJqAsxVhdG/Y9EdsfJSXH9UWDPKhzomZm8W4kMzAaYXSi6XedR\n' +
  'Mq/grdFZF9lGBKiRdfJBAkBSRpSF8NiuK8Ak4gHOfEVbXdHxnV3kXit24AB44Oh5\n' +
  '8juYyePBPIojFy7gEPxg0IA1mZ83+b3fvGGMe/t+3PPv\n' +
  '-----END RSA PRIVATE KEY-----'
const sign = await RsaSignUtil.sign(data, privateKey, 'hex');
console.log("签名结果:", sign);

const pub = '-----BEGIN PUBLIC KEY-----\n' +
  'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDACLTwa3NJFQ+GjaCyuMkhDSGt\n' +
  'Xdgb905qCq0INfa2jyxWtBPakcj1TtCWmjx193FmIl7XlCbll7DRucKdxuczicre\n' +
  'UeCtrApD+27Sh45MlAiHEVlW45tpVvkpcDkGvh8z16Z+2hteeYKS3LvKqoXI2mpu\n' +
  '9iozA4+DRb0/6mBlLQIDAQAB\n' +
  '-----END PUBLIC KEY-----'
const result = await RsaSignUtil.verify(data, sign, pub, 'hex')
console.log(String(result))

3.参考

HAR包开发
使用ArkTS语言完成JWT鉴权令牌
非对称密钥生成和转换规格
@ohos.security.cryptoFramework (加解密算法库框架)之convertPemKey
使用RSA密钥对(PKCS1模式)签名验签(ArkTS)
鸿蒙HarmonyOS-http四种请求的统一封装HarmonyOS-工具类封装-http请求

相关推荐
心中有国也有家15 小时前
ArkTS 鸿蒙开发语法完全指南:从入门到实战
华为·harmonyos
Georgewu18 小时前
如何判断应用在鸿蒙卓易通或者出境易环境下?
android·harmonyos
菜鸟不学编程18 小时前
鸿蒙中的 AR/VR 开发与场景创建
ar·vr·harmonyos
Swift社区20 小时前
鸿蒙应用上架流程经验
华为·harmonyos
@不误正业20 小时前
OpenHarmony集成AI Agent实战:打造鸿蒙智能助理
人工智能·华为·harmonyos
弓.长.1 天前
ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-netinfo — 网络状态检测
网络·react native·harmonyos
弓.长.1 天前
ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-network-info — 网络信息获取
网络·react native·harmonyos
弓.长.1 天前
ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-image-crop-picker — 图片选择裁剪组件
react native·react.js·harmonyos
讯方洋哥1 天前
HarmonyOS App开发——鸿蒙ArkTS基于首选项引导页的集成和应用
华为·harmonyos