18

SSH 和 GPG 非对称密钥有什么区别,为什么 git 支持使用 GPG 签名而不是使用 SSH 代理?

4

4 回答 4

19

2021 年更新:

OpenSSH 8.2+ 可用(例如打包在Git For Windows 2.33.1 中),并且“现在可以使用您的 SSH 密钥签署任意数据”(Andrew Ayer),包括 Git 中的提交。

Andrew 指出git/gitPR 1041“ssh 签名:使用 ssh-keygen 通过 SSH 密钥添加提交和标签签名/验证”现在使用 Git 2.34(2021 年 11 月)

gpg.format将有一个新值“ ssh

设置gpg.format = sshuser.signingkey到一个 ssh 公钥字符串(如来自 authorized_keys 文件)和提交/标签可以使用来自您的 ssh-agent 的私钥进行签名。

安德鲁补充道:

始终警惕将加密密钥重新用于不同的协议。如果不小心操作,就会存在跨协议攻击的风险

例如,如果 Git 签名的消息结构与 SSH 协议消息的结构相似,则攻击者可能能够通过盗用 SSH 脚本的签名来伪造 Git 工件。

幸运的是,SSH 协议消息的结构和由 ssh-keygen 签名的消息的结构非常不同,因此没有混淆的风险。

这来自:

Git 2.34(2021 年第四季度):使用 ssh 公共加密进行对象和推送证书签名。

请参阅Fabian Stelzer ( )的提交1bfb57f、提交f265f2d提交 3326a78提交 facca53提交 4838f62提交 fd9e226提交 29b3157提交 64625c7提交 b5726a5(2021 年 9 月 10 日) 。(由Junio C Hamano 合并 -- --提交 18c6653中,2021 年 10 月 25 日)FStelzer
gitster

ssh signing: 使用 ssh-keygen 验证签名

签字人:Fabian Stelzer

为了验证 ssh 签名,我们首先调用ssh-keygen -Y find-principalallowedSignersFile.
如果找到密钥,那么我们进行验证。
否则我们只验证签名而不能验证签名者的身份。

验证使用包含有效公钥和主体(通常为)的gpg.ssh.allowedSignersFile(参见“允许的签名者” )。 根据环境,此文件可以由单个开发人员管理,或者例如由中央存储库服务器从具有推送访问权限的已知 ssh 密钥生成。 该文件通常存储在存储库之外,但如果存储库只允许签名提交/推送,用户可能会选择将其存储在存储库中。ssh-keygen(1)user@domain

要撤销密钥,请将不带主体前缀的公钥放入gpg.ssh.revocationKeyring或生成 KRL(请参阅ssh-keygen(1)“密钥撤销列表”)。
关于信任谁进行验证的考虑与allowedSignersFile申请相同。

也可以对这些文件使用 SSH CA 密钥。
在主体和密钥之间添加“ cert-authority”作为密钥选项,将其标记为 CA,并且所有由它签名的密钥都对该 CA 有效。
请参阅 中的“证书” ssh-keygen(1)

git config现在在其手册页中包含:

gpg.ssh.allowedSignersFile

包含您愿意信任的 ssh 公钥的文件。该文件由一行或多行主体组成,后跟一个 ssh 公钥。
例如:有关详细信息,user1@example.com,user2@example.com ssh-rsa AAAAX1...
请参阅ssh-keygen(1)“允许的签名者”。
主体仅用于识别密钥,在验证签名时可用。

SSH 没有像 gpg 那样的信任级别概念。为了能够区分有效签名和可信签名,签名验证的信任级别设置为fully公钥存在于allowedSignersFile.
否则信任级别是undefined并且 git verify-commit/tag 将失败。

该文件可以设置在存储库之外的位置,并且每个开发人员都维护自己的信任库。中央存储库服务器可以从具有推送访问权限的 ssh 密钥自动生成此文件,以验证代码。
在公司环境中,此文件可能是在全球位置从已经处理开发人员 ssh 密钥的自动化生成的。

仅允许签名提交的存储库可以使用相对于工作树顶层的路径将文件存储在存储库本身中。这样,只有拥有有效密钥的提交者才能在密钥环中添加或更改密钥。

使用带有 cert-authority 选项的 SSH CA 密钥(请参阅ssh-keygen(1)“证书”)也是有效的。

gpg.ssh.revocationFile

SSH KRL 或已撤销的公钥列表(不带主体前缀)。
详情请参阅ssh-keygen(1)
如果在此文件中找到公钥,则它将始终被视为具有“从不”的信任级别,并且签名将显示为无效。


使用 Git 2.35 (Q1 2022),扩展使用 SSH 密钥对对象的签名,并学会在验证时注意密钥有效时间范围。

