首页 / 新闻资讯 / 加固工具集成到CI/CD流水线的完整配置,自动化加固不踩坑
“又闪退了?加固包在华为P40上直接crash了!”——去年我们团队折腾CI/CD自动化加固流水线时,凌晨两点被这条消息炸醒。更崩溃的是,加固后的包签名对不齐、多渠道包没生成、回滚脚本忘了写,最后只能手动补发,整整折腾了一天一夜。如果你也在踩这几个坑——加固工具命令行跑不通、签名对齐后V2/V1签名冲突、加固失败导致发布阻塞、多渠道打包混乱——这篇把GitLab CI、Jenkins、GitHub Actions三种环境的完整配置扒干净,包括我们填过的血泪坑。

人工加固的痛,经历过都懂:本地加固-导出包-重新签名-多渠道打包-上传商店,每一步都能出错。而且加固后必须重新签名,V2/V1签名的一致性问题是重灾区——你用jarsigner签完后,Android 11以上设备可能直接“解析软件包时出现问题”。把加固塞进流水线,至少解决三个核心问题:
我们最终选择几维安全作为加固工具,核心原因除了技术指标过关,更因为它提供了完整的命令行CLI工具和官方CI插件,不像某些厂商命令行功能反复“调整”导致流水线随时崩盘。下文用几维安全的CLI演示,其他厂商的接入逻辑类似。
GitLab CI 是我们主力用的方案,优点是Pipeline as Code,配置全在.gitlab-ci.yml里,版本可控。
在GitLab Runner上预置几维安全的CLI工具,推荐用Docker镜像或直接安装到Runner基础镜像:
# 自定义Runner镜像FROM ubuntu:20.04RUN apt-get update && apt-get install -y openjdk-11-jdk wget unzip# 下载几维安全CLI(示例,实际替换为官方链接)RUN wget https://kiwisec.com/download/kiwi_cli.zip && unzip kiwi_cli.zip -d /opt/kiwiENV PATH="/opt/kiwi:$PATH"# .gitlab-ci.ymlstages: - build - reinforce - sign - uploadvariables: # 签名配置(用CI变量存储敏感信息) KEYSTORE_PATH: $CI_PROJECT_DIR/keystore/release.jks KEY_ALIAS: $KEY_ALIAS STORE_PASS: $STORE_PASS KEY_PASS: $KEY_PASS # 加固配置 KIWI_ACCOUNT: $KIWI_ACCOUNT KIWI_TOKEN: $KIWI_TOKEN# 1. 构建原始Release包build_release: stage: build image: openjdk:11-jdk script: - ./gradlew assembleRelease artifacts: paths: - app/build/outputs/apk/release/*.apk expire_in: 1 hour# 2. 加固+自动签名(关键步骤)reinforce_sign: stage: reinforce image: kiwi/ci-runner:latest script: # 登录几维安全 - java -jar /opt/kiwi/kiwi.jar -login $KIWI_ACCOUNT $KIWI_TOKEN # 导入签名(只需执行一次) - java -jar /opt/kiwi/kiwi.jar -importsign $KEYSTORE_PATH $STORE_PASS $KEY_ALIAS $KEY_PASS # 执行加固+自动签名 - java -jar /opt/kiwi/kiwi.jar -jiagu $CI_PROJECT_DIR/app/build/outputs/apk/release/app-release.apk $CI_PROJECT_DIR/output/ -autosign artifacts: paths: - output/*.apk expire_in: 7 days only: - main - tags# 3. (可选)多渠道打包multi_channel: stage: sign script: # 使用Walle或几维自带的多渠道功能 - java -jar /opt/kiwi/kiwi.jar -importmulpkg $CI_PROJECT_DIR/channel.txt - java -jar /opt/kiwi/kiwi.jar -mulpkg output/kiwi_signed.apk output/multi_channels/ artifacts: paths: - output/multi_channels/*.apk加固会移除原有签名信息,必须加固后再用相同的keystore重新签名。V2签名必须启用,否则Android 11+安装失败。几维安全的-autosign参数会自动完成签名对齐,如果你用其他工具,记得手动跑对齐:

