【看一遍就懂了】Git 代码托管平台是如何进行安全认证的?

2,008 阅读16分钟

Git 想必大家都不陌生吧,研发工作中必不可少的工具之一,它是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目,提供版本控制以及内容管理。因此奠定了 Git 逐步走向平台化的基础,代码托管平台孕育而生,主流的平台有:GitHub、GitLab、Gitee 等。要想平台化,首要任务就是在 Git 的基础上添加了安全认证机制,因此 Git 代码托管平台均采用了 HTTPSSSH 这两种协议,这两种协议都提供了身份认证,同时也能保证数据资料的安全。

今天我们主要讲的就是 Git 与 SSH 协议是如何进行安全认证的?当我们使用 Git 进行 clonepush 时 Git 是如何验证用户权限的?账号密码认证和免密认证的原理又是什么?由浅入深一起揭开这层面纱,本文通过多个反问?结合多个例子辅助理解,也希望各位朋友在阅读的时候,能够将这种多角度的思维方式变为己有。发散性的思维让你更具备创造性、更快的定位问题等诸多益处。

本文主要内容:

  • SSH 是什么?
    • SSH 基本架构
    • SSH 的优点
    • SSH 密钥对
  • SSH 安全认证
    • 基于密码对安全认证原理及流程
    • 基于密钥对安全认证原理及流程
  • Git 代码托管平台 SSH 是如何进行安全认证的?
    • Git 代码托管平台什么时候进行安全认证?

你还可能发现一些你可能不知道的事情:

  • 同一个公钥可以配置到多个 Git 平台,但是不能同时配置给一个平台下的多个账号
  • SSH 密钥认证不一定非要配置 ~/.ssh/config

PS: 讲的比较多,如果反复看几遍都看不懂的话,可以评论加关注,我再出一期视频教学

什么是 SSH?

至于 Git 代码托管平台(泛指 GitHub、GitLab、Gitee 等) 为什么选择 SSH 协议呢?SSH 是如何进行安全认证的?Git 于 SSH 认证有什么联系?要解答这些问题,我们首先需要知道什么是 SSH?它能做什么?它有哪些优点?

SSH 是安全外壳协议( Secure Shell) 的简称,它是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH 通过在网络中创建安全隧道来实现 SSH 客户端与服务器之间的连接,旨在保证非安全网络环境(例如互联网)中信息加密完整可靠。SSH最常见的用途是远程登录系统,人们通常利用SSH来传输命令行界面和远程执行命令,也可以说 SSH 是专门为远程登录会话和其他网络服务提供的安全性协议,利用 SSH 协议可以有效的防止远程管理过程中的信息泄露问题。

SSH 以非对称加密实现身份验证。身份认证有多种途径,通常我们使用人工生成一对公钥和私钥,通过生成的密钥进行认证,这样就可以在不输入密码的情况下登录。任何人都可以自行生成密钥。公钥需要放在待访问的电脑之中,而对应的私钥需要由用户自行保管。认证过程基于生成出来的私钥,但整个认证过程中私钥本身不会传输到网络中。SSH只验证提供用户是否拥有与公钥相匹配的私钥,只要接受公钥而且密钥匹配服务器就会授予许可

什么是非对称加密?非对称密码学又称公开密钥密码学是密码学的一种算法,它需要两个密钥,一个公开密钥,另一个是私有密钥;公钥用作加密,私钥则用来解密。使用公钥把明文加密后所得的密文,只能使用相对应的私有密钥才能解密并得到原本的明文,最初用来加密的公钥不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;公钥可以公开,可任意向外发布;私钥不可以公开,必须由用户自行严格秘密保管,绝不透过任何途径向任何人提供,也不会透露给被信任的要通信的另一方。

SSH 基本架构

SSH协议框架中最主要的部分是三个协议:

  • 传输层协议(The Transport Layer Protocol):传输层协议提供服务器认证,数据机密性,信息完整性等的支持。
  • 用户认证协议(The User Authentication Protocol):用户认证协议为服务器提供客户端的身份鉴别。
  • 连接协议(The Connection Protocol):连接协议将加密的信息隧道复用成若干个逻辑通道,提供给更高层的应用协议使用。

