摘要

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

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

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

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

一句话结论:只在自己设备上跑,自签名就够了;只要需要交到别人手上(哪怕只给同事一台设备测一下)、需要声明权限、或者要在应用里被拉起来,就得用自己申请的调试证书签名;要公开发给大批用户,才是华为认证开发者那本二进制证书。


1. 一张表看完三种签名

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

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


2. 证书从哪来

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

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

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

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

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

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": "ohos.permission.kernel.LOAD_INDEPENDENT_LIBRARY" },
    { "name": "ohos.permission.INHERIT_PARENT_PERMISSION" },
    { "name": "ohos.permission.kernel.EXEMPT_ANONYMOUS_EXECUTABLE_MEMORY" },
    { "name": "ohos.permission.kernel.IGNORE_LIBRARY_VALIDATION" },
    { "name": "ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY" }
  ],
  "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 那种。这里其实只要回答两个问题。

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

场景 该用哪种签名
只在我自己这台设备上跑 自签名就够了
要给同事在自己设备上测一下(小范围分发) 必须用调试证书
要公开发给大批用户 二进制证书

💡 这才是自签名和调试证书最主要的区别。
自签名的含义就是"签给这台机器用":签完本机能跑,换台机器就得在那台上重签一次。
它也不带任何身份信息——别人的设备既认不出"这是谁给的",也没有为它背书的凭证。
所以一旦有人跟你说"你编好的这个发我测一下",就必须换成调试证书签名。

小范围分发的成本其实不高,三步:

# 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 装好的程序直接拷贝给别人,对方那边跑不起来。
不是因为文件坏了,而是那个自签只对签名时所在的那台机器成立。

那对方自己重签一下行不行?行,但要先摘掉旧的签名段——见下一节 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 别人发来的二进制,怎么在自己机器上用

顺着上面那句"换台机器就得重签",很自然会想:那我在我这台上重新签一次不就行了?

可以,但不能直接签——文件里还带着对方那台机器留下的签名段,签名工具会直接拒绝:

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:从构建机带过来的动态库如果在你这台机器上加载失败,
先按上面这套把 .codesign 摘掉重签一遍,比去查"是不是找不到库、是不是路径不对"要快得多。

4.5 最关键的一条对应关系

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

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

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

一句话对照:

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

5. 怎么选:三步

第 1 步:这个二进制要不要交到别人手上跑?
          ├─ 要公开发给大批用户 ─────────→ 二进制证书(需华为认证)
          └─ 只给自己,或给一两个同事 ──┐
                                        ↓
第 2 步:下面三条,占任意一条吗?
          · 要交给别人(哪怕只有一台设备)
          · 要声明权限
          · 要在(强沙箱)应用里被拉起
          ├─ 一条都不占 ────────────────→ 自签名(adhoc)
          └─ 占了任意一条 ──────────────→ 自己申请的调试证书

对照成表格:

你的情况 用哪种 关键动作
只自己本机用、不要权限 自签名 -selfSign 1 + chmod 755
要让同事在自己设备上测 调试证书 对方提供 UDID → 填 device-ids
要声明权限 调试证书 自己申请的证书 + 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 节就够了。