MoreRSS

site iconLuffy | 沈唁志修改

分享 PHP、Swoole 技术资源、实用插件、有趣网站和 WordPress 相关内容。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Luffy | 沈唁志的 RSS 预览

使用 Trusted Publishing 提升 npm 包发布安全性

2026-04-21 12:52:15

在现代前端生态中,npm 包发布流程的安全性越来越重要。

传统的发布方式通常依赖长期有效的 npm token,一旦泄露,攻击者就可以直接发布恶意版本,带来严重的供应链风险。为了解决这一问题,npm 在 2025 年 7 月份推出了 Trusted Publishing(可信发布) 机制。

什么是 Trusted Publishing?

Trusted Publishing 是 npm 已正式推出基于 OpenID Connect (OIDC) 的可信发布功能。该功能允许使用 OpenID Connect (OIDC) 进行身份验证,直接从 CI/CD 工作流安全地发布 npm 包,从而减少管理长期令牌的需求。

借助可信发布,现在可以

  • 无长期 token:不需要在仓库中保存 npm token
  • 消除令牌安全风险:无需在 CI/CD 环境中存储、轮换或意外暴露 npm token。
  • 短期凭证:每次发布时动态签发,自动过期
  • 绑定 CI 身份,建立加密信任:发布权限与 GitHub 仓库/工作流强绑定,每次发布都使用短期、特定于工作流的凭据进行身份验证,这些凭据无法被泄露或重复使用。
  • 降低泄露风险:即使 CI 配置泄露,也难以滥用
  • 自动获取来源证明:使用可信发布时,npm CLI 默认发布来源证明。--provenance不再需要该标志。

传统发布方式的问题

在 Trusted Publishing 出现之前,常见流程如下:

  1. 在 npm 上生成一个 Access Token
  2. 将 token 存入 GitHub Secrets(如 NPM_TOKEN)
  3. 在 CI 中执行 npm publish 进行发布

问题在于 token 通常是长期有效,一旦泄露,攻击者可以进行发布恶意版本等操作。

现在 npm 限制生成 token 最长有效期为 90 天,还使用这种方式会经常需要轮换 token。

Trusted Publishing 的工作原理

Trusted Publishing 会在 npm 和你的 CI/CD 提供商之间建立信任关系

当你为你的包配置可信发布者后,npm 将只接受来自你已授权的特定工作流的发布。

Trusted Publishing 基于以下流程:

  1. GitHub Actions 在运行时向 npm 发起身份请求(OIDC)
  2. npm 验证该请求是否来自可信仓库
  3. 如果匹配配置,签发一个短期访问令牌
  4. CI 使用该令牌执行 npm publish

整个过程:

  • 无需手动管理 token
  • 每次发布都是一次“临时授权”

设置 Trusted Publishing

我也是在发布 docsify-footnotes时发现 token 不可用了,此处以它为例:

1. npm 侧配置

进入 npm 包的 Settings页面,就可以看到Trusted Publisher,并且需要选择publisher,支持GitHub ActionsGitLab CI/CDCircleCI

我的仓库在 GitHub,所以选择GitHub Actions,需要配置Organization or userRepositoryWorkflow filename,这三个都是必填项。

GitHub 用户名或组织名称 Organization or usersy-records
仓库名称 Repositorydocsify-footnotes
工作流文件名 Workflow filenamepublish.yml

点击Set up connection保存即可

2. GitHub Actions 配置

保存之后需要修改对应的 action yml 文件,需要将所需的 OIDC 权限添加到你的工作流中:

permissions:
  id-token: write  # Required for OIDC
  contents: read

关键要求是id-token: write需要获得相应的权限,才能让 GitHub Actions 生成 OIDC 令牌,这是一个完整例子:

name: Publish Package

on:
  push:
    tags:
      - 'v*'

permissions:
  id-token: write  # Required for OIDC
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm run build --if-present
      - run: npm test
      - run: npm publish