SSH协议框架中设计了大量可扩展项,比如用户自定义算法、客户自定义密钥规则、高层扩展功能性应用协议。各种高层应用协议可以相对地独立于 SSH 基本体系之外,并依靠这个基本框架,通过连接协议使用 SSH 的安全机制。比如:GitHub 等托管平台可以基于 SSH 基本框架,然后借助SFTP或SCP协议既可以安全的传输文件,并对文件加密或解密。

SSH 的优点

  • 技术成熟
  • 数据安全性高
  • 认证途径多
  • 可拓展性

SSH 密钥对

通过上面的介绍,想必大家对 SSH 协议有了一定的认识,上面我们一直提到的公钥、私钥、密钥对,现在就可以通过下表中的一些基本概念了解一下,可能会很无聊,但是结合后面的例子会更容易理解:

概念说明
密钥对任何人都可以通过密钥生成工具(例如:ssh-keygen)生成密钥,密钥分为私钥和公钥,私钥是密钥对所有者持有,不可公布,公钥可以公开,可任意向外发布。
公钥用作数据加密,使用公钥加密的数据只能使用匹配的私钥解密。
私钥用作数据解密,只能成功解密匹配的公钥加密的数据。
加密使用公钥把明文加密后所得的密文,只能使用相对应的私有密钥才能解密并得到原本的明文。即使被非法用户截获也无法获取正确的资料内容,从而保障来数据的安全性。
摘要将需要传输的文本进行 HASH(一般采用 SHA1,SHA2)计算 后获得。
签名使用私钥对需要摘要进行加密,得到的密文即被称为该次传输过程的签名。
签名验证使用公钥对签名进行解密得到摘要,将传输的文本同样进行 HASH 计算得到发送的摘要,并于解密后得到的摘要进行对比,验证传输文本过程中是否被篡改。

上面的概念可能比较乏味,下面我们通过两个例子,分别来理解一下 加密过程签名认证过程

示例1:Bob 想给 Alice 发送一条加密的消息:

  1. 用户 Alice 将自己的公钥给了 Bob 一把,
  2. 利用 Alice 提供的公钥进行加密,然后将得到的密文发送给了 Alice
  3. Alice 利用自己的私钥进行解密得到明文消息。

示例2:Bob 使用签名给 Alice 回复加密消息:

用户 Alice 没有 Bob 的公钥,Alice 又想给 Bob 发送加密消息

  1. Alice 使用自己的私钥对信息进行签名
  2. 将签名信息发送给 Bob
  3. Bob 使用 Alice 的公钥对签名进行解密得到明文消息。

通过上面两个示例,我们可以得出以下结论:

  • 无论是加密过程还是认证过程,都是通过同一对密钥来实现加密和解密
  • 加密过程是公钥加密、私钥解密
  • 认证过程不同于加密过程,利用私钥对信息进行签名,然后使用公钥解密签名和校验私钥持有者信息

SSH 安全验证

SSH 协议同时还提供了两种级别的安全认证,分别是 口令认证密钥认证,类型的不同也决定了认证形式的差异,详情见下表:

认证类型认证级别说明
口令认证基于密码的安全验证知道账号密码登录远程主机。但是,可能会有别的服务器在冒充真正的服务器,无法避免被“中间人”攻击。
密钥认证基于密钥的安全验证依靠密钥以及认证中心对密钥的认证完成认证。

SSH 基于密码的安全验证

是对账号和密码的认证,知道帐号和密码,就可以登录到远程主机,并且所有传输的数据都会被加密。但是,可能会有别的服务器在冒充真正的服务器,无法避免被“中间人”攻击。

下面我们看一下基于密码的安全验证的流程:

  • 客户端使用账号对host服务发起请求,比如:ssh user@host
  • 服务端将服务端的公钥发送给客户端
  • 客户端拿到服务端的公钥对密码进行加密,并发送至服务端
  • 服务端利用私钥对密码进行解密并校验并返回登录结果

SSH 基于密钥的安全验证

基于密钥的安全认证需要依靠密钥,也就是你必须为自己创建一对密钥,并把公有密钥放在需要访问的服务器上。客户端软件会向服务器发出请求,请求用你的密钥进行安全验证。服务器收到请求之后,先在你在该服务器的用户根目录下寻找你的公有密钥,然后把它和你发送过来的公有密钥进行比较。如果两个密钥一致,服务器就用公有密钥加密“质询”(challenge)并把它发送给客户端软件。客户端收到”质询“会让客户端自行选择是否信任,客户端根据ip和域名来选择是否信任,从而避免被“中间人”攻击。

