摘要

鸿蒙 PC(2in1)上,没有合法签名的二进制跑不起来——直接 ./tool 就是 Permission denied,不管你是从哪台机器交叉编译过来的。

但真去看文档,第一步就会卡住:

  • 文档里有三种签名,名字很像,我该选哪个?
  • 签名要证书,证书从哪来?要不要钱?个人能不能申请?
  • 签完之后还是跑不起来,是签错了还是权限没给?
  • 同样是这个二进制,我在 HiShell 里敲能跑,为什么放到我自己的应用里就拉不起来?

这篇就是回答这些的。不涉及原理(原理在解析篇 ),只给你能直接查的表和能直接抄的命令。

一句话结论:不要权限、也不进沙箱应用的话,自签名就够了——而且签好的文件可以拷到别的鸿蒙 PC 上直接跑(adhoc 不绑设备);一旦需要声明权限、或要在应用里被拉起来,就得用自己申请的调试证书签名(这个才绑 UDID);要公开发给大批用户,才是华为认证开发者那本二进制证书。


1. 一张表看完三种签名

自签名(adhoc) 调试证书签名 二进制证书签名
证书从哪来 不需要证书 华为颁发,自己申请 华为颁发,面向认证开发者
门槛 零 有开发者账号即可 个人开发者基本拿不到
能声明权限吗 ❌ 写了也不生效 ✅(共享库 .so 除外) ✅
绑设备吗 不绑,文件可拷到别的鸿蒙 PC 直接跑 绑,只能给 device-ids 里的设备 不绑
能给别人用吗 能(但对方仍受权限/沙箱限制) 只能给 device-ids 名单里的设备 随便分发
设备要开开发者模式吗 — 要 不要
典型用途 本机编译自己用、小范围互传 开发调试、需权限的场景 正式发布
一句话 能跑,但没身份 能干活的开发凭证 正式身份证

三种签名签出来的文件结构其实是同一套,区别在"谁来给你作证"。想搞清楚这一点,看解析篇 。


2. 证书从哪来

这是最容易绕晕的部分,因为三种签名里只有两种真的用到证书。

2.1 自签名:根本没有证书这回事

命令里连证书参数都不用传:

binary-sign-tool sign \
  -inFile tool \
  -outFile tool.signed -selfSign 1
chmod 755 tool.signed

工具会算一个"文件有没有被改过"的校验值写进去。它证明的是"这个文件没被篡改",不是"这是谁做的"。 没有身份,自然也就没有权限可谈。

补一句容易误解的:自签名不绑定设备。它往文件末尾的 .codesign 段里放的是
这份文件自身的摘要与大小,不含任何设备标识——所以签好的文件拷到别的鸿蒙 PC 上
一样能跑,不需要重签。真正绑定设备的是调试证书(device-ids 里那份 UDID 名单)。
两者的区别见 4.1。

2.2 调试证书:华为发的,但必须自己申请

调试证书是华为签发的,个人开发者申请得到。但它有两条获取路径,其中一条是坑:

获取方式 能不能用于二进制签名
✅ 自己申请的调试证书 能用
❌ DevEco Studio「自动签名」下发的 不能用

DevEco Studio 的自动签名看起来最省事——在 IDE 里点一下,.p12 和 .cer 就生成好了。但用它签出来的二进制,执行时会被内核拒掉,报的是 Operation not permitted,在应用日志里只能看到 errno = 129,真正的原因得翻内核日志才看得到。

我们用同一个文件、同一个 module.json、同一台设备做了对照:唯一变量就是证书,换掉就能跑。所以这不是姿势问题,是那张证书本身不行。完整证据链在实战篇 里。

结论记住一句话:二进制签名一律用自己申请的调试证书,别用 IDE 自动签名给的。

2.3 二进制证书:个人开发者基本拿不到

“二进制证书"是华为发给认证开发者的,用来做面向用户的大范围分发:不限设备、不用开发者模式。对绝大多数个人开发者来说这条路暂时走不通——官方文档里也把它标注为"面向华为认证的已知开发者”。

这也是为什么社区里的实践几乎都集中在前面两条路上。


3. 需要什么权限

这里有两层权限,它们不是一回事,很多人会混:

谁声明 写在哪 决定什么
二进制自己的权限 二进制 module.json → 签进 .permission 段 这个程序能不能用某些底层能力
应用侧的权限 拉起它的应用 应用的 module.json 应用能不能拉起这个二进制

3.1 二进制自己声明权限

只有调试证书签名和二进制证书签名的产物,声明的权限才生效;自签名(adhoc)写多少都白写。

