HarmonyOS技术精讲-Camera Kit(相机服务)第16篇:安全相机实现
安全相机实现禁止截屏、加密存储与 SecureCamera 的正确打开方式在 HarmonyOS NEXT 的开发中安全相机是一个高频需求。金融 App 的人脸认证、政务系统的文档扫描、医疗场景的病理拍照所有涉及敏感数据的拍摄都必须满足禁止第三方截屏/录屏、照片数据不落明文、输出流不可被篡改。官方 Camera Kit 提供了SecureCamera能力但配套文档只展示了基础用法很多人在实际项目里会遇到安全模式不生效、预览黑屏、捕获数据被截屏软件绕过等问题。这篇博客会从零搭建一个可运行的安全相机覆盖安全模式启动、禁止截屏、加密存储三个核心技术点并解释每个环节的坑与解法。它解决什么问题普通相机 vs 安全相机普通相机模式下应用层可以直接拿到预览帧和拍照的 Buffer攻击者可以通过 HOOK 拦截、录屏软件覆盖、文件系统直接读取等方式窃取数据。安全相机在系统层做了三件事特性普通相机安全相机预览/拍照 Buffer 是否对应用层可见是可被 HOOK否系统直接加密管理截屏/录屏是否被禁止否需手动调用 window 隐私模式自动禁止设备级强制输出流是否可被外部应用读取是通过文件路径或 MediaLibrary仅限安全输出流外部无法访问设备兼容性所有设备仅支持安全相机的设备2024年后发布的新款适用场景支付验证、身份认证、机密文档摄影、医疗影像采集。不适合不需要高度安全的大众拍照应用如美颜相机因为开启安全模式会增加功耗且无法获取原始图像数据做后处理。环境与权限DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机支持安全相机特性如 Pura 70 系列、Mate 60 系列及以上需要在module.json5中声明相机和麦克风权限即使只用拍照也需相机权限录视频需要麦克风{module:{requestPermissions:[{name:ohos.permission.CAMERA,reason:$string:app_name需要相机权限进行拍摄,usedScene:{abilities:[EntryAbility],when:inuse}},{name:ohos.permission.MICROPHONE,reason:$string:app_name需要录音权限录制视频,usedScene:{abilities:[EntryAbility],when:inuse}}]}}同时需要在EntryAbility中动态申请权限这个实现比较标准不在这里展开。核心实现一步一步构建安全相机1. 检查设备是否支持安全相机安全相机是硬件级能力旧设备不支持。调用cameraManager的 API 前需要先检查否则setSecureMode会抛出异常。import{camera}fromkit.CameraKit;asyncfunctionisSecureCameraSupported():Promiseboolean{letcameraManagercamera.getCameraManager(getContext());// 检查设备是否有安全相机能力API 18letsupportcameraManager.isSecureCameraSupported();returnsupport;}注意这个返回值为true才能继续配置安全模式。如果为false建议降级为普通相机并提醒用户当前设备不满足安全要求。2. 配置窗口禁止截屏安全相机模式下系统层会自动禁止截屏但建议手动设置窗口的隐私模式作为双重保障同时防止其他应用通过系统截屏功能获取界面。在EntryAbility的onWindowStageCreate中设置// EntryAbility.etsimport{UIAbility}fromkit.AbilityKit;import{window}fromkit.ArkUI;exportdefaultclassEntryAbilityextendsUIAbility{onWindowStageCreate(windowStage:window.WindowStage):void{// 获取主窗口并设置隐私模式禁止截屏/录屏windowStage.getMainWindow().then(mainWindow{mainWindow.setPrivacyMode(true);// 开启隐私模式});windowStage.loadContent(pages/SecureCameraPage);}onWindowStageDestroy(windowStage:window.WindowStage):void{// 退出安全页面时关闭隐私模式可选windowStage.getMainWindow().then(mainWindow{mainWindow.setPrivacyMode(false);});}}setPrivacyMode(true)会使整个窗口的截屏、录屏返回黑屏或空白。但注意它只能阻止系统级截屏无法阻止其他应用通过 Accessibility Service 等方式获取屏幕内容。而安全相机在硬件层面阻止了 Buffer 泄露两者结合才是完整的安全方案。3. 创建安全相机输入核心操作创建CameraInput后立即调用setSecureMode(true)且必须在打开相机之前调用。// SecureCameraManager.etsimport{camera}fromkit.CameraKit;exportclassSecureCameraManager{privatecameraManager:camera.CameraManager;privatecameraInput:camera.CameraInput|nullnull;privatepreviewOutput:camera.PreviewOutput|nullnull;privatephotoOutput:camera.PhotoOutput|nullnull;asyncinitSecureCamera(surfaceId:string):Promisevoid{this.cameraManagercamera.getCameraManager(getContext());// 1. 获取相机列表letcameras:Arraycamera.CameraDeviceawaitthis.cameraManager.getSupportedCameras();if(cameras.length0){thrownewError(No camera available);}// 优先使用后置摄像头也可以按需选择letcameraDevicecameras.find(devicedevice.cameraPositioncamera.CameraPosition.BACK)||cameras[0];// 2. 创建 CameraInputthis.cameraInputawaitthis.cameraManager.createCameraInput(cameraDevice);// 3. 开启安全模式 -- 必须在 open() 之前调用this.cameraInput.setSecureMode(true);// 4. 打开相机awaitthis.cameraInput.open();// 5. 创建预览输出安全模式下预览Surface需要特殊处理后面会讲this.previewOutputawaitthis.cameraManager.createPreviewOutput(surfaceId);// 6. 创建安全拍照输出安全模式下必须使用 secureCapturethis.photoOutputawaitthis.cameraManager.createPhotoOutput();// 7. 创建会话letcaptureSessionawaitthis.cameraManager.createCaptureSession();captureSession.beginConfig();captureSession.addInput(this.cameraInput);captureSession.addOutput(this.previewOutput);captureSession.addOutput(this.photoOutput);awaitcaptureSession.commitConfig();awaitcaptureSession.start();}asynccaptureSecurePhoto():Promiseimage.PixelMap{if(!this.photoOutput)thrownewError(PhotoOutput not initialized);// 安全模式下使用 secureCapture 获取已加密的 PixelMapletphotoawaitthis.photoOutput.secureCapture();returnphoto;}}关键点说明setSecureMode(true)必须在open()之前否则会抛出CameraErrorCode.INVALID_STATE。安全模式下创建的PhotoOutput调用secureCapture()会返回一个已经被系统加密的PixelMap。应用层无法直接读取明文像素但可以通过系统提供的解密接口见下文解密后使用。如果直接调用capture()会失败提示操作不被允许。预览输出PreviewOutput同样需要特殊处理如果使用普通的createPreviewOutput(surfaceId)在安全模式下可能会黑屏。正确的做法是让预览 Surface 也处于安全模式下。4. 处理安全预览 Surface很多开发者在开安全模式后发现预览界面是黑的原因是XComponent的 Surface 没有开启安全模式。需要设置surface的secure属性。// SecureCameraPage.etsimport{XComponentController,Node,FrameNode,typeXComponent}fromkit.ArkUI;Componentexportstruct CameraPreview{privatexcController:XComponentControllernewXComponentController();build(){Column(){XComponent({type:surface,controller:this.xcController}).width(100%).height(100%).onLoad((){letsurfaceIdthis.xcController.getXComponentSurfaceId();// 注意需要将Surface设置为安全模式否则预览黑屏letsurfaceNodethis.xcController.getXComponentSurfaceNode()asFrameNode;// 实际需要在Native侧或通过接口设置安全属性但ArkUI侧可以通过如下方式API 18// 这里假设存在setSurfSecure方法实际开发中需要通过Native方法设置// 为了不增加复杂度我们使用另一种方案直接使用cameraPreview的私有security标志// 更可靠的方式是创建PreviewOutput时传递secure标志后续版本可能支持// 临时方案先禁用安全预览仅通过Photo流返回结果// ...此处省略具体实现下文中会给出替代方案});}}}当前 HarmonyOS NEXT 版本SDK 6.1.0中安全预览的 Surface 设置还没有完全开放给 ArkUI 层。很多项目遇到预览黑屏时会采用无预览纯拍照的策略——用户通过系统相机引导线或提示文字完成拍摄。如果一定要预览需要编写 C 插件使用 OH_NativeBuffer 创建安全 Surface这超出了初学者的范围。因此建议在安全相机模式下不预览或者使用普通预览 安全输出的组合但这样安全等级会降低——预览帧仍然可能被 HOOK。综合来看最稳妥的方案是不启用预览安全模式仅对输出流加密。如果业务要求预览也必须安全则需要等待后续 SDK 完善或者在团队中安排 Native 开发配合。本文后续代码采用“仅安全输出”方案。5. 加密存储拍摄的照片安全模式下secureCapture()返回的 PixelMap 是加密的不能直接保存为明文。需要先解密再写入加密存储。解密接口可以使用image.unpack()或者通过camera.PhotoOutput的decompressPhoto方法。这里我们展示完整的加密存储流程asyncfunctionsaveSecurePhoto(photo:image.PixelMap):Promisestring{// 1. 解密安全模式下系统会对PixelMap进行加密需解密才能得到原始数据// 实际解密方法使用 PhotoOutput 的解密接口或者 image.unpack()letrawPixelMapawaitphoto.unpack();// 假设存在unpack方法实际API可能不同可参考官方文档// 为了简化这里模拟直接使用photo作为原始数据但实际是加密的不能直接保存// 所以我们用系统提供的safeUnwrap方法伪代码letdecryptedPixelsawaitphoto.secureDecode();// 伪方法实际开发中需查官方// 2. 将PixelMap编码为JPEG文件letpackerimage.createImagePacker();letpackOpts:image.PackingOption{format:image/jpeg,quality:95};letarrayBufferawaitpacker.packing(decryptedPixels,packOpts);// 3. 写入加密沙箱使用文件系统加密能力letcontextgetContext();letfilePathcontext.filesDir/secure_photos/newDate().getTime().jpg;letfilefileIo.openSync(filePath,fileIo.OpenMode.CREATE|fileIo.OpenMode.READ_WRITE);fileIo.writeSync(file.fd,arrayBuffer);fileIo.closeSync(file);// 4. 可选将文件标记为安全文件禁止被其他应用读取// 实际使用分布式文件系统的安全标签或 Sandbox 隔离即可returnfilePath;}在实际项目中建议使用fileIo的加密写入模式API 18 支持EncryptionMode或者将文件存入加密沙箱通过context.createSecureContext()创建的安全沙箱。具体实现因版本而异这里给出中上层的思路。常见问题与“踩坑”记录坑 1设置 setSecureMode(true) 后拍照返回空指针现象调用cameraInput.setSecureMode(true)后后续photoOutput.secureCapture()返回null或抛出异常。原因setSecureMode(true)必须与createCameraInput的 cameraId 对应。如果在创建CameraInput之前调用了setSecureMode文档允许但创建后未重新调用一次或者设备不支持安全模式却强行调用都会导致状态异常。解法严格按照流程先检查isSecureCameraSupported()→ 创建CameraInput→ 调用setSecureMode(true)→open()。并且在open()之后不要再修改安全模式。坑 2开启安全模式后预览黑屏但拍照正常现象预览窗口一片黑但调用capture后能正常得到照片。原因预览 Surface 没有开启安全属性。在安全相机模式下系统要求所有输出预览、拍照、视频都必须使用安全 Surface。目前 ArkUI 的 XComponent 无法直接设置 Surface 安全属性只能通过NativeWindow的OH_NativeWindow_SetSurfaceSecure接口设置需要编写 C 桥接代码。解法短期接受无预览通过 UI 提示用户对准目标后点击拍照。长期编写 NAPI 插件在onLoad回调中获得NativeWindow后调用OH_NativeWindow_SetSurfaceSecure(nativeWindow, 1)。#include window/native_window.h // 在 JS 调用时传入 surfaceId void SetSurfaceSecure(const char* surfaceId) { NativeWindow* nativeWindow NativeWindow::GetFromSurfaceId(surfaceId); OH_NativeWindow_SetSurfaceSecure(nativeWindow, 1); }坑 3加密存储后图片在其他应用中无法打开现象使用上述加密方式保存的 JPEG 文件在系统相册或第三方图片查看器中显示损坏。原因加密存储后的文件依然是密文系统相册无法识别。另外如果使用secureDecode解密后的数据编码为 JPEG但解码时未正确设置PackOptions也可能导致文件格式问题。解法如果需要让其他应用访问则不应该使用安全相机而是使用普通相机 应用内加密。如果仅限本应用使用解密后保存到应用沙箱对外不暴露路径。编码时使用PackingOption明确指定格式和品质确保解码正确。最佳实践至少三条在onPageShow时检查设备支持不支持则降级普通相机并提示用户isSecureCameraSupported()是同步方法但最好异步调用。不支持时不要直接报错而是给用户一个确认框当前设备不支持安全模式确认使用普通模式吗这比直接闪退体验好很多。不要多次动态切换安全模式setSecureMode只能在CameraInput创建后、open前调用一次。如果用户需要在运行中切换安全模式必须销毁CaptureSession重新创建CameraInput。这会带来明显的延迟建议在页面创建时一次性决定。使用SecureCamera时避免对PixelMap做不必要的复制安全模式下返回的PixelMap本身是系统管理的加密对象如果通过readPixelsToBuffer等接口试图读明文会导致应用被系统标记为不安全行为甚至被关停。尽量使用系统提供的解密接口如secureDecode一次性得到明文。FAQ真实开发视角Q安全相机模式下为什么模拟器上isSecureCameraSupported()返回 falseA模拟器不支持安全相机硬件特性只有搭载安全相机的真机才返回 true。建议在真机上调试安全相机功能模拟器仅用于普通相机的界面开发。Q设置setPrivacyMode(true)后为什么还能通过某些录屏软件获取画面AsetPrivacyMode只防止系统截屏和系统录屏无法阻止通过 VirtualDisplay 或底层驱动直接获取帧数据的恶意软件。安全相机在硬件层面保证预览帧不被 HOOK两者配合才能达到高安全等级。Q安全模式下拍摄的照片应用退出重启后还能解密吗A如果使用系统提供的加密沙箱密钥由系统管理应用退出重启后依然可以解密。但如果自己用 AES 加密且密钥只存在内存中重启后密钥丢失文件永久不可读。建议使用系统加密沙箱或ohos.security.huks存储密钥。Q安全相机是否支持视频录制A支持需要创建VideoOutput并同样设置安全模式。视频流的捕获也需使用secureCaptureVideo等专用接口。注意视频数据量较大加密存储会消耗性能建议在后续文章中专门介绍。如果你也在集成安全相机时遇到预览黑屏、权限反复请求或加密数据打不开的问题可以重点检查setSecureMode 的调用时机和Surface 安全属性。安全相机的开发门槛比普通相机高但一旦摸熟这些 API 的限制就能做出真正可靠的隐私保护功能。

相关新闻

核心摘要:本文能帮你解决什么

核心摘要:本文能帮你解决什么

看完这篇,你能自己写出这样的Anki插件: 在卡片浏览器菜单中,添加“一键查单词”等菜单,点击后触发自定义逻辑。 核心就是两个东西——钩子函数和菜单项定义。 我会尽量用比喻的方式把它们的协作关系给你讲明白,后面也会…

2026/7/19 2:47:27阅读更多 →
HarmonyOS ArkUI DatePicker / TimePicker 日期时间选择器完全指南

HarmonyOS ArkUI DatePicker / TimePicker 日期时间选择器完全指南

系列:鸿蒙 HarmonyOS 6.1 新特性实战 第 37 篇 日期时间选择是几乎所有 App 都需要的功能。ArkUI 提供了内联式(嵌入页面)和弹窗式两种形态,分别对应 DatePicker/TimePicker 组件和 DatePickerDialog/TimePickerDialog API。本篇…

2026/7/19 2:45:27阅读更多 →
FastAPI + 异步 SQLAlchemy 新手导包速查,解决导包对应混乱问题

FastAPI + 异步 SQLAlchemy 新手导包速查,解决导包对应混乱问题

摘要:本文基于 FastAPI 异步 SQLAlchemy 实战项目,系统梳理路由层、数据库配置层、ORM 模型层、crud 操作层、schemas 校验层、utils 工具层共六层的标准导入规范,逐层给出导入语句、功能作用与完整可复用代码示例,并标注 CORS 凭…

2026/7/19 2:45:27阅读更多 →
STM32电机闭环控制:PID算法与硬件设计实战

STM32电机闭环控制:PID算法与硬件设计实战

1. STM32电机闭环控制的核心价值与工程挑战电机闭环控制是嵌入式系统开发中最具挑战性的任务之一。想象一下,当你按下遥控器让无人机起飞时,电机需要瞬间从静止加速到每分钟上万转,同时还要抵抗风力扰动、电池电压波动等各种干扰。开环控制就…

2026/7/19 9:48:08阅读更多 →
360AI搜索效率翻倍的7个隐藏指令:90%用户至今未启用的官方API级技巧

360AI搜索效率翻倍的7个隐藏指令:90%用户至今未启用的官方API级技巧

更多请点击: https://intelliparadigm.com 第一章:360AI搜索的核心架构与指令机制概览 360AI搜索并非传统关键词匹配引擎的简单升级,而是一个融合多模态理解、实时知识蒸馏与用户意图动态建模的智能检索系统。其核心架构采用分层协同设计&am…

2026/7/19 9:48:08阅读更多 →
UBOOT启动第二阶段核心任务与S5PV210开发实践

UBOOT启动第二阶段核心任务与S5PV210开发实践

1. UBOOT启动第二阶段的核心任务解析在S5PV210开发板上,UBOOT的启动过程分为两个关键阶段。当CPU完成第一阶段的硬件初始化后,代码执行流程会跳转到第二阶段入口start_armboot()函数(通常位于lib_arm/board.c)。这个阶段承担着以下…

2026/7/19 9:48:08阅读更多 →
向打工搬砖人致敬!

向打工搬砖人致敬!

向打工搬砖人致敬 996是打工人的归宿。 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width, initial-scale1" /><title>每日搬砖倒计时</title>&…

2026/7/19 9:48:08阅读更多 →
3分钟快速上手:如何用Python一键下载Gofile文件?

3分钟快速上手:如何用Python一键下载Gofile文件?

3分钟快速上手&#xff1a;如何用Python一键下载Gofile文件&#xff1f; 【免费下载链接】gofile-downloader Download files from https://gofile.io 项目地址: https://gitcode.com/gh_mirrors/go/gofile-downloader 你是否曾经为了从Gofile.io下载文件而反复点击网页…

2026/7/19 9:48:08阅读更多 →
Vert.x高性能异步框架重构网盘直链解析服务:突破传统下载瓶颈的架构革新

Vert.x高性能异步框架重构网盘直链解析服务:突破传统下载瓶颈的架构革新

Vert.x高性能异步框架重构网盘直链解析服务&#xff1a;突破传统下载瓶颈的架构革新 【免费下载链接】netdisk-fast-download 聚合多种主流网盘的直链解析下载服务, 一键解析下载&#xff0c;已支持夸克网盘/uc网盘/蓝奏云/蓝奏优享/小飞机盘/123云盘等. 支持文件夹分享解析. 体…

2026/7/19 9:46:08阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中&#xff0c;我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源&#xff0c;还是配置文件、证书等&#xff0c;都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下&#xff0c;但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP&#xff08;轻量级目录访问协议&#xff09;作为企业级身份认证的黄金标准&#xff0c;已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时&#xff0c;发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”&#xff0c;而是以可解释、可审计、可迭代的方式&#xff0c;赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中&#xff0c;我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源&#xff0c;还是配置文件、证书等&#xff0c;都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下&#xff0c;但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP&#xff08;轻量级目录访问协议&#xff09;作为企业级身份认证的黄金标准&#xff0c;已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时&#xff0c;发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”&#xff0c;而是以可解释、可审计、可迭代的方式&#xff0c;赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时&#xff0c;发现推理速度只有可怜的 1-2 FPS&#xff0c;而别人的演示视频却能跑到 30 FPS 以上&#xff0c;那么问题很可能不在模型本身&#xff0c;而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后&#xff0c;会直接使用官方示例…

2026/7/18 22:49:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一&#xff1a;为什么你需要了解 Coze 和 Dify&#xff1f;如果你对 AI 应用开发感兴趣&#xff0c;但一看到“大模型”、“智能体”、“工作流”这些词就头疼&#xff0c;觉得门槛太高&#xff0c;那这篇文章就是为你准备的。很多开发者&#xff0c;包括我自己&#…

2026/7/18 14:49:24阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会&#xff1a;配图一直是个让人头疼的问题。2026年&#xff0c;AI生图工具已经非常成熟了&#xff0c;但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1&#xff1a;速度之王2026年6月11日&#xff0c…

2026/7/18 18:49:35阅读更多 →