参见Fabian Stelzer ( )的提交50992f9、提交122842f提交 dd3aa41提交 4bbf378提交 6393c95提交 30770aa提交 0276943提交 cafd345提交 5a2c1c0(2021 年 12 月 9 日) 。(由Junio C Hamano 合并 -- --d2f0b72 提交中,2021 年 12 月 21 日)FStelzer
gitster

ssh signing:使验证提交考虑密钥生命周期

签字人:Fabian Stelzer

如果在文件中为此签名密钥配置了有效的之前/之后日期,allowedSigners则验证应检查密钥在提交时是否有效。
这允许优雅的密钥翻转和撤销密钥,而不会使所有先前的提交无效。
此功能需要 openssh > 8.8。
较旧的 ssh-keygen 版本将简单地忽略此标志并使用当前时间。
严格来说,此功能在 8.7 中可用,但由于 8.7 有一个错误,使其无法在另一个需要的调用中使用,因此我们需要 8.8。

大多数调用check_signature.
但是签名者身份不是。
稍后我们将需要签名者电子邮件/姓名才能实现“首次使用时信任”功能。
由于有效载荷包含所有必要的信息,我们可以从那里解析它。调用者只需要通过在结构中
设置来向我们提供有关有效负载的一些信息。payload_typesignature_check

  • 添加payload_type字段和枚举并payload_timestamp构造`signature_check
  • 如果我们知道有效负载类型,则在尚未设置时填充时间戳
  • 将用户时区传递-Overify-time={payload_timestamp}给所有 ssh-keygen 验证调用
  • 验证提交时设置有效负载类型
  • 为已过期、尚未生效以及提交日期在密钥有效性之外和之内的密钥添加测试

git config现在在其手册页中包含:

从 OpensSSH 8.8 开始,此文件允许使用 valid-after 和 valid-before 选项指定密钥生存期。

如果签名密钥在签名创建时有效,Git 会将签名标记为有效。

这允许用户更改签名密钥,而不会使所有先前制作的签名无效。


ssh-而且,仍然使用 Git 2.35(2022 年第一季度),使用 ssh 密钥的加密签名可以通过使用“ key::”前缀机制
(例如“ key::ecdsa-sha2-nistp256”)为名称不以“”前缀开头的密钥类型指定文字密钥。

请参阅Fabian Stelzer ( ) 的提交 3b4b5a7提交 350a251(2021 年 11 月 19 日(由Junio C Hamano 合并 -- --ee1dc49 提交中,2021 年 12 月 21 日)FStelzer
gitster

ssh signing: 支持非 ssh-* keytypes

签字人:Fabian Stelzer

ssh 签名的user.signingKey配置支持包含密钥的文件的路径,或者为方便起见,支持带有 ssh 公钥的文字字符串。

为了区分这两种情况,我们检查前几个字符是否包含“ ssh-”,这不太可能是路径的开始。
ssh 支持其他不以“”为前缀的密钥类型,ssh-当前将被视为文件路径,因此无法加载。
为了解决这个问题,我们将前缀检查移到它自己的函数中,并key::为文字 ssh 键引入前缀。
这样我们就不需要在新的密钥类型可用时添加它们。
保留现有ssh-前缀以与当前用户配置兼容,但从官方文档中删除以阻止其使用。

git config现在在其手册页中包含:

如果gpg.format设置为ssh这可以包含您的私有 ssh 密钥或使用 ssh-agent 时的公共密钥的路径。或者,它可以包含直接以前缀为前缀的公钥key:: (例如:“ key::ssh-rsa XXXXXX identifier”)。

私钥需要通过 ssh-agent 提供。
如果未设置,git 将调用 gpg.ssh.defaultKeyCommand(例如:“ ssh-add -L”)并尝试使用第一个可用的键。

为了向后兼容,以“ ”开头的原始密钥ssh-,例如“ ssh-rsa XXXXXX identifier”,被视为“ key::ssh-rsa XXXXXX identifier”,但这种形式已被弃用;改用key::表格。


" git merge $signed_tag" ( man )开始从它意外使用的默认合并消息中删除标记消息,这已在 Git 2.35 (Q1 2022) 中得到纠正。

请参阅Taylor Blau ( ) 的提交 c39fc06(2022 年 1 月 10 日(由Junio C Hamano 合并 -- --提交 cde28af中,2022 年 1 月 12 日)ttaylorr
gitster

fmt-merge-msg: 防止使用签名标签释放后使用

报告人:Linus Torvalds
签字人:Taylor Blau

合并签名标签时,fmt_merge_msg_sigs()负责使用签名标签的名称、它们的签名以及这些签名的有效性填充合并消息的正文。

0276943(“ssh 签名:使用 sigc 结构传递有效负载”,2021-12-09,Git v2.35.0-rc0 -批次 #4中列出的合并)中,被教导通过 sigc 结构而不是传递对象有效负载分别传递有效载荷缓冲区。check_signature()

实际上,0276943导致buf, 和sigc.payload指向内存中的同一区域。
这导致了一个问题fmt_tag_signature(),它想要从这个位置读取,因为它是预先被释放的signature_check_clear()(它通过 sigc 的payload成员释放它)。

这使得后续的使用成为fmt_tag_signature()一个use-after-free。

因此,合并消息不包含任何签名标签的正文。
幸运的是,它们也往往不包含垃圾,因为 strstr()-ing 对象缓冲区的结果fmt_tag_signature()受到保护:

const char *tag_body = strstr(buf, "\n\n");
if (tag_body) {
  tag_body += 2;
  strbuf_add(tagbuf, tag_body, buf + len - tag_body);
}

通过等待调用signature_check_clear()直到可以安全地丢弃其内容来解决此问题。
通过确保我们也可以在 fmt-merge-msg 的输出中找到签名的标记消息,来加强我们自己以应对该领域的任何未来回归。


原始答案(2017 年):在 Git 中签署任何东西的第一个概念在提交 ec4465a、Git v0.99、2005 年 4 月(几乎从一开始)中就被引用了

/**
 * A signature file has a very simple fixed format: three lines
 * of "object <sha1>" + "type <typename>" + "tag <tagname>",
 * followed by some free-form signature that git itself doesn't
 * care about, but that can be verified with gpg or similar.
 **/

所以你的问题有腿。

第一个签名提交使用 gpg,但可以使用其他任何东西(提交 65f0d0e):

#!/bin/sh
object=${2:-$(cat .git/HEAD)}
type=$(cat-file -t $object) || exit 1
( echo -e "object $object\ntype $type\ntag $1\n"; cat ) > .tmp-tag
rm -f .tmp-tag.asc
gpg -bsa .tmp-tag && cat .tmp-tag.asc >> .tmp-tag
git-mktag < .tmp-tag
#rm .tmp-tag .tmp-tag.sig

从技术上讲,您可以使用gpg 代替 ssh。不过,我并没有经常看到相反的情况。
但是您可以将 ssh 密钥对与 PGP/GPG 一起使用
这意味着第一个验证脚本可能仍然有效(commit f336e71)......除了它需要 PGP 注释:

#!/bin/sh
GIT_DIR=${GIT_DIR:-.git}

tag=$1
[ -f "$GIT_DIR/refs/tags/$tag" ] && tag=$(cat "$GIT_DIR/refs/tags/$tag")

git-cat-file tag $tag > .tmp-vtag || exit 1
cat .tmp-vtag | sed '/-----BEGIN PGP/Q' | gpg --verify .tmp-vtag -
rm -f .tmp-vtag

所以,“为什么 git 使用 GPG 密钥而不是使用 SSH 密钥进行签名?”:这是 GPG 的本意,与 SSH 不同,SSH不能单独使用 openssh(它需要 openssl)

正如torek所评论的,理论上使用 SSH 是可行的,只是不方便。

此外,PGP 有额外的功能(不是 Git 直接使用它们——Git 本身只是调用一些外部软件——但在这些情况下,密钥撤销之类的东西很有用)。

于 2017-07-15T16:39:51.943 回答
4

您不应该使用ssh签名提交的原因是密码学的常见规则之一:您不应该为不同的应用程序/用例使用相同的密钥。

在 SSH 中,您使用密钥进行身份验证,但这与签署您的提交不同。为此,GPG 更适合,因为它已经广泛用于签署电子邮件、文件等。

于 2017-07-16T08:21:12.537 回答
3

一个可能的原因是不是每个使用 git 的人都在使用 ssh。

你可以创建一个 git repo 并且永远不要让它离开你的本地磁盘。您可以使用 git 协议、http、https 或网络文件系统……这些都不涉及 ssh,但您仍然可以签署提交,因为这与任何网络传输或提交的其他推/拉共享无关.

于 2017-07-15T19:23:34.360 回答
2

FWIW,允许使用 SSH 密钥进行签名(和验证)的工作正在进行中:https ://lore.kernel.org/git/pull.1041.git.git.1625559593910.gitgitgadget@gmail.com/

这可能在有限的(例如公司)环境中很有价值,其中 git 目前是处理 GPG的唯一原因,并且只坚持使用 SSH 可以为用户节省一些密钥管理和软件管理开销......

于 2021-11-17T09:53:54.560 回答