写法是在 module.json 里声明,签名时通过 -moduleFile 传进去:

{
  "requestPermissions": [
    // 权限全名见下表,替换 <权限名>
    { "name": "<权限名 1>" },
    { "name": "<权限名 2>" }
  ],
  "debug-info": {
    "device-id-type": "udid",
    "device-ids": ["<你的设备 UDID>"]
  }
}

几个常见的对应关系:

你想要的能力 对应的权限名
自己加载动态库 ohos.permission.kernel.LOAD_INDEPENDENT_LIBRARY
继承父进程权限 ohos.permission.INHERIT_PARENT_PERMISSION
JIT / 可写可执行内存 ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY
跳过动态库校验 ohos.permission.kernel.IGNORE_LIBRARY_VALIDATION
匿名可执行内存 ohos.permission.kernel.EXEMPT_ANONYMOUS_EXECUTABLE_MEMORY

两点必须注意:

  1. device-ids 是调试证书签名的必填项,缺了直接报 udid not in module.json。取 UDID:hdc shell bm get --udid。
  2. 共享库(.so)不能声明权限——这条是官方明确的。要权限就签可执行程序。

3.2 应用侧要开的权限

这决定"你的应用能不能把这个二进制拉起来"。规则和应用的沙箱强弱有关:

  • 弱沙箱应用:具备 ohos.permission.CUSTOM_SANDBOX(这是一条 ACL 权限)
  • 强沙箱应用:不具备该权限

具体见下一节的矩阵。


4. 场景对应:HiShell 里 vs 自己的应用里

这应该是你最关心的部分。同一个二进制,在这两个地方的待遇完全不同。

4.1 在 HiShell 里直接跑

就是连上设备、打开终端、敲 ./tool 那种。这里其实只要回答两个问题。

问题一:要不要交到别人手上跑?

场景 该用哪种签名
自己在自己设备上跑 自签名就够了
只在小圈子里互传(同事、朋友拿去跑一下) 自签名也能跑——它不是设备绑定的
需要声明权限、或要在应用里被拉起 必须用调试证书
要公开发给大批用户 二进制证书

💡 这里有个常见误解,值得单独说清楚。

很多资料(包括本文早期版本)会说"自签名是签给这台机器用的,换台机器就得重签"。
这是不准确的。 自签名往 .codesign 段里写的是文件自身的摘要,
不含设备标识——所以文件本身是可移植的,拷到别的鸿蒙 PC 上照样通过验签。

真正让"别人的机器跑不了"的,通常不是签名,而是下面这两件事:

  1. 依赖没跟着走:程序能跑,但它 dlopen 的 .so 没签名——报错长得一样
    (都是 permission denied),看内核日志才能分辨。
  2. 要声明权限 / 要在应用里被拉起:这时需要调试证书,而调试证书才真的绑设备
    (device-ids 里的 UDID 名单)。

所以判断标准不是"给不给别人",而是**“要不要权限、要不要被沙箱应用拉起”**。

如果只是想把一个不需要额外权限的小工具传给同事直接跑,其实连调试证书都不必:
把文件发过去(连同它依赖的 .so),对方 chmod +x 即可。不需要登记对方 UDID。

反过来,一旦涉及权限或沙箱应用,就得走调试证书,那时才需要 UDID:

# 1) 让同事在自己设备上执行,
#    把 UDID 发给你
hdc shell bm get --udid

# 2) 把对方 UDID 写进 module.json 的
#    device-ids(数组,可以填多个)
#    "debug-info": {
#      "device-id-type": "udid",
#      "device-ids": [
#        "<同事的 UDID>",
#        "<另一个同事的 UDID>"
#      ]
#    }

# 3) 用这个 module.json 重新签名,
#    再把签好的文件发给同事

对方那边只需要打开开发者模式,不需要任何证书或工具。

问题二:需要声明权限吗?

你想要的 该用哪种签名 还要做什么
自己写的小工具,能用就行 自签名 chmod 755
要声明权限(JIT、独立加载库…) 调试证书 填 device-ids + 开开发者模式
要给同事的设备声明权限跑 调试证书 把对方设备的 UDID 加进 device-ids
要分发给大批用户 二进制证书 个人拿不到

⚠️ 两个高频坑:

  1. 签完必须 chmod +x。签名工具是往已有文件里写,会沿用旧权限位;cp 过来的文件没有执行位,签完照样 Permission denied,很容易误判成"签名没用"。
  2. 别放在 /data/local/tmp。这个目录因为 SELinux 标签读不了,反而跑不起来。放家目录就行。

4.2 在自己的应用里拉起

