SSH和GPG非对称密钥有何不同?为什么Git支持使用GPG签名而不是使用SSH代理?
SSH和GPG非对称密钥有何不同?为什么Git支持使用GPG签名而不是使用SSH代理?
2022年9月更新:1Password支持生成和存储用于Git提交签名的SSH密钥,被GitHub认可。
更新2021:
OpenSSH 8.2+已经可用(例如打包在Git For Windows 2.33.1中),并且“现在可以使用您的SSH密钥对任意数据进行签名”(Andrew Ayer),包括Git中的提交。
Andrew指向{{link4:git/git
PR 1041“ssh signing:Add commit & tag signing/verification via SSH keys using ssh-keygen”}}, 现在使用Git 2.34(2021年11月)
{{link6:gpg.format
}}将有一个新值“ssh
”
FStelzer
)。gitster
)在commit 18c6653中合并,日期为2021年10月25日)
ssh签名
:使用ssh-keygen验证签名签署者:Fabian Stelzer
为了验证ssh签名,我们首先调用ssh-keygen -Y find-principal
来通过公钥从allowedSignersFile
中查找签名主体。gpg.ssh.allowedSignersFile
(参见ssh-keygen(1)
“ALLOWED SIGNERS”),其中包含有效的公钥和主体(通常为user@domain
)。gpg.ssh.revocationKeyring
或生成KRL(参见ssh-keygen(1)
“KEY REVOCATION LISTS”)。allowedSignersFile
一样,关于信任谁进行验证的考虑也适用于此。cert-authority
”作为密钥选项,以将其标记为CA,并将由其签名的所有密钥标记为对此CA有效。ssh-keygen(1)
中的“CERTIFICATES”。
git config
现在在其手册页面中包含了:
gpg.ssh.allowedSignersFile
一个包含你愿意信任的 ssh 公钥的文件。
该文件由一个或多个主体和一个 ssh 公钥组成。
例如:user1@example.com,user2@example.com ssh-rsa AAAAX1...
详见 ssh-keygen(1)
中的 "ALLOWED SIGNERS"。
主体仅用于标识密钥,在验证签名时可用。
SSH 没有像 gpg 那样的信任级别概念。为了能够区分有效签名和受信任的签名,
当公钥存在于 allowedSignersFile
中时,签名验证的信任级别设置为 fully
。
否则,信任级别为 undefined
,git verify-commit/tag 将失败。
此文件可以设置到存储库之外的位置,每个开发人员都维护自己的信任存储。
中央存储库服务器可以从具有推送访问权限的 ssh 密钥自动生成此文件以验证代码。
在企业环境中,此文件可能是在全局位置生成的,使用已处理开发人员 ssh 密钥的自动化。
只允许签名提交的存储库可以将该文件存储在存储库本身中,使用相对于工作树顶部的路径。 这样,只有已经具有有效密钥的提交者才能添加或更改密钥。
使用带有 cert-authority 选项的 SSH CA 密钥
(详见 ssh-keygen(1)
中的 "CERTIFICATES")也是有效的。
gpg.ssh.revocationFile
SSH KRL 或吊销的公钥列表(不包括主体前缀)。
详见 ssh-keygen(1)
。
如果在此文件中找到公钥,则始终将其视为信任级别为 "never",签名将显示为无效。
通过 Git 2.35(2022年第一季度),使用SSH密钥扩展对象签名,并学会在验证时注意密钥有效期时间范围。
请查看 提交 50992f9, 提交 122842f, 提交 dd3aa41, 提交 4bbf378, 提交 6393c95, 提交 30770aa, 提交 0276943, 提交 cafd345, 提交 5a2c1c0 (2021年12月09日) 由 Fabian Stelzer (FStelzer
) 提交。
(由 Junio C Hamano -- gitster
-- 合并于 提交 d2f0b72, 2021年12月21日)
ssh签名
:使verify-commit考虑密钥生命周期签署者:Fabian Stelzer
如果在allowedSigners
文件中为此签名密钥配置了有效日期,则验证应检查密钥在提交时是否有效。check_signature
调用中都存在时间戳信息。signature_check
结构中设置payload_type
来向我们提供有关负载的一些信息即可。payload_type
字段和枚举以及payload_timestamp
到结构体`signature_check`-Overify-time={payload_timestamp}
以进行所有ssh-keygen验证调用git config
现在在其手册页面中包括以下内容:
自OpensSSH 8.8以来,该文件允许使用valid-after和valid-before选项指定密钥生命周期。
如果签名密钥在签名创建时是有效的,则Git将标记签名为有效。
这使用户可以更改签名密钥而不会使所有先前生成的签名无效。
key :: ecdsa-sha2-nistp256 ”)。
查看 提交 3b4b5a7,提交 350a251(2021年11月19日)由Fabian Stelzer (FStelzer
)。
(由Junio C Hamano -- gitster
--合并于提交 ee1dc49,2021年12月21日)
ssh签名
:支持非 ssh-* 密钥类型
签署者:Fabian Stelzer
user.signingKey
配置用于 ssh 签名,支持包含密钥的文件路径或为方便起见的 ssh 公钥字面字符串。
为了区分这两种情况,我们检查前几个字符是否包含 "ssh-
",这不太可能是路径的开头。
ssh 支持其他未以 "ssh-
" 为前缀的密钥类型,目前将被视为文件路径并因此无法加载。
为了解决这个问题,我们将前缀检查移动到自己的函数中,并引入前缀 key::
用于字面 ssh 密钥。
这样,当新的密钥类型可用时,我们就不需要添加新的密钥类型了。
现有的 ssh-
前缀保留了与当前用户配置的兼容性,但从官方文档中删除以防止使用。
git config
现在在其man页面中包含以下内容:
如果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) 开始,会默认包含标签信息在合并信息中,这是之前由于意外而被删除的。
查看 提交 c39fc06 (2022年1月10日),作者为Taylor Blau (ttaylorr
)。
(由Junio C Hamano -- gitster
--合并于提交 cde28af,2022年1月12日)
fmt-merge-msg
:防止使用已签名标签后释放
报告者:Linus Torvalds
签署者:Taylor Blau
When merging a signed tag, fmt_merge_msg_sigs()
is responsible for populating the body of the merge message with the names of the signed tags, their signatures, and the validity of those signatures.
In 0276943 ("ssh signing: use sigc struct to pass payload", 2021-12-09, Git v2.35.0-rc0 -- merge listed in batch #4), check_signature()
was taught to pass the object payload via the sigc struct instead of passing the payload buffer separately.
In effect, 0276943 causes buf
, and sigc.payload
to point at the same region in memory.
This causes a problem for fmt_tag_signature()
, which wants to read from this location, since it is freed beforehand by signature_check_clear()
(which frees it via sigc's payload
member).
That makes the subsequent use in fmt_tag_signature()
a use-after-free.
As a result, merge messages did not contain the body of any signed tags.
Luckily, they tend not to contain garbage, either, since the result of strstr()-ing the object buffer in fmt_tag_signature()
is guarded:
const char *tag_body = strstr(buf, "\n\n");
if (tag_body) {
tag_body += 2;
strbuf_add(tagbuf, tag_body, buf + len - tag_body);
}
Resolve this by waiting to call signature_check_clear()
until after its contents can be safely discarded.
Harden ourselves against any future regressions in this area by making sure we can find signed tag messages in the output of fmt-merge-msg, too.
原始答案(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,但也可以使用其他方式(commit 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一起使用。
这意味着第一个验证脚本仍然可能有效(提交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相反,后者无法仅使用openssh实现(它需要openssl)。
正如 torek {{commented:所述}}, 理论上使用SSH是可能的,只是不方便。
此外,PGP具有额外的功能(尽管Git本身仅调用一些外部软件,但像密钥吊销之类的功能在这些情况下非常有用)。
在 Git 2.37 (Q3 2022) 中,解释了 ssh.defaultKeyCommand
:
请参见 commit ce18a30(由 Fabian Stelzer (FStelzer
) 于 2022 年 6 月 8 日提交)
(由 Junio C Hamano -- gitster
-- 合并于 commit 686790f,2022 年 6 月 15 日)
gpg文档
:更好地解释ssh.defaultKeyCommand的使用方法
签名作者:Fabian Stelzer
对于gpg.ssh.defaultKeyCommand
,使用ssh-add -L
并不是一个好的建议。
它可能会根据已知密钥的顺序切换密钥,并且仅支持ssh-*
而不支持ecdsa
或其他密钥。
明确表示我们期望一个以key::
为前缀的文字密钥,给出有效的示例用例,并将user.signingKey
作为首选选项。
git config
现在在其手册页面中包括:
请求签名。 成功退出后,预期在其输出的第一行带有前缀 key ::
的有效ssh公钥。
这允许脚本在不可能静态配置user.signingKey
时动态查找正确的公共密钥。
例如,当密钥或SSH证书经常轮换或选择正确的密钥取决于对git未知的外部因素时。
同时,由Fabian Stelzer(FStelzer
)于2022年6月8日提交的ce18a30与之结合。
(由Junio C Hamano -- gitster
--于2022年6月15日在commit 686790f中合并)
686790f6c1
:合并分支'fs/ssh-default-key-command-doc'
文档更新:fs/ssh-default-key-command-doc
:gpg文档:更好地解释了ssh.defaultKeyCommand
的使用方法。
gpg.ssh.defaultKeyCommand
:
当未设置user.signingkey
并请求ssh签名时,将运行此命令。
成功退出后,预期在其输出的第一行中有一个带有key::
前缀的有效ssh公钥。
这允许脚本在无法静态配置user.signingKey
时动态查找正确的公钥。
例如,当密钥或SSH证书经常轮换或选择正确的密钥取决于git不知道的外部因素时。
为什么不应该使用ssh
来签署提交的原因是密码学的一个常规规则:您不应该将相同的密钥用于不同的应用程序/用例。
在SSH中,您使用密钥进行身份验证,但这与签署提交不同。对于这个问题,GPG更适合,因为它已经广泛用于签署电子邮件、文件等。
ssh
密钥来签署您的提交。 - Marcos Tapajós值得一提的是,目前正在进行工作以允许使用SSH密钥进行签名(和验证):https://lore.kernel.org/git/pull.1041.git.git.1625559593910.gitgitgadget@gmail.com/
这将在有限的(例如公司)环境中非常有价值,因为Git目前是处理GPG的唯一原因,而仅使用SSH可以为用户节省一些密钥管理和软件管理开销...
GPG
是有道理的,因为它共享分散式设计原则。 - Dan Kowalczyk