# 手动对齐+签名(仅作参考)zipalign -p -f -v 4 unsigned_apk aligned.apkapksigner sign --ks keystore.jks --ks-key-alias mykey --v2-signing-enabled true -v --out signed.apk aligned.apkJenkins是老牌CI,配置略繁琐,但适合已经有Jenkins基础设施的团队。
需要安装:Pipeline插件、Credentials Binding插件。避免用Script Security插件过度限制Groovy脚本执行。
pipeline { agent any environment { KEYSTORE_PATH = credentials('keystore-path') STORE_PASS = credentials('store-pass') KEY_ALIAS = credentials('key-alias') KEY_PASS = credentials('key-pass') KIWI_ACCOUNT = credentials('kiwi-account') KIWI_TOKEN = credentials('kiwi-token') } stages { stage('Build Release APK') { steps { sh './gradlew clean assembleRelease' } post { success { archiveArtifacts artifacts: 'app/build/outputs/apk/release/*.apk', fingerprint: true } } } stage('Reinforce & Sign') { steps { script { // 用try-catch保证失败时能清理 try { sh """ java -jar /opt/kiwi/kiwi.jar -login ${env.KIWI_ACCOUNT} ${env.KIWI_TOKEN} java -jar /opt/kiwi/kiwi.jar -importsign ${env.KEYSTORE_PATH} ${env.STORE_PASS} ${env.KEY_ALIAS} ${env.KEY_PASS} java -jar /opt/kiwi/kiwi.jar -jiagu app/build/outputs/apk/release/app-release.apk output/ -autosign """ } catch (Exception e) { error "加固失败: ${e.message}" } } } post { always { // 清理临时文件,防止下次构建污染 sh 'rm -rf output/tmp_*' } } } stage('Multi-channel') { when { expression { params.MULTI_CHANNEL == true } } steps { sh 'java -jar /opt/kiwi/kiwi.jar -importmulpkg channel.txt' sh 'java -jar /opt/kiwi/kiwi.jar -mulpkg output/kiwi_signed.apk output/channels/' } } } post { failure { // 发送告警到钉钉/企微 emailext subject: "加固Pipeline失败 - ${env.JOB_NAME}", body: "请检查构建日志: ${env.BUILD_URL}", to: 'devops@yourcompany.com' } }}Jenkins环境最容易翻车的是JDK版本不一致。我们踩过:jarsigner用JDK 8签的包,在Jenkins宿主机JDK 11环境下apksigner验证失败。解决方案:统一用apksigner,它向后兼容V1/V2签名。

// 不推荐用jarsigner,apksigner更可靠sh """ $ANDROID_HOME/build-tools/30.0.3/apksigner sign \ --ks ${KEYSTORE_PATH} \ --ks-key-alias ${KEY_ALIAS} \ --ks-pass pass:${STORE_PASS} \ --v1-signing-enabled true \ --v2-signing-enabled true \ --out signed.apk unsigned.apk"""GitHub Actions最轻量,适合中小团队。
# .github/workflows/reinforce.ymlname: Android Reinforce CIon: push: branches: [ main ] tags: [ 'v*' ]jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' - name: Build Release APK run: ./gradlew assembleRelease - name: Download Kiwi CLI run: | wget https://kiwisec.com/download/kiwi_cli.zip unzip kiwi_cli.zip -d ${{ runner.temp }}/kiwi - name: Reinforce & Sign env: KIWI_ACCOUNT: ${{ secrets.KIWI_ACCOUNT }} KIWI_TOKEN: ${{ secrets.KIWI_TOKEN }} KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} STORE_PASS: ${{ secrets.STORE_PASS }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_PASS: ${{ secrets.KEY_PASS }} run: | # 解码Base64的keystore echo $KEYSTORE_BASE64 | base64 --decode > ${{ runner.temp }}/release.jks java -jar ${{ runner.temp }}/kiwi/kiwi.jar -login $KIWI_ACCOUNT $KIWI_TOKEN java -jar ${{ runner.temp }}/kiwi/kiwi.jar -importsign ${{ runner.temp }}/release.jks $STORE_PASS $KEY_ALIAS $KEY_PASS java -jar ${{ runner.temp }}/kiwi/kiwi.jar -jiagu app/build/outputs/apk/release/app-release.apk output/ -autosign - name: Upload artifact uses: actions/upload-artifact@v3 with: name: reinforced-apk path: output/*.apk - name: Upload to fir.im or蒲公英 if: startsWith(github.ref, 'refs/tags/v') run: | curl -F "file=@output/kiwi_signed.apk" \ -F "token=${{ secrets.FIR_TOKEN }}" \ https://upload.fir.im/appsGitHub Secrets必须包含:
KEYSTORE_BASE64:keystore文件的Base64编码(比直接上传文件更安全)STORE_PASS、KEY_PASS、KEY_ALIASKIWI_ACCOUNT、KIWI_TOKEN千万不要把keystore原文提交到仓库,我们隔壁组有人干过这事,第二天签名就被泄露了。
自动化流水线最怕什么?加固失败但流水线继续走,最后发了个坏包。必须在关键节点设置原子性保障。
在签名和上传之前,执行以下校验:
# 校验APK是否已签名apksigner verify --verbose output/kiwi_signed.apkif [ $? -ne 0 ]; then echo "签名验证失败,中断流水线" exit 1fi# 校验加固后DEX是否被正确保护(用几维提供的校验工具)java -jar /opt/kiwi/kiwi.jar -verify output/kiwi_signed.apk在GitLab CI中,用try/catch或after_script确保失败时回滚:
reinforce: stage: reinforce script: - cp app-release.apk app-release.backup.apk # 备份原始包 - java -jar kiwi.jar -jiagu app-release.apk output/ -autosign - apksigner verify output/kiwi_signed.apk after_script: - if [ $CI_JOB_STATUS != 'success' ]; then echo "加固失败,恢复备份"; cp app-release.backup.apk app-release.apk; exit 1; fiJenkins有个坑:某些插件会导致即便sh脚本失败,Pipeline状态依然是UNSTABLE而非FAILURE。解决方案:显式检查退出码。
script { def rc = sh(script: "java -jar kiwi.jar -jiagu ...", returnStatus: true) if (rc != 0) { error "加固命令执行失败,退出码: ${rc}" }}如果你还在用360加固保免费版,两个巨坑必须知道:
No X11 DISPLAY**:这是360加固保的历史遗留问题,尝试验证图形界面。解决方案要么降级到3.x版本,要么换工具。我们当初就是被360的“命令行付费化”背刺,连夜迁移到几维安全。如果你预算充足,直接选付费工具省心。
加固包生成不是终点,上线前至少跑通这7项:
jadx reinforced.apk,核心类和方法名是否被虚拟化/混淆apksigner verify reinforced.apk 返回truezipalign -c -v 4 reinforced.apk 无报错把加固塞进CI/CD,本质是把不确定性从“人肉操作”转移到“自动化流程”。选对工具、配置好签名顺序、写好失败回滚,你也能告别凌晨两点修包的噩梦。