ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSH Key与GPG Key怎么区分?一文搞懂Git认证与签名机制

SSH Key与GPG Key怎么区分?一文搞懂Git认证与签名机制 说实话提起 SSH Key 和 GPG Key很多刚接触 Git 的开发者第一反应是不都是密钥吗有什么好区分的我当年也这么想过直到有一次在 GitHub 上看到一个仓库所有 commit 都带绿色 Verified 徽章而另一个仓库什么都没有才意识到这两把“钥匙”干的事压根不一样。后来做团队协作、整理开源项目才彻底把这两个机制吃透SSH Key 解决的是“你怎么证明你有权限访问仓库”GPG Key 解决的是“你怎么证明这个提交确实是你写的”。一个是门禁卡一个是骑缝章职责完全不同。这篇文章不搞教科书式罗列直接从我自己的实操经历出发把 SSH Key 和 GPG Key 的生成、配置、底层原理、常见坑一次讲清楚。不管你是刚装好 Git 的新手还是已经在团队里被“commit 签名校验”卡过的老手都能在这篇文章里找到可以直接照抄的命令和排查思路。1. 先把概念掰扯清楚SSH Key 与 GPG Key 各自解决什么问题1.1 SSH Key门禁卡证明“我能进这个仓库”SSH Key 在 Git 里最常用的场景就是替代 HTTPS 密码认证。你向 GitHub、GitLab 这类远程仓库推送代码时远程服务器需要确认“你是你”。SSH 采用非对称密钥对机制本地存私钥远程服务器存公钥。当你发起连接时服务器会生成一个随机挑战发给你的客户端客户端用私钥对这个挑战做签名服务器再用你预留的公钥验证签名是否匹配。验证通过你才能执行 push、pull、fetch 这些操作。用生活里的事来类比SSH Key 就像写字楼的门禁卡。门禁系统只认“卡号”不关心你这个人叫什么名字、平时干了什么。你刷得进去就能在大楼里走动刷不进去连大门都进不了。生成 SSH Key 的命令大多数人都熟ssh-keygen -t ed25519 -C your_emailexample.com生成后默认会出现在~/.ssh/id_ed25519和~/.ssh/id_ed25519.pub前者是私钥后者是公钥。把.pub文件内容贴到 GitHub 的 SSH Keys 设置页本地的私钥保护好推送代码时就再也不用输密码了。这里有个容易忽略的细节SSH 的“签名”和 GPG 的“签名”不是一回事。SSH 签名的目的仅仅是为了完成实时的身份认证服务器验证一次、通过一次这个签名就作废了它不附着在代码或提交记录上。1.2 GPG Key印章和签字证明“这个提交确实是我写的”GPG Key 全称是 GNU Privacy Guard最初的设计目标是加密和签名邮件、文件之类的数据。到了 Git 的世界里它被用来给 commit 和 tag 做数字签名。当你在本地提交代码时Git 会对提交内容计算摘要然后用 GPG 私钥对这个摘要进行签名最终生成的签名信息会嵌进 commit 对象里。之后任何人拿到你的 GPG 公钥都能验证这个提交是不是真的出自你手有没有被篡改过。这就是 GitHub 上那个绿色 Verified 徽章的来历。平台用你上传的 GPG 公钥去验证上报的 commit 签名验证通过就给你打上可信标签。如果有人伪造了一个提交冒充你提交了恶意代码由于他没有你的私钥签名验证必定失败这层机制就防住了最常见的“身份冒用”风险。生活化的类比是合同盖章或者签名。合同上的签字意味着“我认这个东西我负这个责”而门禁卡只能证明你能进办公室不能证明你签过的每一份合同都是有效的。所以从语义上看GPG Key 在 Git 中承担的责任比 SSH Key 要重得多。1.3 最容易混淆的两个使用场景第一个容易混淆的点是为什么我配了 SSH KeyGitHub 上还是没有 Verified因为 SSH Key 只作用于推送链路它认证的是“你作为用户有没有权限操作仓库”而不是“这个提交本身是不是你写的”。提交内容里的 author、committer 字段是可以用git commit --author之类的选项伪造的SSH Key 完全拦不住这个。第二个容易搞混的点是GPG Key 能不能替代 SSH Key 做 Git 推送经典回答是不能。Git over SSH 的传输层认证用的是 SSH 协议GPG Key 不参与这个流程。当然现在 GitHub 也支持用 SSH 密钥来签名 commit通过gpg.format ssh这种配置实现但那是另一套方案先按下不表后面我在经验部分单独聊。2. 生成与配置两种密钥的完整实操记录2.1 5分钟搞定 SSH Key 生成与远程仓库接入我装好 Git 后的第一件事永远是把 SSH Key 配好。下面是完整流程你可以直接跟着操作。第一步生成密钥对。建议用 Ed25519 算法而不是传统的 RSA。原因很简单Ed25519 密钥更短、生成速度更快、安全性也足够高OpenSSH 6.5 以上版本都支持。如果你的服务器比较老再退回去用 RSA 4096 也不迟。ssh-keygen -t ed25519 -C 你的邮箱地址命令执行后会问你保存位置和 passphrase。保存位置直接回车用默认路径~/.ssh/id_ed25519就行。passphrase 我强烈建议设置它是私钥的额外保护层即使私钥文件泄露没有 passphrase 也无法使用。第二步把公钥复制出来。我习惯用下面这个命令直接输出到终端手动复制到 GitHub 设置页。cat ~/.ssh/id_ed25519.pub打开 GitHub → Settings → SSH and GPG keys → New SSH key把输出内容粘贴进去Title 可以写“ThinkPad X1”之类方便识别的设备名。第三步验证连接。用 GitHub 官方的测试命令ssh -T gitgithub.com看到Hi yourname! Youve successfully authenticated, but GitHub does not provide shell access.就说明通了。这里额外说一下 HTTPS 仓库怎么平滑切换成 SSH。如果你的仓库已经用https://github.com/user/repo.git克隆下来了不需要重新克隆直接改远端地址git remote set-url origin gitgithub.com:user/repo.git改完之后git push走的就是 SSH 通道不用再输入用户名密码。2.2 生成 GPG Key 并让每个 commit 都带签名GPG 的配置比 SSH 稍微绕一点但也就是十分钟的事。第一步生成密钥gpg --full-generate-key交互过程会问你算法和密钥长度。我一般选默认的 RSA and RSA密钥长度选 4096过期时间看你自己个人项目选“永不过期”其实问题不大。真实姓名和邮箱一定要认真填最好和 Git 的user.name、user.email保持一致不然验签时对不上号后面演示的时候会乱。生成完成后用这个命令查看密钥 IDgpg --list-secret-keys --keyid-format LONG输出结果里sec那一行会对应一个 16 位的十六进制字符串类似3A93 1A08 B3C9 1D82这就是你需要记下来的密钥 ID。接下来把这个 ID 配给 Gitgit config --global user.signingkey 3A931A08B3C91D82接着让 Git 默认所有提交都签名同时指定 gpg 程序路径有些系统里 gpg 不在默认 PATH 下git config --global commit.gpgSign true git config --global gpg.program $(which gpg)然后导出一份公钥贴到 GitHub 上gpg --armor --export 3A931A08B3C91D82复制输出内容粘贴到 GitHub 的 GPG Key 设置页。在本地提交时Git 会调起 gpg-agent 弹出输入 passphrase 的窗口输入后提交成功。推送后再看 GitHub 页面新提交后面那个绿色 Verified 徽章就出来了。2.3 密钥文件管理与备份心得这些密钥文件放的位置很讲究。SSH 私钥在~/.ssh目录下该目录权限建议设置为 700私钥文件权限设置为 600。系统有这个要求更是个安全习惯。我见过有人把id_rsa文件权限改成 644结果 ssh 直接罢工报Permissions too open。微信传输、网盘上传私钥这种事千万别干一定要有意识。GPG 的密钥存放在~/.gnupg下迁到新电脑或者做备份时自己私钥的导出方式如下gpg --export-secret-keys --armor my-gpg-private-key.asc公钥可以重新从 keyserver 拉取私钥则必须自己保存好。我习惯把导出的私钥文件放进加密的 U 盘或者密码管理器绝不放云笔记。另外建议生成密钥时顺手把撤销证书也做出来gpg --gen-revoke 你的密钥ID撤销证书是最后一道防线。密钥泄露、邮箱更换、设备丢失的时候它可以帮你第一时间公告“原密钥作废”防止别人继续用你的身份签名。3. 底层机制拆解签名和认证到底是怎么运转的3.1 SSH 的挑战-响应认证流程SSH 认证的核心思路是“挑战-响应”。我画个简化流程你就明白了客户端发起 SSH 连接远程服务器不知道你是谁于是生成一个随机字符串作为挑战发给客户端同时拿着客户端公钥等待回复。客户端收到挑战后用私钥对这个字符串做签名把签名结果返回给服务器。服务器用公钥解签、比对一致就放行否则拒绝。整个过程密钥本身不会在网络里传输真正在网络上跑来跑去的是一个随机挑战和对应的签名。这比 HTTPS 密码认证要安全得多因为密码会出现在握手报文中有被截获的风险而 SSH 私钥始终留在本地服务器拿到的公钥怎么截获都没用。也正因如此SSH Key 一旦配置好日常使用中你根本感知不到“认证”的发生。它发生在数据流进入 Git 协议之前属于传输层的安全检查。3.2 GPG 签名与验签的完整链条GPG 签名 commit 的过程比 SSH 认证复杂一点。当你在本地执行git commit -S时Git 先把要提交的内容整理成 commit 对象计算出一个哈希摘要接着用 GPG 私钥对摘要做签名最终把签名和 commit 对象一起写入仓库。检验的时候验证者先拿到你的公钥对签名做解密得到一个摘要值同时对 commit 对象重新计算哈希比对两份摘要是否一致。摘要一致说明这个 commit 从签名那一刻起就没有被改动过而且签名者确实持有对应的私钥。这就是数字签名的不可抵赖性。本地验证自己历史提交的有效性可以用git log --show-signature能看到Good signature from ...就表示签名有效。如果看到Cant check signature多半是公钥没导入或者密钥不匹配。这里有一个值得说透的点为什么 Git 需要单独做签名而不是靠 SSH 认证就够了因为 SSH 认证发生在“推送”这个动作发生时一旦代码推上去后续如果有人克隆下来改了内容再推上去只要他有仓库写权限按 SSH 的规则他就能操作。但提交记录里的 author 字段可以伪造你没法证明某条 commit 到底是谁在什么时候写的。GPG 签名把“代码内容”和“作者身份”绑定在一起这就没法赖账。3.3 为什么开源项目对签名提交要求越来越高这两年很多知名开源项目在 CI 里直接加了“所有 commit 必须签名”的校验不签名的提交会被机器人打回。原因不复杂公共仓库写权限一旦被突破攻击者可以往代码里塞后门再把提交伪造成核心维护者的名字。GPG 验签机制会让这类伪装暴露得彻彻底底。还有一个细节不仅 commit 可以签名tag 也可以。发布版本的时候用git tag -s v1.2.3 -m release 1.2.3给 tag 做签名再把公钥上传到公开 keyserver使用者就能验证“这个 v1.2.3 确实是官方维护者打的 tag”。发布物安全这件事在软件供应链攻击频繁的当下已经不是锦上添花而是标配。4. 真实踩坑记录常见问题与排查思路4.1 SSH 认证失败的经典原因我见过太多“SSH 认证失败”的求助帖了问题来了先别慌按顺序排查。第一种情况提示Permission denied (publickey)。先看仓库远端地址是不是gitgithub.com:...这种 SSH 格式如果是 HTTPS 格式的 URLSSH Key 根本不会参与认证。确认格式没问题再看公钥有没有正确贴到远程平台的 SSH Keys 列表里以及是不是贴到了另一个账号的钥匙列表里。第二种情况提示load pubkey ... invalid format。大概率是生成的密钥和当前系统 ssh 版本不兼容或者你复制公钥的时候换行符被改了重新用cat ~/.ssh/id_ed25519.pub复制一遍试试。第三种情况多设备、多账号混用。比如你同时有 GitHub 工作账号和私人账号两把密钥都放在同一个~/.ssh目录下SSH 默认会拿第一个匹配的私钥去试常常试错。解决办法是改~/.ssh/configHost github-work HostName github.com User git IdentityFile ~/.ssh/work_ed25519 Host github-personal HostName github.com User git IdentityFile ~/.ssh/personal_ed25519然后克隆仓库时把地址改成gitgithub-work:user/repo.gitSSH 就会自动选择对应的私钥。第四种情况比较隐蔽就是 IDE 里拉取仓库报认证失败。很多人在 IntelliJ IDEA 里新建项目、选择 Git 仓库时发现 SSH 认证一直失败但命令行里明明能正常操作。通常是因为 IDEA 用的是内置的 SSH 实现而不是系统的 OpenSSH。你需要在 Settings → Version Control → Git 中找到 SSH executable 选项把它从 Built-in 切换成 Native然后在 SSH Key 设置里指向你的私钥路径。这类排队问题命令行验证通了再回 IDE 一般就正常了。我习惯用ssh -vT gitgithub.com带 verbose 参数来做最终定位日志里会清楚显示加载了哪个密钥文件、服务器接受了哪个公钥问题出在哪一层一目了然。4.2 GPG 签名提交失败的常见排查GPG 报错花里胡哨但绝大多数也就是几个固定问题。最常见的一个提交时报gpg: signing failed: Inappropriate ioctl for device。这通常发生在 Linux 或 WSL 环境里原因是 GPG 想要弹窗输入 passphrase但它连不上控制终端。解决方案是在 shell 配置里加一行export GPG_TTY$(tty)然后source ~/.bashrc或重启终端问题基本就消失了。第二个高频问题gpg: Sorry, no pinentry could be launched。说明你的系统缺少 pinentry 程序或者窗口环境不对。macOS 用户一般装了pinentry-mac就能解决Linux 用户检查一下 pinentry 包的安装状态。第三个问题提交时 Git 提示error: gpg failed to sign the data但没有具体错误。先手动跑一遍echo test | gpg --clearsign看 GPG 本身能不能正常工作能正常工作再用git config --global --list检查user.signingkey是否配置正确。多个 GPG 密钥同时存在时Git 容易拿错 key建议在~/.gnupg/gpg.conf里加一行default-key 你的密钥ID一锤定音。第四个问题密钥过期或者被人 revoke 了。GPG 密钥是有生命周期的过期后签名校验会不通过。重新生成密钥、更新远程配置、再向远程平台加新公钥三步走解决。注意旧公钥最好保留在平台上一段时间因为历史 commit 还挂着旧签名突然删掉会导致历史记录全部显示成 Unverified。还有一个不算异常但很多人问的点我配置了commit.gpgSign true但偶尔不想签名某个临时提交怎么办可以用git commit --no-gpg-sign -m temp临时绕过但这种“临时”行为在强制签名校验的项目里是不允许的自己心里有数。4.3 分支合并场景下的签名问题前面热词里有人提到“git 分支合并”这个场景和签名机制确实有交集。比如你在本地合并一个功能分支时系统会自动生成一个 merge commit如果全局开启了commit.gpgSign true这个 merge commit 也会被签名。正常情况下没有什么感知但万一签名环境有问题合并会被卡住反映出来的现象就是git merge直接报错。遇到这种情况优先检查排气终端问题也就是前面的GPG_TTY。如果只是想快速完成这次合并也可以临时再加--no-gpg-sign参数。不过还是建议先把 GPG 环境配好毕竟自动签名的 merge commit 在审计时非常有价值每个合并都能追溯到具体操作者。4.4 密钥轮换与多设备迁移实操换电脑的时候SSH 和 GPG 都不只是把文件拷过去就完事。SSH 这边我建议新设备生成新密钥把新公钥加到远程平台旧私钥在旧设备上删掉。这样做的好处是设备丢了最多吊销一把密钥不影响整体身份。GPG 这边则不同签名身份的延续性很重要如果你换设备就换主密钥历史提交的签名验证会全部断掉。正确做法是把原来的私钥导出、导入到新设备继续保持同一身份。导入私钥的命令gpg --import my-gpg-private-key.asc导入后还要设置信任级别不然会提示密钥不可信gpg --edit-key 你的密钥ID # 在交互界面输入 trust然后选择 5I trust ultimately完成之后所有旧签名和未来的签名都能用这把密钥正常验证。这样多设备共用一个 GPG 主身份SSH 每台设备独立是我觉得最灵活的组合。5. 关于选型和效率我的几点实在经验5.1 什么场景下只配 SSH 就够如果只是自己的私人仓库、个人博客源码、公司内部项目不涉及对外展示和供应链审计那配好 SSH Key 就足够了。整个流程最省心认证速度快也没有什么弹窗输入交互push 体验非常顺滑。SSH 单点登录再加上 2FA已经能覆盖绝大多数日常开发需求。5.2 什么场景必须把 GPG 安排上一旦项目要开源、要发布公共版本或者你在一个要求“提交可审计”的团队里强烈建议全量开启 GPG 签名。另一个判断标准是如果有人冒用你的身份向仓库提交恶意代码你会不会觉得后怕。怕的话就上签名。现在 GitHub 已经默认所有 2022 年后的新提交都尝试用 GPG/SSH 签名做验证大平台的趋势已经很明确了。5.3 几个关键时刻能救命的实用技巧第一个技巧给 Git 配置一个“总是签名”的脚本同时避免签名失败时静默跳过。第二个技巧在.gitconfig中按仓库目录区分配不同的身份和签名密钥工作库签工作身份个人库签个人身份互不干扰。我的个人习惯是笔记本上用独立的 SSH 密钥做认证而签名统一用同一把 GPG 主密钥换电脑也不换签名身份。这套组合我用了快三年最直观的收益是 GitHub 上所有历史提交干干净净都是 Verified团队 review 的时候也不用反复去核实“这个提交是不是本人操作”。工具层面其实没有复杂操作但把两个机制的分工想明白之后你就不会再在“为什么我的提交没有小绿标”这种问题上浪费一整天了。
返回列表