摘要
鸿蒙 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 |
两点必须注意:
device-ids是调试证书签名的必填项,缺了直接报udid not in module.json。取 UDID:hdc shell bm get --udid。- 共享库(
.so)不能声明权限——这条是官方明确的。要权限就签可执行程序。
3.2 应用侧要开的权限
这决定"你的应用能不能把这个二进制拉起来"。规则和应用的沙箱强弱有关:
- 弱沙箱应用:具备
ohos.permission.CUSTOM_SANDBOX(这是一条 ACL 权限) - 强沙箱应用:不具备该权限
具体见下一节的矩阵。
4. 场景对应:HiShell 里 vs 自己的应用里
这应该是你最关心的部分。同一个二进制,在这两个地方的待遇完全不同。
4.1 在 HiShell 里直接跑
就是连上设备、打开终端、敲 ./tool 那种。这里其实只要回答两个问题。
问题一:要不要交到别人手上跑?
| 场景 | 该用哪种签名 |
|---|---|
| 自己在自己设备上跑 | 自签名就够了 |
| 只在小圈子里互传(同事、朋友拿去跑一下) | 自签名也能跑——它不是设备绑定的 |
| 需要声明权限、或要在应用里被拉起 | 必须用调试证书 |
| 要公开发给大批用户 | 二进制证书 |
💡 这里有个常见误解,值得单独说清楚。
很多资料(包括本文早期版本)会说"自签名是签给这台机器用的,换台机器就得重签"。
这是不准确的。 自签名往.codesign段里写的是文件自身的摘要,
不含设备标识——所以文件本身是可移植的,拷到别的鸿蒙 PC 上照样通过验签。真正让"别人的机器跑不了"的,通常不是签名,而是下面这两件事:
- 依赖没跟着走:程序能跑,但它
dlopen的.so没签名——报错长得一样
(都是permission denied),看内核日志才能分辨。- 要声明权限 / 要在应用里被拉起:这时需要调试证书,而调试证书才真的绑设备
(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 |
| 要分发给大批用户 | 二进制证书 | 个人拿不到 |
⚠️ 两个高频坑:
- 签完必须
chmod +x。签名工具是往已有文件里写,会沿用旧权限位;cp过来的文件没有执行位,签完照样Permission denied,很容易误判成"签名没用"。- 别放在
/data/local/tmp。这个目录因为 SELinux 标签读不了,反而跑不起来。放家目录就行。
4.2 在自己的应用里拉起
应用里拉起二进制,除了签名本身,还要看应用的沙箱类型和有没有申请开关:
| 应用类型 | ALLOW_EXTERNAL_NATIVE_CODE |
normal bin | debug bin | adhoc bin |
|---|---|---|---|---|
| 弱沙箱 | 无 | ✅ | ✅ | ✅ |
| 弱沙箱 | 有 | ✅ | ✅ | ✅ |
| 强沙箱 | 无 | ❌ | ❌ | ❌ |
| 强沙箱 | 有 | ✅ | ✅ | ❌ |
读法是三句话:
- 弱沙箱应用怎样都能拉起独立二进制。 因为它握有
CUSTOM_SANDBOX这条 ACL 权限,本身就自带"允许外部原生代码"的身份。 - 强沙箱应用必须申请
ALLOW_EXTERNAL_NATIVE_CODE,否则一律拉不起来。 - 就算申请了,强沙箱也拉不起 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'"
相关文章
- 《二进制签名实战:三类签名、沙箱权限矩阵,以及一张调试证书的坑》 ——完整的踩坑记录:五种签名组合实测、内核日志取证、为什么 IDE 自动签名的证书不能用
- 《二进制签名解析:自签名、调试证书、二进制证书到底差在哪》 ——原理篇:从 OpenHarmony 源码看三种签名在文件结构和内核校验上的真实差异
说明:本文的规则部分整理自华为开发者支持与官方文档,命令均在实际设备
(HUAWEI MateBook Pro,OpenHarmony-7.0.0.105,API 26,2in1)上验证过。
如果只想快速决定用哪种签名,看到第 5 节就够了。
更正记录(2026-10-16):本文早期版本称"自签名是签给这台机器用的,
换台机器就得重签"——这个说法是错的,感谢 hqzing
指出。自签名往.codesign段里写的是文件自身的摘要与大小(flags置0x10
表示跳过证书链校验),不含任何设备标识,所以产物是可移植的。
真正绑定设备的是调试证书的device-ids。
详细的自签名字节布局与算法,见其《鸿蒙 PC 底层开发技术详解(七):二进制自签名算法的实现》 。