前言

接着上篇的安全话题,今天聊两个更硬核的东西:分布式数字身份(DID)和数字盾。这俩配合起来,能解决一个很现实的问题——怎么在鸿蒙生态里证明"我是我",同时不把自己的隐私全抖出去

DID 到底是干嘛的

传统的身份验证,你得把身份证号、手机号、人脸数据全交给 App,App 再传给服务器验证。中间任何一个环节泄露,你就完了。

DID(Decentralized Identifier,分布式数字身份)换了个思路:系统帮你在 TEE 里生成一个唯一的数字身份标识,这个标识跟你的真实身份绑定,但应用只能拿到一个"身份证明",拿不到你的原始信息。

打个比方:你去酒店入住,DID 相当于你跟前台说"系统可以证明我的身份",前台向系统验证一下就完事,不需要把你的身份证复印件留在他那里。

数字盾的三板斧

数字盾是 HarmonyOS 7 在安全操作层面的能力,它提供三个核心保障:

A clean Notion-style concept map illustrating the

可信数字签名。用户的签名操作在 TEE 里完成,签出来的数字签名具有法律效力级别的防篡改能力。

可信 UI 确认。关键操作(比如转账确认、合同签署)的界面由系统安全层渲染,应用无法伪造或劫持这个界面。

可信输入。密码、PIN 码这些敏感输入走安全输入通道,键盘和输入框都在 TEE 保护下,截屏录屏都拿不到内容。

给智能助手接入 DID

场景:用户在智能助手里签署一份智能家居设备的共享授权书,需要验证身份并进行数字签名。

注册 DID 身份

首次使用时,为用户创建一个 DID 身份。这个过程系统会自动处理密钥生成和 TEE 存储:

import { did } from '@kit.DistributedIdentityKit';
import { common } from '@kit.AbilityKit';

async function registerUserDID(context: common.UIAbilityContext): Promise<string> {
  // 检查设备是否支持 DID
  const isSupported = await did.isSupported();
  if (!isSupported) {
    throw new Error('当前设备不支持分布式数字身份');
  }

  // 创建 DID 身份声明
  const claim: did.IdentityClaim = {
    // 声明类型:自然人身份
    type: did.ClaimType.NATURAL_PERSON,
    // 绑定的认证方式(支持多种)
    authMethods: [
      did.AuthMethod.FACE,           // 人脸
      did.AuthMethod.FINGERPRINT,    // 指纹
    ],
    // 最小化隐私暴露:只声明必要属性
    disclosedAttributes: [
      did.Attribute.AGE_VERIFIED,     // 只证明"已成年",不暴露具体年龄
      did.Attribute.IDENTITY_VALID,   // 只证明"身份有效",不暴露证件号
    ],
  };

  try {
    const didResult = await did.register(context, claim);
    console.info(`DID 注册成功: ${didResult.did}`);

    // 把 DID 标识存到 Preferences 里,后续使用
    const prefs = await preferences.getPreferences(context, 'user_identity');
    await prefs.put('did', didResult.did);
    await prefs.flush();

    return didResult.did;
  } catch (err) {
    console.error('DID 注册失败:', JSON.stringify(err));
    throw err;
  }
}

A structured Notion-style table or checklist detai

A step-by-step flowchart in Notion style showing t

注意 disclosedAttributes 这个字段——这就是"最小化隐私暴露"的核心。你只声明"我已成年",验证方只能得到 true/false,看不到你的出生日期。

发起身份验证

签署授权书之前,先验证用户身份:

import { did } from '@kit.DistributedIdentityKit';

async function verifyUserIdentity(
  context: common.UIAbilityContext,
  userDid: string
): Promise<did.VerifyResult> {
  // 构造验证请求
  const verifyRequest: did.VerifyRequest = {
    did: userDid,
    // 要求人脸验证
    challenge: did.Challenge.BIOMETRIC,
    // 验证用途声明(会展示给用户看)
    purpose: '签署智能家居设备共享授权书',
    // 超时时间
    timeout: 30000,
  };

  try {
    // 系统会弹出可信 UI 让用户进行生物认证
    // 这个 UI 是系统层渲染的,应用无法干预
    const result = await did.verify(context, verifyRequest);

    if (result.verified) {
      console.info('身份验证通过');
      // result.token 是一个一次性的验证令牌
      return result;
    } else {
      console.warn('身份验证未通过');
      throw new Error('身份验证失败');
    }
  } catch (err) {
    console.error('验证过程出错:', JSON.stringify(err));
    throw err;
  }
}

系统弹出的验证 UI 是可信 UI,由安全层直接渲染。你的应用代码拿不到用户的人脸数据,也看不到验证过程中的任何中间状态。

数字盾签名

验证通过后,用数字盾对授权书进行签名:

import { digitalShield } from '@kit.DigitalShieldKit';

interface AuthDocument {
  deviceId: string;
  sharedUsers: string[];
  permissions: string[];
  timestamp: number;
}