因为我的几个 npm 仓库都采用的 复用 actions,所以只需要修改加上permissions和移除写入 npm token 就可以使用了,见 commit

发布成功后的邮件通知也会增加说明 GitHub Actions 的信息。

Hi sy-records!

A new version of the package @sy-records/docsify-footnotes (2.3.0) was published at 2026-04-21T02:48:22.069Z from GitHub Actions: https://github.com/sy-records/docsify-footnotes/actions/runs/24701393194/attempts/1 (triggered via a "push" event on git ref "refs/tags/v2.3.0").
The shasum of this package is 8136f65cb7d651d4b3671290b7c16da4cac71b24.

If you have questions or security concerns, you can contact us at https://www.npmjs.com/support.

Thanks,

The npm team.

需要注意的是 Trusted Publishing 需要 npm CLI 版本 11.5.1 或更高版本以及 Node 版本 22.14.0 或更高版本。

Trusted Publishing 是 npm 在供应链安全方向的重要升级,建议所有开源项目逐步迁移。

Linux 服务器实现 Word 转图片方案

2026-03-12 16:25:11

在很多业务系统中,经常需要对 Word 文档进行在线预览或生成图片。

常见场景包括:

  • 电子函件或通知预览
  • 审批系统附件展示
  • OCR 识别前的文档预处理
  • IM / 消息系统模板截图
  • 文档归档与审计

Word 文档 (.docx) 本身不适合直接渲染为图片,因此通常采用如下转换链路:

DOCX → PDF → PNG

原因:

  • DOCX → PDF:排版最稳定
  • PDF → PNG:渲染简单,兼容性好

本文记录一次完整的服务器环境搭建过程,使用:

  • CentOS/PHP
  • phpword:负责处理 Word 模板中的变量替换
  • LibreOffice:负责 Word 转 PDF
  • ImageMagick:负责 PDF 转 PNG

完整转换流程如下:

PHP (phpword)
  ↓
DOCX
  ↓
LibreOffice (soffice)
  ↓
PDF
  ↓
ImageMagick (convert)
  ↓
PNG

LibreOffice

首先在服务器安装 LibreOffice:

sudo yum install libreoffice

安装完成后可以使用 soffice进行文档转换。

测试是否安装成功:

soffice --version

接下来就可以开始 DOCX 转 PDF 了,使用命令:

# 单文件转换
soffice --headless \
--convert-to pdf \
/tmp/upload_data/letter.docx \
--outdir /tmp/upload_data

执行后会在/tmp/upload_data目录下生成letter.pdf

如果目录中有大量 Word 文件,也可以批量转换:

soffice --headless \
--convert-to pdf \
/tmp/upload_data/1/*.docx \
--outdir /tmp/upload_data/1

执行后会在/tmp/upload_data/1/目录下生成和 docx 文件同名的 pdf 文件。

但是这个时候查看生成好的 PDF 文件,会发现字体和 Word 有区别,在 Linux 服务器上转换 Word 时,经常会遇到:

  • 字体被替换
  • 排版错位
  • 中文显示异常

原因是服务器没有 Windows 上对应的字体。解决方法就是从 Windows 复制字体,放到服务器上。

字体目录:

C:\Windows\Fonts

常用中文字体:

字体 文件
宋体 simsun.ttc
黑体 simhei.ttf
微软雅黑 msyh.ttc
仿宋 simfang.ttf
楷体 simkai.ttf

将这些字体打包为fonts.zip后上传服务器,上传到服务器后执行:

unzip fonts.zip -d /usr/share/fonts/chinese

刷新字体缓存:

fc-cache -fv

查看字体:

fc-list | grep simsun

如果看到输出,说明字体安装成功。

ImageMagick

转为 PDF 以后,就可以使用 ImageMagick 将 PDF 渲染为图片。

安装:

yum install -y ImageMagick ImageMagick-devel

检查版本:

convert -version

基本转换命令:

convert -density 300 letter.pdf letter.png

参数说明:

参数 说明
-density 300 设置 PDF 渲染 DPI
letter.pdf 输入文件
letter.png 输出文件

默认情况下,每页 PDF 都会生成一张图片,多页 PDF 时就会生成letter-0.pngletter-1.png...

如果需要拼接多页为一张图片,生成一张长图:

convert -density 300 letter.pdf -append letter.png

-append表示纵向拼接,+append表示横向拼接。

同时可以加上-background white -alpha remove,防止透明背景。

完整示例命令:

soffice --headless --convert-to pdf letter.docx
convert -density 300 letter.pdf -background white -alpha remove -append letter.png

通过 LibreOffice 与 ImageMagick,可以在 Linux 服务器上实现稳定的文档转换流程。

2025|在代码之外,我学会了旅行、牵手和拥抱

2026-01-19 11:03:00

2025 年,对我来说不是被某一个高光时刻定义的一年,而是一段在持续构建中慢慢成型的过程。

白天,我仍然在研发世界里与需求和 Bug 周旋,用一次次迭代换取稳定与确定;而当屏幕合上,生活开始显影:我们去旅行,把时间交给路途;牵手、拥抱,在忙碌与疲惫之间确认彼此的存在。

技术在向前,生活也在向前,而这一年最重要的关键词,或许正是这些看似柔软、却足够支撑长期前行的瞬间:旅行、牵手、拥抱,以及永远的我们。

工作:自由度、责任感,以及真正属于自己的事

去年换了新的工作,到了 2025 年,这种变化开始真正显现出来。

事情不再只是“完成任务”,而是被当作一份长期经营的事业来对待。工作时间更可控,选择空间也更大,节奏上不再是持续高压的忙碌,而是一种忙中有闲、但心里有数的状态。

在工作之外,我依旧活跃在开源世界中,5 月,我在 opensource.org 发布了我的开源故事:Lu Fei: Open Source Changed My Life – From User to Maintainer,因为有了更多可支配的时间,也重新提起了 docsify 的维护工作,今年发布了两个 RC 版本,目标是在 2026 年正式发布 5.0。

回头看这一年,技术没有离开中心,但它不再是消耗型的存在,而是逐渐成为支撑自由度、事业感和长期价值的底层能力。

生活:从同行开始,走向“我们”

如果说 2025 年的技术关键词是稳定与自由,那生活这条线,更像是一段被时间慢慢推着向前的故事。

一个很有意思的 callback 是:

去年,我和一位关系很好的同事一起离职;

今年,她成了我的女朋友。

这是蓄谋已久,还是命运绕了一圈?现在回头看,答案其实已经不重要了。

这一年,我们总是在路上,所有看似的错位,最后都成了走向彼此的路线。

3 月,她的一句“期待 3.14 我们青岛见”,我没去成,我是在月底之前才回去的,见了见以前的同事,聊了聊近况,吃喝玩乐一圈;

4 月,我去宁波出差,她去了亚庇;

5 月,她去郑州看 KPL 比赛,我在金鸡湖边喝咖啡。

那段时间,好像总差一步,却始终没有走远。

5 月中旬,我去了北京找她。一个人逛了国博、人民大会堂,那是第一次意识到:有些地方,一个人去是“参观”,两个人去才是“记忆”。

6 月,杭州出差,我去了灵隐寺、西湖、天下第一财神庙,替她请了一个手串,还特地坐了 1314 路公交车;

7 月,我第一次不想剪头发,想试试不一样的自己,染了她帮我选的颜色——树莓红;

8 月,秋天的第一杯奶茶,是霸王茶姬,送给我天下第一好的朋友。后来回头看,那大概已经不是一杯奶茶那么简单了;

9 月,我在南京,她在杭州,又一次错过。南京博物院、古鸡鸣寺、夫子庙,一个人走完,灵隐寺能斩孽缘,鸡鸣寺能斩乱桃花,哦~

直到 9 月底,她换了新工作,我们终于在杭州重合了行程。

一起爬天下第一财神庙,这次索道刚好检修,她一路等我,我爬得半死;路上还有位姐姐助攻:现在怎么还有小年轻出来爬山约会的。她没有反驳~

一起在西溪湿地坐了摇橹船,我们面对面坐着,船行得很慢,橹声一下一下落在水面上。视线会不经意地对上,又很快移开,听她说了一些工作上的槽点。

那一刻我其实很想问点什么,但话到嘴边,又咽了回去。很多话还没说出口,但距离,已经不是原来的样子了。

10 月,国庆,我们各自回到生活里。

我没回家,有天晚上出门去看了烟花,发给了她;她在我生日那天送了礼物,买了花和蛋糕。

国庆后错峰出行,我又去找她了。可能是看完《盛夏芬德拉》,终于学会了“长嘴”,我问她:

“你觉得我们还算是普通朋友吗?”

答案如我所愿。

那之后,北京的故事正式展开:

古代建筑博物馆、天坛公园、红螺寺、雁栖湖,爬山、骑车;

她看着我累得不行,笑得特别开心;

还有北京野生动物园,下车后往后勾勾的小手~

热恋期的我们,几乎每天都要打三四个小时的视频。

11 月,一起去了北京欢乐谷过万圣节,拍了那张用来给家里官宣的照片;

8 号一起去鸟巢看了 KPL 年度总决赛;

某个深夜,她因为太想我,买了一束花送到了我公司。

那一刻,想念终于有了形状。

11 月底,搬了新家,一起看了《疯狂动物城》。

“Love you, partner”

她第二次给我写了手写信,上一次,还是我们一起离职的时候。

12 月,我出差长沙,给她写了明信片;

看到剧里女主叠小星星写日记,我给她叠了 520 颗;

她带我去看了芭蕾舞剧《海盗》,居然没有睡着,原来我也可以这么“优雅”;

圣诞节的 callback,去年圣诞节在上海的匆匆一面,到今年的相拥而眠。

回看这一年,我们走过很多城市,也在不同节奏里慢慢靠近。

关系并不是某一个瞬间被确认的,而是在一次次同行中自然发生。

至于之后的路,不急着写完,

我们继续走下去就好。

使用 GitHub Actions 自动同步 Docker 镜像到 CNB

2025-08-04 15:47:30

为了提升访问速度、增强稳定性并规避部分官方源的不确定性,将常用的开源镜像同步到中国大陆可访问的镜像仓库是一种高效的解决方案。

本文介绍如何通过 GitHub Actions 自动化完成该同步流程,支持选择性构建与定制版本。

背景

许多开源镜像托管在 Docker Hub 上,但由于网络、访问频率限制等问题,拉取速度不稳定,甚至存在连接失败的情况。

之前是使用 GitHub Actions 同步到 CODING 上,不过要 CODING 要停服了,所以改为同步到 CNB 了。

cnb.cool 也是由腾讯出品,基于 Docker 生态,对环境、缓存、插件进行抽象,通过声明式的语法,帮助开发者以更酷的方式构建软件。

支持代码托管、云原生构建和云原生开发等功能。

同步方案概览

  • 镜像列表维护在 .github/images.yml 中;
  • 手动或自动触发 GitHub Actions;
  • 使用 skopeo 工具将镜像从 Docker Hub 同步到 CNB;
  • 支持选择同步特定镜像或全部镜像;
  • 支持指定版本(tag)。

GitHub Actions 工作流详解


点击查看完整文件内容

前往 GitHub 查看:docker-proxy.yml

name: Mirror Docker Images to CNB

on:
  workflow_dispatch:
    inputs:
      name:
        description: 'Select image to mirror (or leave blank to mirror all)'
        required: false
        type: choice
        options:
          - ""
          - vaultwarden
          - bark-server
          - elasticsearch
          - mysql
          - hyperf
          - clickhouse
      version:
        description: 'Override tag version'
        required: false
        type: string
  push:
    paths:
      - '.github/images.yml'
    branches: [ 'main' ]

jobs:
  mirror:
    name: >-
      Mirror ${{ github.event.inputs.name || 'All Images' }}${{ github.event.inputs.version && format(' (version: {0})', github.event.inputs.version) || '' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Mirror images
        env:
          INPUT_NAME: ${{ github.event.inputs.name }}
          INPUT_VERSION: ${{ github.event.inputs.version }}
        run: |
          images=$(yq -o json '.images' ${{ github.workspace }}/.github/images.yml)

          if [ -n "$INPUT_NAME" ]; then
          matrix=$(echo "$images" | jq -c --arg name "$INPUT_NAME" '.[] | select(.name == $name)')
          else
          matrix=$(echo "$images" | jq -c '.[]')
          fi

          if [ -z "$matrix" ]; then
          echo "No matching images found for name: $INPUT_NAME"
          exit 1
          fi

          echo "$matrix" | while read -r item; do
            image=$(echo "$item" | jq -r '.image')
            name=$(echo "$item" | jq -r '.name')
            default_tag=$(echo "$item" | jq -r '.tag')
            tag=${INPUT_VERSION:-$default_tag}

            echo "Mirroring $image:$tag to docker.cnb.cool/lufei/docker/$name:$tag"

            skopeo copy --all docker://docker.io/${image}:${tag} \
              docker://docker.cnb.cool/lufei/docker/${name}:${tag} \
              --src-creds "${{ secrets.DOCKERHUB_USERNAME }}:${{ secrets.DOCKERHUB_TOKEN }}" \
              --dest-creds "cnb:${{ secrets.CNB_DOCKER_TOKEN }}"

            echo "::notice title=Image Published::https://docker.cnb.cool/lufei/docker/${name}:${tag}"
          done

触发机制

on:
  workflow_dispatch:
    inputs:
      name:     # 可选镜像名
      version:  # 可选覆盖 tag
  push:
    paths:
      - '.github/images.yml'
    branches: [ 'main' ]

支持两种触发方式:

  1. 手动触发:可选择特定镜像名和版本;
  2. 自动触发:当 .github/images.yml 文件变更时,自动同步全量更新。

镜像清单文件

.github/images.yml

images:
  - image: "vaultwarden/server"
    tag: "latest"
    name: "vaultwarden"
  - image: "finab/bark-server"
    tag: "latest"
    name: "bark-server"
  ...

核心同步逻辑

skopeo copy --all docker://docker.io/${image}:${tag} \
  docker://docker.cnb.cool/lufei/docker/${name}:${tag} \
  --src-creds "${{ secrets.DOCKERHUB_USERNAME }}:${{ secrets.DOCKERHUB_TOKEN }}" \
  --dest-creds "cnb:${{ secrets.CNB_DOCKER_TOKEN }}"
  • --all:确保同步多平台镜像(如 amd64 和 arm64);
  • 使用 GitHub Secrets 配置凭据;
  • 输出同步完成后可直接拉取的地址提示。

如何使用

1. 手动触发

在 GitHub → Actions → “Mirror Docker Images to CNB” → 点击 “Run workflow”:

选择镜像

  • 留空镜像名 → 同步所有;
  • 指定镜像名 → 同步该镜像;
  • 同时指定 tag → 覆盖默认 tag。

指定tag

2. 自动触发

每次更新 .github/images.yml 文件并推送到 main 分支时,会自动同步所有的镜像。

镜像拉取示例

同步完成后,用户可直接通过 CNB 公网地址拉取镜像:

docker pull docker.cnb.cool/lufei/docker/hyperf:8.3-alpine-v3.21-swoole

加上https://可以访问网页查看详情:

输出可以访问的镜像链接

安全配置(GitHub Secrets)

名称 用途说明
DOCKERHUB_USERNAME 用于拉取源镜像
DOCKERHUB_TOKEN Docker Hub 登录 token
CNB_DOCKER_TOKEN CNB 镜像仓库推送凭据

CNB 默认可以创建 npm、Composer 等的制品库,但 Docker 的制品默认就在仓库中,所以创建一个仓库即可。

总结

通过 GitHub Actions + skopeo + CNB 服务,我们构建了一个可复用、自动化、支持多镜像同步的工具链,显著提升了镜像的可用性与部署效率。

怎么申请开具中国税收居民身份证明?

2025-04-18 17:04:42

之前因为 Google Adsense 要求进行新加坡税务信息填写,需要上传税务证明,但是实在找不到个人在哪里申请,于是就暂停了 Google Adsense 服务。

不过在 2025 年 4 月 1 日起施行了新规,申请《中国税收居民身份证明》不再困难了!

下面就来说说申请步骤:

  1. 访问自然人电子税务局官网,找到我要办税,点击中国税收居民身份证明开具

自然人电子税务局官网

  1. 点击申请开具《中国税收居民身份证明》。后续查询也是这里,点击旁边的查询。

申请开具《中国税收居民身份证明》

  1. 选择申请年度等信息,选择完成后下一步会要求你确认主管税务机关,这个是根据任职受雇单位自动匹配,只需要选择使用那个单位申报就可以。

选择申请年度

  1. 填写申请信息,这里的要求比较多,我下文直接提供了

填写申请信息

  • 对方纳税人名称:Google Asia Pacific Pte. Ltd.
  • 拟适用协定名称:中华人民共和国政府和新加坡共和国关于对所得避免双重征税和防止偷漏税的协定
  • 拟适用协定条款:独立个人劳务条款
  • 拟享受协定待遇收入金额(元):按实际填,比如 720
  • 预计减免税金额(元):按实际填,比如 72
  • 相关附件:使用 Google AdSense 的在线服务条款即可,从这里下载:AdSense 在线服务条款
  • 申请人信息会自动补全
  • 在中国境内是否有住所:是
  • 有住所个人提供因户籍、家庭、经济利益关系而在中国境内习惯性居住的证明材料或说明材料:多选,可以选户籍或者经济利益关系,选择后需要上传身份证、户口簿,我还补充了自己公司的营业执照

在中国境内是否有住所填写

  • 证明领取方式:电子领取

点击下一步提交确认等待主管机关审核即可。

审核通过后没有通知,需要自己去登录网站去查看,如果有问题的话,可能会被拒绝,或者主管机关会给你打电话。

目前我已经通过了,1 号政策开放的时候提交了一次,前几天主管机关下级给我打电话说没有下发成功,让我撤销重新提交一次,这次提交后两三天就通过了。

HMAC 签名编码的坑:Go 和 PHP 的不同处理方式

2025-03-06 17:19:07

在开发过程中,我们经常使用 HMAC(散列消息认证码)对数据进行签名,以确保数据完整性和身份验证。

然而,不同编程语言在对签名数据进行编码时可能会有所不同,导致相同的 HMAC 计算在不同语言中产生不同的结果。

这篇文章也是因为我直接将 PHP 的签名算法扔给 ChatGPT 生成,并没有实际测试,导致客户反馈签名计算失败,测试后才发现的。

本文将以 Go 和 PHP 为例,探讨为什么直接对 HMAC 签名进行 Base64 编码与先转换为 16 进制字符串再编码的结果不同。

代码示例

Go 代码

package main

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/base64"
    "encoding/hex"
    "fmt"
)

func main() {
    data := "hello"
    password := "123456"
    h := hmac.New(sha1.New, []byte(password))
    h.Write([]byte(data))
    signatureBytes := h.Sum(nil)

    // 直接对 HMAC 结果进行 Base64 编码
    base64Signature := base64.StdEncoding.EncodeToString(signatureBytes)
    fmt.Println(base64Signature) // 输出:NYSQUfYBHG0EZ6pU+r+Iw4CvPIQ=

    // 先转换成 16 进制字符串,再进行 Base64 编码
    hexString := hex.EncodeToString(signatureBytes)
    base64OfHex := base64.StdEncoding.EncodeToString([]byte(hexString))
    fmt.Println(base64OfHex) // 输出:MzU4NDkwNTFmNjAxMWM2ZDA0NjdhYTU0ZmFiZjg4YzM4MGFmM2M4NA==
}

PHP 代码

<?php
$data = "hello";
$password = "123456";

// 直接对 HMAC 结果进行 Base64 编码
echo base64_encode(hash_hmac('sha1', $data, $password, true));
// 输出:NYSQUfYBHG0EZ6pU+r+Iw4CvPIQ=

echo "\n";

// 先转换成 16 进制字符串,再进行 Base64 编码
echo base64_encode(hash_hmac('sha1', $data, $password));
// 输出:MzU4NDkwNTFmNjAxMWM2ZDA0NjdhYTU0ZmFiZjg4YzM4MGFmM2M4NA==
?>

为什么结果不同?

表面上看,Go 和 PHP 代码的逻辑是相同的,但它们的 Base64 结果却不同。

其根本原因在于编码前的输入数据不同。

PHP 参数定义文档

hash_hmac(
    string $algo,
    string $data,
    #[\SensitiveParameter] string $key,
    bool $binary = false
): string

PHP 手册中也提到了:当 binary 设置为 true 输出原始二进制数据,设置为 false 输出小写 16 进制字符串。

原始二进制 vs. 16 进制字符串

  1. 原始二进制数据
    • 在 Go 代码中,signatureBytes 是 HMAC 计算出的二进制数据。
    • 在 PHP 代码中,hash_hmac('sha1', $data, $password, true) 也返回二进制数据。
    • 直接对这些二进制数据进行 Base64 编码,输出的是编码后的 HMAC 结果。
  2. 16 进制字符串转换
    • 在 PHP 中,hash_hmac('sha1', $data, $password) 默认返回 16 进制字符串,每个字节被转换成 2 个字符。
    • 在 Go 中,hex.EncodeToString(signatureBytes) 也会将二进制数据转换为 16 进制字符串。
    • 由于 16 进制字符串的长度是原始二进制数据的 2 倍,在进行 Base64 编码时,最终结果也会完全不同。

Base64 编码的作用

Base64 编码的主要作用是将二进制数据转换为文本格式,便于在 URL 或 JSON 等环境中传输。

它不会改变数据的内容,而是按照固定的方式将每 3 个字节转换为 4 个可打印字符。

因此,输入数据的不同会直接影响最终的编码结果。

如何保证一致性?

如果希望跨语言 HMAC 计算保持一致,建议:

  • 确保 Base64 编码前的数据格式一致,统一使用二进制数据进行编码。
  • 在 PHP 中,使用 hash_hmac('sha1', $data, $password, true) 以获取二进制结果。
  • 在 Go 中,直接使用 base64.StdEncoding.EncodeToString(signatureBytes),避免中间转换为 16 进制字符串。

结论

  • 直接对 HMAC 结果进行 Base64 编码,能保持原始数据格式,保证数据可还原。
  • 先转换为 16 进制字符串再进行 Base64 编码,会导致数据翻倍,最终的编码结果不同。
  • 在不同语言间使用 HMAC 签名时,务必保证编码方式的一致性,以避免验证失败。

希望这篇文章能帮助你理解 HMAC 签名在不同语言中的编码差异,并在开发中避免类似的问题!