应用里拉起二进制,除了签名本身,还要看应用的沙箱类型和有没有申请开关:

应用类型 ALLOW_EXTERNAL_NATIVE_CODE normal bin debug bin adhoc bin
弱沙箱 无 ✅ ✅ ✅
弱沙箱 有 ✅ ✅ ✅
强沙箱 无 ❌ ❌ ❌
强沙箱 有 ✅ ✅ ❌

读法是三句话:

  1. 弱沙箱应用怎样都能拉起独立二进制。 因为它握有 CUSTOM_SANDBOX 这条 ACL 权限,本身就自带"允许外部原生代码"的身份。
  2. 强沙箱应用必须申请 ALLOW_EXTERNAL_NATIVE_CODE,否则一律拉不起来。
  3. 就算申请了,强沙箱也拉不起 adhoc bin。 因为自签名没有身份,参与不了"这个二进制是谁给的、能不能信"的判断。

4.3 一个真实例子:brew 装出来的命令行软件

这条特别容易踩,因为它正好把上面两张表串起来了。

鸿蒙 PC 上社区的 Harmonybrew(Homebrew 的鸿蒙移植)装出来的命令行软件,基本都是 adhoc bin(自签名)。

原因很直接:brew install 是把东西下载到你这台机器上,然后就地自签一次。
它不会、也没法替你去申请二进制证书或调试证书——那要绑定开发者身份,调试证书还得逐个登记设备 UDID。

一个常见但错误的说法:把 brew 装好的程序拷贝给别人,对方跑不起来,
是因为"自签只对签名时那台机器成立"。

这不准确。 自签名不含设备信息,文件本身是可以拷走的。
拷贝后跑不起来,通常是依赖没跟着走(它要的 .so 没签或路径变了),
或者是在沙箱应用里被拉起——这两种情况的报错都是 permission denied,
光看终端分不出来,得看内核日志(hilog -t kmsg,按进程名或 xpm 过滤)。

真要重签的话,先摘掉旧的签名段再签,流程见 4.4。

于是同一个 brew install 装出来的命令,在不同地方待遇不一样:

你在哪执行 能不能跑 原因
HiShell(系统自带终端) ✅ 它是系统终端,不走应用的沙箱判定
第三方终端模拟器 / 工具类应用 取决于它有没有弱沙箱权限 见上面 4.2 的矩阵

关键结论:在 HiShell 之外的应用里要执行 brew 装的东西,那个应用必须拿到弱沙箱权限
ohos.permission.CUSTOM_SANDBOX。

这里有个反直觉的点:申请 ALLOW_EXTERNAL_NATIVE_CODE 是没用的。
回到 4.2 的矩阵——强沙箱应用就算申请了这个开关,依然拉不起 adhoc bin。
对 brew 这类自签产物来说,唯一的出路就是弱沙箱。

💡 所以如果你要做一个终端模拟器,或者任何需要执行外部命令行程序的应用(编辑器、工具集、AI CLI 的宿主…),
第一个要确认的问题不是"界面怎么做",而是"我能不能申请到 ohos.permission.CUSTOM_SANDBOX"。
这是一条 ACL 权限,拿不到的话,brew install 装的东西在你的应用里一个都跑不起来。
社区里已经有过同类提问:workbuddy 为什么不能用 brew 安装的依赖?

4.4 别人发来的二进制,怎么在自己机器上用

先把结论说清楚:别人发来的、已经自签过的文件,通常直接就能跑——
自签名不含设备信息,不需要你重签。

需要重签的是这几种情况:

  • 对方发来的是未签名的裸二进制;
  • 文件在传输中被改过(校验值对不上);
  • 你想换一种签名(比如从 adhoc 换成调试证书,以便声明权限)。

重签时会遇到一个坑:不能直接签——文件里还带着旧签名段,工具会直接报
E [SignElf] .codesign section already exists:

正确顺序是先摘掉旧签名段,再签:

# 1) 摘掉别人留下的签名段
#    (ohos-sdk 自带 llvm-objcopy,
#      在 native/llvm/bin/)
llvm-objcopy --remove-section .codesign \
  tool.recv tool.unsigned

# 2) 在本机重新自签
binary-sign-tool sign \
  -inFile tool.unsigned \
  -outFile tool.signed -selfSign 1

# 3) 补回执行位
#    (objcopy 的输出文件不带执行位)
chmod 755 tool.signed

⚠️ 第 3 步别漏。漏了就会在"签名明明成功了"之后收到一个 Permission denied,
很容易又绕回"是不是签名没用"的老坑里。