async function signAuthDocument(
  doc: AuthDocument,
  verifyToken: string
): Promise<digitalShield.SignatureResult> {
  // 准备签名内容
  const content = JSON.stringify(doc);
  const contentHash = await digitalShield.hash(content, digitalShield.HashAlgorithm.SM3);

  // 配置签名参数
  const signConfig: digitalShield.SignConfig = {
    algorithm: digitalShield.SignAlgorithm.SM2,  // 国密 SM2
    hashAlgorithm: digitalShield.HashAlgorithm.SM3,
    // 关联身份验证令牌(证明签名者已通过身份验证)
    identityToken: verifyToken,
    // 签名时间戳
    signTime: Date.now(),
  };

  // 构造可信 UI 确认内容
  const confirmUI: digitalShield.ConfirmUIConfig = {
    title: '签署设备共享授权书',
    body: `将以下设备共享给 ${doc.sharedUsers.length} 位用户:\n` +
          `设备: ${doc.deviceId}\n` +
          `权限: ${doc.permissions.join(', ')}`,
    confirmText: '确认签署',
    cancelText: '取消',
    // 安全级别:TEE
    securityLevel: digitalShield.SecurityLevel.TEE,
  };

  try {
    // 系统弹出可信 UI 让用户确认
    // 确认后在 TEE 内完成签名
    const result = await digitalShield.sign(contentHash, signConfig, confirmUI);

    console.info(`签名完成,签名值: ${result.signature.substring(0, 20)}...`);
    console.info(`签名证书哈希: ${result.certHash}`);

    return result;
  } catch (err) {
    if (err.code === digitalShield.ErrorCode.USER_CANCELLED) {
      console.info('用户取消了签名');
    }
    throw err;
  }
}

可信输入框

授权书里可能需要用户输入 PIN 码做二次确认。用数字盾的可信输入组件:

import { digitalShield } from '@kit.DigitalShieldKit';

@Component
struct SecurePinInput {
  @State pinLength: number = 0;
  private onComplete: (encryptedPin: string) => void;

  build() {
    Column({ space: 16 }) {
      Text('请输入安全 PIN 码')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)

      // 可信输入组件:渲染和输入都在安全层完成
      // 应用层只能拿到加密后的结果
      digitalShield.SecureInput({
        type: digitalShield.InputType.PIN,
        length: 6,
        placeholder: '请输入 6 位 PIN',
        // 防截屏标记
        antiCapture: true,
        onComplete: async (result: digitalShield.SecureInputResult) => {
          // result.encryptedValue 是 TEE 加密后的 PIN
          // 应用层拿不到原始 PIN 值
          this.onComplete(result.encryptedValue);
        },
        onInput: (length: number) => {
          this.pinLength = length;
        }
      })
        .width('80%')
        .height(48)

      Text(`已输入 ${this.pinLength}/6 位`)
        .fontSize(14)
        .fontColor('#999')
    }
    .width('100%')
    .padding(24)
    .backgroundColor($r('app.color.bg_secondary'))
    .borderRadius(16)
  }
}

SecureInput 组件跟普通的 TextInput 用法差不多,但内部完全不同——键盘是安全键盘,输入内容直接进 TEE,你连输入了多少个字符都只能通过回调知道,拿不到原始值。

跨设备 DID 互认

DID 的一个强大之处在于跨设备互认。用户在手机上注册了 DID,走到平板上,平板能自动识别并信任这个身份。这靠的是鸿蒙的分布式身份信任链——所有设备的 TEE 共享一个根信任证书。

实际操作是这样的:

import { did } from '@kit.DistributedIdentityKit';

// 在平板上查找用户在其他设备上注册的 DID
async function findExistingDID(context: common.UIAbilityContext): Promise<string | null> {
  const existingDIDs = await did.discoverTrustedDIDs(context);

  if (existingDIDs.length > 0) {
    // 找到已有的 DID,直接使用
    const primaryDID = existingDIDs[0];
    console.info(`发现已有 DID: ${primaryDID.did}, 注册设备: ${primaryDID.registeredDevice}`);
    return primaryDID.did;
  }

  // 没有找到,需要新注册
  return null;
}

这意味着用户在手机上签过的授权书,在平板上也能验证真伪,不需要重新走一遍身份验证。我们智能助手的设备共享授权就利用了这个能力——用户在手机上授权一次,平板和车机都能自动识别。

完整流程串起来

把上面几个步骤串成完整签名流程:

async function completeAuthSigning(
  context: common.UIAbilityContext,
  doc: AuthDocument
): Promise<boolean> {
  try {
    // 1. 获取用户 DID
    const prefs = await preferences.getPreferences(context, 'user_identity');
    const userDid = await prefs.get('did') as string;

    if (!userDid) {
      // 首次使用,先注册 DID
      await registerUserDID(context);
      return false; // 注册完让用户重新触发签名
    }

    // 2. 身份验证
    const verifyResult = await verifyUserIdentity(context, userDid);

    // 3. 数字签名
    const signResult = await signAuthDocument(doc, verifyResult.token);

    // 4. 保存签名结果
    await saveSignature(doc.deviceId, signResult);
    return true;

  } catch (err) {
    console.error('签名流程失败:', JSON.stringify(err));
    return false;
  }
}

几个实用建议

DID 注册一次就行。别每次操作都注册,注册一次后 DID 标识永久有效(除非用户主动重置)。

可信 UI 的文案要认真写purposeconfirmUI.body 会直接展示给用户,写清楚"这个签名是干嘛的"。模糊的描述会降低用户信任度,也容易被安全审核打回。

TEE 操作有性能开销。一次签名大概 500ms-1s,别在列表滑动或者高频操作里嵌入 TEE 调用。

模拟器调试受限。跟星盾引擎一样,DID 和数字盾都依赖 TEE 硬件。调试时可以用 did.isSupported() 做判断,不支持时走 mock 路径。

下篇换个轻松点的话题,聊聊 HarmonyOS 7 的网络优化——QUIC 持久连接和预建链怎么让你的应用网速起飞。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