HarmonyOS开发:应用签名------签名配置
核心要点:签名是HarmonyOS应用安装的"通行证",没有签名的HAP装不上设备,签名不对的HAP也装不上------搞懂签名配置,是应用能跑起来的前提。
背景与动机
你辛辛苦苦打了个HAP包,连上设备准备安装,结果弹出来一个错误:"签名校验失败"。然后你开始各种搜、各种试,折腾半天还是装不上。
这场景熟悉不?
签名这事儿,开发阶段你可能不太在意------DevEco Studio帮你自动签名了嘛。但到了真机调试、团队协作、发布上架这些环节,签名问题就一个接一个地冒出来:调试签名和发布签名有啥区别?签名文件怎么配?换台电脑为什么签名就失效了?多人协作怎么共享签名?
这些问题不搞清楚,你就永远在"签名失败"的泥潭里打转。
核心原理
为什么需要签名?
签名解决的是信任问题。设备怎么知道这个HAP是你开发的、没有被篡改?靠的就是签名。签名用非对称加密算法,你用私钥签名,设备用公钥验证。私钥只有你有,所以只有你能签出合法的包。
#mermaid-svg-Q2GqVY5vXfXjNAvx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q2GqVY5vXfXjNAvx .error-icon{fill:#552222;}#mermaid-svg-Q2GqVY5vXfXjNAvx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q2GqVY5vXfXjNAvx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .marker.cross{stroke:#333333;}#mermaid-svg-Q2GqVY5vXfXjNAvx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q2GqVY5vXfXjNAvx p{margin:0;}#mermaid-svg-Q2GqVY5vXfXjNAvx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .cluster-label text{fill:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .cluster-label span{color:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .cluster-label span p{background-color:transparent;}#mermaid-svg-Q2GqVY5vXfXjNAvx .label text,#mermaid-svg-Q2GqVY5vXfXjNAvx span{fill:#333;color:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .node rect,#mermaid-svg-Q2GqVY5vXfXjNAvx .node circle,#mermaid-svg-Q2GqVY5vXfXjNAvx .node ellipse,#mermaid-svg-Q2GqVY5vXfXjNAvx .node polygon,#mermaid-svg-Q2GqVY5vXfXjNAvx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .rough-node .label text,#mermaid-svg-Q2GqVY5vXfXjNAvx .node .label text,#mermaid-svg-Q2GqVY5vXfXjNAvx .image-shape .label,#mermaid-svg-Q2GqVY5vXfXjNAvx .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q2GqVY5vXfXjNAvx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .rough-node .label,#mermaid-svg-Q2GqVY5vXfXjNAvx .node .label,#mermaid-svg-Q2GqVY5vXfXjNAvx .image-shape .label,#mermaid-svg-Q2GqVY5vXfXjNAvx .icon-shape .label{text-align:center;}#mermaid-svg-Q2GqVY5vXfXjNAvx .node.clickable{cursor:pointer;}#mermaid-svg-Q2GqVY5vXfXjNAvx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .arrowheadPath{fill:#333333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q2GqVY5vXfXjNAvx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Q2GqVY5vXfXjNAvx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q2GqVY5vXfXjNAvx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Q2GqVY5vXfXjNAvx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .cluster text{fill:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx .cluster span{color:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q2GqVY5vXfXjNAvx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q2GqVY5vXfXjNAvx rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q2GqVY5vXfXjNAvx .icon-shape,#mermaid-svg-Q2GqVY5vXfXjNAvx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q2GqVY5vXfXjNAvx .icon-shape p,#mermaid-svg-Q2GqVY5vXfXjNAvx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Q2GqVY5vXfXjNAvx .icon-shape .label rect,#mermaid-svg-Q2GqVY5vXfXjNAvx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q2GqVY5vXfXjNAvx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q2GqVY5vXfXjNAvx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q2GqVY5vXfXjNAvx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-Q2GqVY5vXfXjNAvx .dev>*{fill:#4A90D9!important;stroke:#2C5F8A!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .dev span{fill:#4A90D9!important;stroke:#2C5F8A!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .dev tspan{fill:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .file>*{fill:#F39C12!important;stroke:#D68910!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .file span{fill:#F39C12!important;stroke:#D68910!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .file tspan{fill:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .device>*{fill:#2ECC71!important;stroke:#25A55A!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .device span{fill:#2ECC71!important;stroke:#25A55A!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .device tspan{fill:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .check>*{fill:#9B59B6!important;stroke:#8E44AD!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .check span{fill:#9B59B6!important;stroke:#8E44AD!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .check tspan{fill:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .success>*{fill:#27AE60!important;stroke:#1E8449!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .success span{fill:#27AE60!important;stroke:#1E8449!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .success tspan{fill:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .fail>*{fill:#FF6B6B!important;stroke:#CC5555!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .fail span{fill:#FF6B6B!important;stroke:#CC5555!important;color:#fff!important;}#mermaid-svg-Q2GqVY5vXfXjNAvx .fail tspan{fill:#fff!important;} 私钥签名
安装
公钥验证
是
否
开发者
HAP文件
设备
签名合法?
安装成功
拒绝安装
HarmonyOS签名体系
HarmonyOS的签名体系比Android更严格,涉及三层证书:
- 调试证书(Debug Certificate):开发阶段使用,只能安装在调试设备上
- 发布证书(Release Certificate):发布阶段使用,可以安装在所有设备上
- 应用签名证书(App Signing Certificate):华为AGC托管的签名证书,上架应用市场时使用
签名流程涉及的关键文件:
| 文件 | 说明 | 谁生成 |
|---|---|---|
.p12 |
个人信息交换文件,包含私钥 | 开发者通过OpenSSL生成 |
.cer |
数字证书文件,包含公钥 | 华为开发者联盟签发 |
.p7b |
应用签名Profile文件 | 华为AGC生成 |
.jks |
Java密钥库(调试用) | DevEco Studio自动生成 |
签名验证机制
设备安装HAP时的签名验证流程:
- 解析HAP中的签名数据
- 从签名数据中提取证书链
- 验证证书链的合法性(是否由华为CA签发)
- 用证书中的公钥验证HAP的签名
- 检查签名Profile中的包名和指纹是否匹配
- 全部通过才允许安装
代码实战
基础用法:调试签名配置
开发阶段最简单的签名方式,DevEco Studio自动帮你搞定:
typescript
// build-profile.json5
{
"app": {
"signingConfigs": [
{
"name": "default",
"type": "HarmonyOS",
"material": {
"certpath": "C:/Users/Dev/.ohos/config/default_MyApp_xxx.cer", // 证书文件
"storeFile": "C:/Users/Dev/.ohos/config/default_MyApp_xxx.p12", // 密钥库文件
"storePassword": "xxx", // 密钥库密码
"keyAlias": "debugKey", // 密钥别名
"keyPassword": "xxx", // 密钥密码
"profile": "C:/Users/Dev/.ohos/config/default_MyApp_xxx.p7b", // 签名Profile
"signAlg": "SHA256withECDSA", // 签名算法
"storeFileHash": "xxx" // 密钥库哈希(防篡改)
}
}
],
"products": [
{
"name": "default",
"signingConfig": "default" // 使用名为default的签名配置
}
]
}
}
这是DevEco Studio自动生成的调试签名配置。你第一次运行项目时,IDE会自动生成调试证书和Profile,填到这个配置里。
但这里有个坑------调试签名只能在调试设备上用。什么是调试设备?就是在华为开发者联盟注册过UDID的设备。没有注册的设备,调试签名也装不上。
进阶用法:发布签名配置
发布应用时,调试签名不够用了,需要正式的发布签名:
第一步:生成密钥对和证书请求文件
bash
# 生成密钥对(.p12文件)
openssl req -new -x509 -key private_key.pem -out cert_request.csr \
-newkey rsa:2048 -nodes -keyout private_key.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=Dev/CN=MyApp" \
-days 3650
# 转换为PKCS12格式
openssl pkcs12 -export -out MyApp_Release.p12 \
-inkey private_key.pem -in cert_request.csr \
-name "releaseKey" -passout pass:your_password
第二步:在华为开发者联盟申请发布证书
登录华为开发者联盟 → 证书管理 → 新增证书 → 上传CSR文件 → 下载.cer证书文件。
第三步:在AGC创建应用并下载Profile
登录AppGallery Connect → 我的应用 → 创建应用 → 填写包名 → 下载.p7b签名Profile。
第四步:配置发布签名
typescript
// build-profile.json5
{
"app": {
"signingConfigs": [
{
"name": "default",
"type": "HarmonyOS",
"material": {
"certpath": "signing/MyApp_Release.cer",
"storeFile": "signing/MyApp_Release.p12",
"storePassword": "your_password",
"keyAlias": "releaseKey",
"keyPassword": "your_password",
"profile": "signing/MyApp_Release.p7b",
"signAlg": "SHA256withECDSA"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "default"
}
]
}
}
关键安全提示 :发布签名的.p12文件和密码绝对不能泄露!一旦泄露,别人就能用你的签名打包恶意应用。建议:
- 签名文件不要放在工程目录里,放在安全的位置
.gitignore中排除签名文件- 密码不要明文写在配置文件里,用环境变量
typescript
// 更安全的签名配置方式
{
"name": "default",
"type": "HarmonyOS",
"material": {
"certpath": "${SIGN_CERT_PATH}", // 环境变量
"storeFile": "${SIGN_STORE_FILE}", // 环境变量
"storePassword": "${SIGN_STORE_PASSWORD}", // 环境变量
"keyAlias": "${SIGN_KEY_ALIAS}", // 环境变量
"keyPassword": "${SIGN_KEY_PASSWORD}", // 环境变量
"profile": "${SIGN_PROFILE}", // 环境变量
"signAlg": "SHA256withECDSA"
}
}
完整示例:多环境签名配置
实际项目中,你可能需要多套签名------调试一套、内测一套、正式发布一套:
typescript
// build-profile.json5
{
"app": {
"signingConfigs": [
{
"name": "debug",
"type": "HarmonyOS",
"material": {
"certpath": "signing/debug/debug.cer",
"storeFile": "signing/debug/debug.p12",
"storePassword": "debug_pwd",
"keyAlias": "debugKey",
"keyPassword": "debug_pwd",
"profile": "signing/debug/debug.p7b",
"signAlg": "SHA256withECDSA"
}
},
{
"name": "beta",
"type": "HarmonyOS",
"material": {
"certpath": "signing/beta/beta.cer",
"storeFile": "signing/beta/beta.p12",
"storePassword": "${BETA_SIGN_PWD}",
"keyAlias": "betaKey",
"keyPassword": "${BETA_SIGN_PWD}",
"profile": "signing/beta/beta.p7b",
"signAlg": "SHA256withECDSA"
}
},
{
"name": "release",
"type": "HarmonyOS",
"material": {
"certpath": "${RELEASE_CERT_PATH}",
"storeFile": "${RELEASE_STORE_FILE}",
"storePassword": "${RELEASE_STORE_PWD}",
"keyAlias": "${RELEASE_KEY_ALIAS}",
"keyPassword": "${RELEASE_KEY_PWD}",
"profile": "${RELEASE_PROFILE}",
"signAlg": "SHA256withECDSA"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "debug" // 默认用调试签名
},
{
"name": "beta",
"signingConfig": "beta" // 内测用beta签名
},
{
"name": "release",
"signingConfig": "release" // 正式用release签名
}
]
}
}
命令行指定签名配置打包:
bash
# 调试包
hvigorw assembleHap --mode module -p module=entry@default -p product=default
# 内测包
hvigorw assembleHap --mode module -p module=entry@default -p product=beta
# 正式发布包
hvigorw assembleHap --mode module -p module=entry@default -p product=release
签名验证工具:
typescript
// scripts/verify-signature.ets
// 验证HAP签名信息的工具脚本
import { hapSignVerify } from '@ohos/hvigor-ohos-plugin';
interface SignatureInfo {
bundleName: string; // 包名
appId: string; // 应用ID
fingerprint: string; // 证书指纹
validity: { // 证书有效期
notBefore: string;
notAfter: string;
};
permissions: string[]; // 声明的权限
}
// 解析HAP签名信息
function verifyHapSignature(hapPath: string): SignatureInfo | null {
try {
const result = hapSignVerify.verify(hapPath);
if (result.code === 0) {
return {
bundleName: result.bundleName,
appId: result.appId,
fingerprint: result.fingerprint,
validity: {
notBefore: result.notBefore,
notAfter: result.notAfter
},
permissions: result.permissions || []
};
}
console.error(`签名验证失败: ${result.message}`);
return null;
} catch (error) {
console.error(`签名验证异常: ${error}`);
return null;
}
}
// 检查证书是否即将过期
function checkCertExpiry(hapPath: string, warnDays: number = 30): void {
const info = verifyHapSignature(hapPath);
if (!info) return;
const notAfter = new Date(info.validity.notAfter);
const now = new Date();
const daysLeft = Math.floor((notAfter.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
if (daysLeft <= 0) {
console.error(`❌ 证书已过期!请立即续期`);
} else if (daysLeft <= warnDays) {
console.warn(`⚠️ 证书将在 ${daysLeft} 天后过期,请尽快续期`);
} else {
console.info(`✅ 证书有效期剩余 ${daysLeft} 天`);
}
}
踩坑与注意事项
坑1:调试设备未注册UDID
调试签名安装失败,最常见的原因就是设备UDID没在华为开发者联盟注册。注册流程:开发者联盟 → 设备管理 → 添加设备 → 输入UDID。
获取设备UDID的方法:
bash
# 通过hdc命令获取
hdc shell param get const.ohos.serial
# 或者通过DevEco Studio的设备管理器查看
# Tools → Device Manager → 点击设备详情
坑2:签名Profile过期
.p7b签名Profile有有效期,过期后打包的HAP无法安装。调试Profile有效期通常是一年,发布Profile有效期更长。
解法:定期检查Profile有效期,过期前重新申请。建议在CI脚本中加入自动检查:
bash
# 检查p7b文件有效期
openssl pkcs7 -inform DER -in MyApp.p7b -print_certs -noout -text | grep -A 2 "Not After"
坑3:签名配置中密码明文泄露
把.p12密码直接写在build-profile.json5里,然后提交到Git------这是最常见的安全事故。一旦仓库泄露,签名私钥就暴露了。
解法:
.gitignore中加入signing/目录和*.p12、*.jks文件- 密码用环境变量,CI中通过Secret注入
- 本地开发用
local.properties存放敏感信息(这个文件也在.gitignore中)
坑4:多模块签名不一致
多HAP工程中,Entry和Feature模块必须使用同一套签名。如果Entry用调试签名、Feature用发布签名,安装时会报签名不匹配。
解法 :在工程级build-profile.json5中统一配置签名,所有模块共用。不要在模块级别单独配置签名。
坑5:签名算法不兼容
HarmonyOS 5.0默认签名算法是SHA256withECDSA,但有些旧设备只支持SHA256withRSA。如果你的目标设备包含旧设备,需要同时支持两种算法。
解法 :signAlg字段使用默认值SHA256withECDSA即可,HarmonyOS的签名工具会自动处理兼容性。如果确实遇到旧设备签名验证失败,可以尝试切换为SHA256withRSA。
HarmonyOS 6适配说明
HarmonyOS 6对签名机制做了以下更新:
-
SHA-384签名算法支持 :新增
SHA384withECDSA签名算法,安全性更高。新申请的发布证书默认使用此算法。 -
AGC托管签名增强:AppGallery Connect的托管签名功能升级,支持自动签名和密钥轮换。开发者不再需要自己管理私钥,AGC帮你签名,更安全。
-
签名Profile权限细化 :HarmonyOS 6的签名Profile新增权限分级,不同等级的权限需要不同等级的证书。比如
ohos.permission.INSTALL_BUNDLE需要系统级证书,普通开发者证书无法申请。 -
多证书签名:支持一个HAP使用多个证书签名,用于不同安全等级的代码段。这在金融、政务等高安全场景下有用。
-
证书透明性(CT)日志:HarmonyOS 6要求发布证书必须记录到CT日志中,防止证书被恶意签发。新申请的证书会自动加入CT日志,无需开发者额外操作。
总结
签名这事儿,开发阶段你可能觉得"能用就行",但到了发布阶段,任何一个签名问题都能让你卡住半天。
记住这几个关键点:
- 调试签名只能调试用,发布必须用发布签名
- 签名文件和密码是命根子,泄露了等于把应用交给了别人
- 多模块签名必须一致,不同签名会导致安装失败
- 证书和Profile有有效期,过期前记得续期