同样的道理也适用于别人给的预编译 .so:从构建机带过来的动态库如果在你这台机器上加载失败,
常见原因是它根本没签名(构建机没开链接器签名),而不是签名"过期"。
先按上面这套把 .so 自签一遍,比去查"是不是找不到库、是不是路径不对"要快得多。

4.5 最关键的一条对应关系

把上面两张表连起来看,会得到一个很容易踩的结论:

用 DevEco Studio 的 clang 编译出来的二进制,默认就是 adhoc bin,不是 debug bin。

所以如果你的目标是"在(强沙箱)应用里拉起自己编译的二进制",从编译那一刻起就已经注定失败了——必须补一步调试证书签名,把它从 adhoc bin 变成 debug bin。

一句话对照:

你手上是 在 HiShell 里 在弱沙箱应用里 在强沙箱应用里
adhoc bin(交叉编译默认) ✅ ✅ ❌
debug bin(调试证书签过) ✅ ✅ ✅
normal bin(二进制证书签过) ✅ ✅ ✅

5. 怎么选:三步

第 1 步:要公开发给大批用户吗?
    └─ 是 ──→ 二进制证书(需华为认证)

第 2 步:下面两条,占任意一条吗?
    · 要声明权限
    · 要在(强沙箱)应用里被拉起
    ├─ 一条都不占
    │     ──→ 自签名(adhoc)
    │         (自己用、给同事互传都行
    │           ——文件拷过去就能跑)
    └─ 占了任意一条
          ──→ 自己申请的调试证书
              (这个才需要对方 UDID)

对照成表格:

你的情况 用哪种 关键动作
不要权限、不进沙箱应用 自签名 -selfSign 1 + chmod 755
把工具给同事跑(同样不要权限) 自签名 直接把文件和依赖的 .so 发过去
要声明权限 调试证书 自己申请的证书 + device-ids + 开发者模式
要在应用里被拉起 看第 4.2 节矩阵 注意交叉编译默认是 adhoc bin
要分发给大量用户 二进制证书 需华为认证

💡 判断的关键不是"给不给别人",而是**“要不要权限 / 要不要被沙箱应用拉起”**。
自签名不绑设备,所以"给别人"本身并不构成必须用调试证书的理由。

⚠️ 还有一种情况,签名的类型根本轮不到你选:要在自己的应用里跑 brew 装好的命令行软件。
它是 brew install 时在你本机下载后就地自签的——既没有二进制证书,也没有调试证书,
天生就是 adhoc bin。你能做的只有让你自己的应用拿到弱沙箱权限
ohos.permission.CUSTOM_SANDBOX(见 4.3)。


6. 命令速查

# ---- 取设备 UDID(调试证书签名要用)----
hdc shell bm get --udid

# ---- 自签名(adhoc)----
binary-sign-tool sign \
  -inFile tool \
  -outFile tool.signed -selfSign 1
chmod 755 tool.signed

# ---- 调试证书签名 ----
binary-sign-tool sign -keyAlias <alias> \
  -signAlg SHA256withECDSA \
  -appCertFile  <cert.cer> \
  -keystoreFile <key.p12> \
  -keyPwd <pwd> -keystorePwd <pwd> \
  -inFile tool -outFile tool.signed \
  -moduleFile module.json
chmod 755 tool.signed

# ---- 别人发来的二进制 ----
# 已在别机签过:先摘签名段再重签
llvm-objcopy --remove-section .codesign \
  tool.recv tool.unsigned
binary-sign-tool sign \
  -inFile tool.unsigned \
  -outFile tool.signed -selfSign 1
chmod 755 tool.signed

# ---- 看签成了什么(权限段+证书链)----
binary-sign-tool display-sign \
  -inFile tool.signed

# ---- 跑不起来时看日志 ----
hdc shell "hilog -x | grep -E \
  'code_protect|CODE_SIGN'"

相关文章

说明:本文的规则部分整理自华为开发者支持与官方文档,命令均在实际设备
(HUAWEI MateBook Pro,OpenHarmony-7.0.0.105,API 26,2in1)上验证过。
如果只想快速决定用哪种签名,看到第 5 节就够了。

更正记录(2026-10-16):本文早期版本称"自签名是签给这台机器用的,
换台机器就得重签"——这个说法是错的,感谢 hqzing
指出。自签名往 .codesign 段里写的是文件自身的摘要与大小(flags 置 0x10
表示跳过证书链校验),不含任何设备标识,所以产物是可移植的。
真正绑定设备的是调试证书的 device-ids。
详细的自签名字节布局与算法,见其《鸿蒙 PC 底层开发技术详解(七):二进制自签名算法的实现》 。