当远程机器持有公钥,而本地持有对应私钥时,登录过程不再需要手动输入密码,常见的场景有:远程免密登录、git 免密操作等。另外为了额外的安全性,私钥本身也能用密码保护,在使用到私钥进行签名或者解密的时候,会对私钥进行密码验证。

正确的流程是:

  • 本地生成密钥对
  • 将本地生成的公钥发送到远程主机
  • 发起请求,要求使用密钥进行安全验证
  • 服务端收到请求之后,返回一个摘要信息
  • 客户端利用私钥对摘要信息签名,并发送给远程主机
  • 服务端利用公钥对签名进行校验并解密,返回结果

总结

通过对口令认证和密钥认证的介绍,并结合认证时序图,我们可以发现这两种认证类型的差异:

  1. 都可以保障数据安全,但是口令无法避免「中间人」攻击,密钥可以避免中间人攻击。
  2. 拥有对方公钥的情况下,可以使用加密认证的方式给对方发消息;
  3. 对方拥有我们公钥的情况下,我们可以使用签名认证的方式给对方发消息;

Git 代码托管平台 与 SSH 安全认证的关系

不知道大家有没有发现 Git 代码托管平台提供的 SSH 地址都是 git@HOST_NAME 开头的,比如:git@github.com:chudongvip/awesome_video_player.git。这是为什么呢?首先我们知道 SSH 远程登录是通过 ssh user@host 命令。user 是服务主机的用户名,host 是指服务主机的 IP 或者域名,也称之为 host name。那么 git 是不是这些代码托管平台的服务主机的用户呢?我们通过 GitHub 的两个例子来测试一下(此处只为验证 git 是否为代码托管平台服务主机的子账号)。

示例1

首先我们使用未配置 GitHub SSH Keys 的账号进行测试:

$ ssh git@github.com
The authenticity of host 'github.com (13.229.188.59)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,13.229.188.59' (RSA) to the list of known hosts.
git@github.com: Permission denied (publickey).

出现了授权失败的问题。

示例2

我们使用一个已经配置了 GitHub SSH Keys 的账号(本地生成密钥对,然后前往 GitHub 配置,此处省去步骤)再次进行测试:

$ ssh git@github.com
The authenticity of host 'github.com (13.229.188.59)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,13.229.188.59' (RSA) to the list of known hosts.
PTY allocation request failed on channel 0
Hi chudongvip! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

这次没有提示失败,而是提示认证成功,但是 GitHub 不提供 shell 访问。那么通过这两个示例,我们可以得出最终结论了,另外还有意外收获:

  • Git 代码托管平台都提供了一个公开的子账号 git
  • Git 代码托管平台只能通过 SSH 进行安全认证,不能通过 shell 访问

Git 代码托管平台 SSH 是如何进行安全认证的?

在弄清楚如何进行安全认证之前,我们需要弄清楚 Git 的那些操作需要进行安全认证?

众所周知,Git 代码托管平台分两种仓库:公共仓库私有仓库,仓库类型的不同意味着对仓库所有者操作步骤的身份认证也不通,因此安全认证需要区分仓库的类型,至于是什么公共仓库和私有仓库,我想这里就没必要介绍了。下面我们可以通过两个简单的例子:

  • 将远程仓库克隆到本地(私有仓库)

    $ git clone git@github.com:chudongvip/xxxx.git
    

    这里会提示我们没有权限,或者开始校验我们的账号密码。

  • 本地仓库关联远程仓库后,将代码推送到分支(公共仓库或私有仓库)

    $ git init
    $ git add .
    $ git commit -m "first commit"
    $ git add remote origin git@github.com:chudongvip/xxxx.git
    $ git push
    

    公共仓库则不会在我们 clone 仓库(或本地仓库关联远程仓库)时对我们进行安全认证,而是在我们 push 时对我们进行身份的认证。

通过对仓库的理解,以及大量的测试我们大致可以得出以下结论:

仓库类型校验权限的 Git 操作
公共仓库 publicgit push
私有仓库 privategit clonegit pullgit push

上文提到 Git 代码托管平台有公共仓库和私有仓库之分,私有仓库权限校验比较多,所以接下来的例子我们都使用是用私有仓库举例。假设我们现在有一个 GitHub 账号chudongvip, 有一个私有仓库 awesome_video_player,仓库的 SSH 地址为 git@github.com:chudongvip/awesome_video_player.git。信息详情见下表:

平台账号仓库仓库 ssh 地址
GitHubchudongvipawesome_video_playergit@github.com:chudongvip/awesome_video_player.git

首先我们需要创建本地 SSH 密钥对。

然后前往 GitHub 给账号 chudongvip 添加 SSH Keys。

接下来我可以对私有仓库进行 Clone 操作了,下面我们来看看 git clone 时是 SSH 怎么进行安全认证的:

因此,Git 代码托管平台 SSH 是基于密钥的安全认证的。

你可能不知道的事

网上关于 Git SSH 配置的文章数不胜数,这类文章都是通篇一律都是讲述如何去配置(授之以鱼)并没有告诉你为什么要这么配置,恰恰 Git SSH 又是一个低频的操作,大家也是奔着如何配置去的,自然也就很容易忽略一些限制。

SSH Keys 配置的限制

使用密钥生成工具 ssh-keygen 生成的密钥,可以将公钥同时配置到 GitHub 、Gitee、GitLab 的某个账户下面,但是不能同时配置给同一个平台的多个账户。下面为可以通过一个示例来验证一下真实性:

示例的步骤

  • 本地使用 ssh-keygen 生成一对密钥对 githubA_rsa.pubgithubA_rsa
  • 你有两个 GitHub 账号(accountA@xx.comaccountB@xx.com
  • A 账号在 SSH Keys 中添加 githubA_rsa.pub (公钥)
  • 再将同一个公钥添加到 B 账号的 SSH Keys 中
  • 这个时候 GitHub 会提示你 Key 已经被使用。

因此我们只能通过配置多个密钥以及配置 ~/.ssh/config 指定 host 来解决,详情见下表:

GitHub 账号公有仓库本地密钥Git SSH Keys
accountA@xx.com 简称 AAprogithubA_rsa.pub、githubA_rsagithubA_rsa.pub
accountB@xx.com 简称 BBprogithubB_rsa.pub、githubB_rsagithubB_rsa.pub

什么时候才要配置 ~/.ssh/config

SSH 客户端配置 ~/.ssh/config 不一定需要配置,只有在一个平台(比如:GitHub)下你有多个账号的情况下,通过配置 ~/.ssh/config 来自定义 host,然后通过更改 Git 仓库 SSH 地址中的 Host,来告诉平台当前使用的是哪个账号,因为每次 SSH 客户端通过质询操作,就会在 ~/.ssh/known_hosts 中添加一条记录(域名、IP、公钥信息等信息)。

基于什么时候需要配置?为什么需要配置?我们先看看一个例子:

首先,还是上面两个 GitHub 账户 ,分别都配置了 SSH Keys。

然后,我们使用账号 A 拉取 A 下面的某一个仓库,我们更改项目中的 README.md,然后第一次提交。

$ vim README.md
$ git commit -a -m "update README.md"
$ git push
The authenticity of host 'github.com (52.74.223.119)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,52.74.223.119' (RSA) to the list of known hosts.

由于是第一次提交,Git 验证我们的身份并询问是否询问我们是否信任该 github.com (52.74.223.119) 这台主机,选择 yes 信任,这个时候会在 known_hosts 中添加一条记录。我们可以查看一下 known_hosts 中的信息:

$ cat known_hosts 
github.com,52.74.223.119 ssh-rsa AAAAB3NzaC1yc2EA********************************************************************************************************************************************************这里不能给你看***************==

一切正常不过,不信邪是吧?我们接着往下测试。

我们使用账号 B 拉取 GitHub 账号 B 下面的仓库,更改项目中的 README.md,然后提交。

$ vim README.md
$ git commit -a -m "update README.md"
[master b5319ad] update README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
$ git push
ERROR: Permission to DyncMark/autoUpdateDEMO.git denied to chudongvip.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

通过上面的例子,我们会发现这次执行 git push 时, known_hosts 中不会新增记录。因为记录已经存在( 域名、IP、以及 GitHub 的公钥都是一样的),所以在你提交 B 的这个项目的时,Git 把你当成了用户 A。所有这个时候我们需要配置 ~/.ssh/config 来自定义 host,然后通过更改 Git 仓库 SSH 地址中的 Host,来告诉平台当前使用的是哪个账号。至于如何配置,可以关注我下一篇文章**《如何维护 Git 平台的多个账号?》**

相关文档

[掘金年度征文 | 2020 与我的技术之路 征文活动正在进行中......](