《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第7篇:安全认证——星闪安全连接与数据加密
一个被忽视的入口为什么你的星闪连接“裸奔”了很多人在 HarmonyOS 上第一次接触 NearLink Kit 时会很快跑通一个基础 Demo扫描设备、发起连接、收发数据。看起来一切顺利但如果仔细查看日志你会发现一个不太对劲的地方——通信全程没有加密。这不是 NearLink 的 Bug而是很多人跳过了安全认证配置这一步。默认的NearLinkDevice配置是不会自动开启加密的你需要显式声明 SecurityConfig并指定配对方式和加密类型。简单来说如果你的设备互相通信时数据包在网络中是明文传输的那么攻击者只需要在有信号覆盖的范围内进行嗅探就能拿到你的业务数据。这在物联网、智能家居、甚至一些近场支付场景中是绝对不允许的。这篇文章会带你完整走一遍 NearLink 安全连接的配置、配对、加密通信的完整流程。它解决什么问题安全连接的生命周期NearLink Kit 的安全认证核心解决的是三个问题身份确认确保连接的设备是“可信的”而不是一个假冒设备。密钥协商双方在不安全的信道中安全地交换一个临时密钥。数据加密使用协商出来的密钥对后续所有业务数据进行加密传输。它适合的场景智能家居网关与传感器之间的控制指令传输车载钥匙与车辆之间的身份验证工业控制设备之间的实时数据回传它不适合的场景纯广播类应用如 Beacon 信标广播本身是单方向的不需要双向认证。已经通过其他通道如蓝牙、Wi-Fi完成了安全协商的场景如果 NearLink 只是作为传输通道可以复用在其他层的安全机制。与其他方案对比方案安全层次实现成本推荐场景NearLink 原生安全物理层 链路层低配置即用设备间直连无应用层加解密需求应用层加密AES应用层高需要自行管理密钥设备能力有限或需要专用加密方案不做任何加密无无仅适合开发调试绝对禁止上生产环境实际项目里强烈推荐使用 NearLink 提供的原生安全连接。因为它是在物理层和链路层完成的加密对应用层完全透明开发者也无需关心密钥如何存储和分发。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备支持 NearLink 功能的手机/平板/开发板核心实现三步完成安全连接下面从零开始实现一个带安全认证的 NearLink 通信 Demo。1. 配置 SecurityConfig这是整个安全流程的入口。你需要在NearLinkDevice创建时通过SecurityConfig指定配对方式和加密类型。// model/NearLinkConfig.etsimport{nearLink}fromkit.ConnectivityKit;/** * 构建安全配置对象 * 使用 JustWorks 配对 AES-GCM-128 加密 * 这是最常用的组合兼顾安全性与易用性 */exportfunctionbuildSecurityConfig():nearLink.SecurityConfig{return{// 配对方式JustWorks无需界面交互的免密配对适用于无屏设备pairingMethod:nearLink.PairingMethod.JUST_WORKS,// 加密类型AES-GCM-128业界主流对称加密算法encryptType:nearLink.EncryptType.GCM_128,// 验证握手回调关键用于确认配对结果verifyHandshakeCallback:(device:nearLink.NearLinkDevice):boolean{console.info([NearLinkDemo] 验证握手设备地址${device.deviceAddress});// 实际项目中可以做额外的设备校验例如// 1. 检查设备号是否在白名单中// 2. 发送挑战值// 这里默认返回 true表示接受returntrue;},// 显示密钥回调仅用于确认用户在设备上按下了确认键的回调对JustWorks模式影响不大displayKeyCallback:(key:ArrayBuffer):void{console.info([NearLinkDemo] 显示密钥:${newUint8Array(key).join(,)});}};}注意事项verifyHandshakeCallback与displayKeyCallback是配对过程中的重要组件。如果verifyHandshakeCallback返回false连接会被直接拒绝。displayKeyCallback一般用于确认用户在设备上按下了确认键在JUST_WORKS模式下这个回调的作用相对弱一些。2. 发起安全连接配置好安全参数后调用NearLinkDevice的connect方法发起连接。// view/DeviceConnectPage.etsimport{nearLink}fromkit.ConnectivityKit;import{buildSecurityConfig}from../model/NearLinkConfig;EntryComponentstruct DeviceConnectPage{Statemessage:string准备连接;privatenearLinkDevice:nearLink.NearLinkDevice|nullnull;privatedeviceList:nearLink.DiscoveredDevice[][];build(){Column(){Button(扫描并连接安全设备).onClick((){this.startSecureDiscovery();})Button(发送加密数据).onClick((){this.sendEncryptedMessage();})Text(this.message).fontSize(14)}.padding(20).width(100%).height(100%)}// 扫描并连接asyncstartSecureDiscovery(){this.message正在扫描...;try{// 第一步扫描设备this.deviceListawaitnearLink.discoverDevices({discoveryMode:nearLink.DiscoveryMode.PASSIVE});if(this.deviceList.length0){this.message未扫描到设备;return;}consttargetDevicethis.deviceList[0];this.message发现设备:${targetDevice.deviceAddress};// 第二步创建 NearLinkDevice 实例传入安全配置this.nearLinkDevicenewnearLink.NearLinkDevice({deviceAddress:targetDevice.deviceAddress,securityConfig:buildSecurityConfig()// 传入安全配置});// 第三步注册状态回调this.nearLinkDevice.on(connectionStateChange,(state:number){console.info([NearLinkDemo] 连接状态变化:${state});if(statenearLink.ConnectionState.CONNECTED){this.message连接成功 (已加密);}elseif(statenearLink.ConnectionState.DISCONNECTED){this.message连接断开;}});// 第四步发起连接this.nearLinkDevice.connect();}catch(error){console.error([NearLinkDemo] 连接失败:${JSON.stringify(error)});this.message连接失败:${error.message};}}// 发送加密数据asyncsendEncryptedMessage(){if(!this.nearLinkDevice){this.message未连接设备;return;}try{constmessageHello, Secure NearLink!;// 这里不需要关心加密细节NearLink SDK 会自动处理awaitthis.nearLinkDevice.sendMessage({data:newTextEncoder().encode(message).buffer,dataLength:message.length});this.message加密数据已发送;}catch(error){console.error([NearLinkDemo] 发送失败:${JSON.stringify(error)});this.message发送失败:${error.message};}}}3. 接收端解密接收接收端不需要显式解密只要配对成功底层的安全通道已经建立。// 接收端代码片段与发送端类似重点在回调this.nearLinkDevice.on(messageReceived,(message:nearLink.Message){// 接收到的数据是蓝牙/星闪链路层已解密后的明文constdecodedMessagenewTextDecoder().decode(message.dataasArrayBuffer);console.info([NearLinkDemo] 收到解密后的消息:${decodedMessage});});为什么这样写更稳定安全配置在connect之前传入避免连接建立后无法再升级安全等级。状态变化和消息接收都使用事件回调方式避免阻塞 UI 线程。verifyHandshakeCallback提供了二次校验的机会适合在生产环境中做白名单过滤。踩坑记录最容易出问题的两个点坑1verifyHandshake 返回 false连接立即被拒现象配置了安全连接connect成功后几秒日志中出现onConnectionStateChange状态变为DISCONNECTED没有收到任何错误提示。原因很多人以为verifyHandshakeCallback只是一个通知返回false也没事。但实际上SDK 内部会等待这个回调的返回值如果返回false它会认为“握手失败”然后主动断开连接。这个回调不能用默认实现必须显式返回true。解决方案在verifyHandshakeCallback中一定返回true。如果需要对设备进行更精细的过滤可以在这个方法里同步查询数据库或白名单然后返回对应的布尔值。注意这个回调是同步执行的不要在它里面做耗时操作。坑2配对完成后认为“安全”却忘了 verifyHandshake现象设备 A 和 B 配对成功也能互相发数据。但是 A 设备被替换成一个冒牌设备后配对依然能成功。原因PairingMethod.JUST_WORKS模式本身不提供设备身份校验。它只负责密钥协商并不确认“对端的身份是否合法”。如果你的业务场景要求极高的安全性比如门锁与钥匙必须在verifyHandshakeCallback中加入额外的校验逻辑否则 JustWorks 本质上就是开放给所有人的。解决方案在verifyHandshakeCallback中可以通过device.deviceAddress来判断设备是否在白名单中。更安全的方式是在verifyHandshakeCallback触发时同步向设备发送一个预先约定好的随机挑战值只有能正确回应的设备才允许连接。verifyHandshakeCallback:(device:nearLink.NearLinkDevice):boolean{// 白名单校验constwhiteList[AA:BB:CC:DD:EE:FF,11:22:33:44:55:66];returnwhiteList.includes(device.deviceAddress);}最佳实践不要使用默认的SecurityConfig: 官方文档没有强制要求你传SecurityConfig但一旦不传默认就是无加密明文传输。直接写死最常用的JUST_WORKS GCM_128组合比依赖默认值更安全可靠。在onConnectionStateChange(disconnected)中清理资源: 当连接断开时nearLinkDevice实例已经不可用。你需要在断开回调中this.nearLinkDevice null;并取消所有监听否则在页面返回后容易出现cannot call sendMessage on destroyed object的错误。生产环境优先使用PASSKEY_ENTRY或NUMERIC_COMPARISON模式: 虽然JUST_WORKS开发方便但在对安全要求严苛的场景建议使用需要用户界面参与确认的配对方式如 PIN 码输入或键盘确认。这可以杜绝上一条坑2提到的“冒牌设备”问题前提是设备有显示屏或输入能力。Demo 入口下面是完整的Index.ets文件可以直接粘贴到新建的EntryAbility中使用。// entry/src/main/ets/pages/Index.etsimport{nearLink}fromkit.ConnectivityKit;EntryComponentstruct Index{Statestatus:string未连接;privatedevice:nearLink.NearLinkDevice|nullnull;build(){Column({space:20}){Text(状态:${this.status}).fontSize(18)Button(扫描 连接).onClick(()this.startConnection())Button(发送数据).onClick(()this.sendData()).enabled(this.device!null)Button(断开连接).onClick(()this.disconnect()).enabled(this.device!null)}.padding(20).width(100%).height(100%)}asyncstartConnection(){this.status扫描中...;constdevicesawaitnearLink.discoverDevices({discoveryMode:nearLink.DiscoveryMode.PASSIVE});if(devices.length0){this.status未找到设备;return;}consttargetdevices[0];this.devicenewnearLink.NearLinkDevice({deviceAddress:target.deviceAddress,securityConfig:{pairingMethod:nearLink.PairingMethod.JUST_WORKS,encryptType:nearLink.EncryptType.GCM_128,verifyHandshakeCallback:(d)true,displayKeyCallback:(k){console.info(Display key:${newUint8Array(k).join(,)});}}});this.device.on(connectionStateChange,(state){if(statenearLink.ConnectionState.CONNECTED){this.status已连接安全;}elseif(statenearLink.ConnectionState.DISCONNECTED){this.status已断开;this.devicenull;}});this.device.connect();this.status连接中...;}asyncsendData(){if(!this.device)return;try{constdataHello Secure NearLink;awaitthis.device.sendMessage({data:newTextEncoder().encode(data).buffer,dataLength:data.length});this.status数据已发送;}catch(e){this.status发送失败:${e.message};}}disconnect(){this.device?.disconnect();this.devicenull;}}FAQQ为什么我的设备不能和手机配对成功A请确认手机或开发板和远程设备都支持 NearLink 功能并且在配置SecurityConfig时pairingMethod选择一致。JUST_WORKS是“免密配对”如果一方必须要求输入 PIN 码就会出现配对失败。Q为什么verifyHandshakeCallback必须返回 true否则连接立即断开A这是 NearLink 安全模型的核心设计。verifyHandshakeCallback是应用层对设备的最后一次“同意”确认。如果返回 falseSDK 会认为应用层不认可这个设备为了保证安全直接终止连接。这不是 Bug是安全机制。Q官方文档里明明写了displayKeyCallback为什么我从来没被触发AdisplayKeyCallback主要用于 ‘Numeric Comparison’ 模式用于向用户展示一个数字以便双方确认。在JUST_WORKS模式下它几乎不会被调用。如果你确实需要它请将pairingMethod改为NUMERIC_COMPARISON然后在手机上实现一个弹窗显示这个数字同时让远程设备也显示相同的数字。