一、SSH 引入
在使用 https 协议访问我们的 Gitee 库的时候,如果我们想要克隆一个私有库,或者说想要使用 git push,我们都需要输入账户账号和密码,这样十分繁琐而且很不安全。
虽然我们可以使用 Git for Windows 的 Git Credential Manager:
来记录我们的账号和密码,来避免我们每一次进行某些操作需要输入账号密码。但是这样还是需要将账号和密码在公共网络中传输,所以还是不算太安全。
然后我们还有一种比较安全的方法,对于 Github 和 Gitee 都有个人访问令牌(Personal Access Token,PAT),使用个人访问令牌可以避免在公共网络中直接传输用户名和密码,从而提高安全性。
令牌通常具有特定的权限,可以限制其访问范围,这样即使令牌被泄露,也不会影响整个账户。用户在首次使用时输入一次令牌后,凭据管理器可以保存该令牌,之后的操作不需要再次输入。个人访问令牌在 GitHub 和 Gitee 等平台中均可使用,是处理 API 调用和 Git 操作的安全方式。
另一个比较安全的方法就是使用 SSH 来实现对于用户的身份验证。
二、SSH 介绍
1、SSH 是什么
SSH(Secure Shell)是一种网络协议,用于通过不安全的网络安全地访问计算机。它提供了一个加密的通道,可以在客户端与服务器之间安全地传输数据。SSH 在远程登录、文件传输等场景中广泛应用。
SSH 的实现有多种,比较常见的是 OpenSSH。
在大多数 Linux 和 macOS 系统上,SSH 客户端默认安装。我们需要通过 SSH 客户端来生成秘钥。
在 Windows 上,可以使用 Git Bash、WSL 或 PuTTY 等工具。
2、SSH 的原理
1)对称加密和非对称加密
i.对称加密
对称加密也就是只有一个秘钥,使用这个秘钥加密,也使用这个秘钥解密,下面是对称加密的登录方式(简化版):
这样就有一个问题,加密和解密都是使用同一个秘钥,如果这个秘钥一旦被攻击者获取了,攻击者就可以直接截获用户的登录密码的加密后的序列,然后使用这个秘钥解密这个序列就可以的到用户的账号密码了,这样就可以攻击用户的账户了,这显然不够安全。
ii.非对称加密
非对称就是有两个秘钥,一个公钥一个私钥,使用公钥加密,然后使用私钥解密。而且这里的公钥加密后的密文只能通过对应的私钥解密,而且通过公钥几乎不能倒推出私钥。
这样只要将私钥保存好,就可以较好地防止信息泄露。
2)非对称加密的登录方式(基于密码登录)(简化版)
对于这种方式,还是有一些风险。
例如,如果攻击者从最开始就已经监听了用户的信息,然后攻击者将自己的公钥给用户,然后用户并不知情,所以就使用这个错误的公钥加密的账号和密码,然后用户将密文再次发出,然后攻击者就可以将这个密文截获,然后使用自己的秘钥解密,然后就可以得到用户的账号密码,这时就可以攻击用户的账户。
解决方式就是让用户通过要连接的服务器的公钥的指纹来判断是否是要登录的服务器。也就是对比下面的指纹和服务器的实际指纹。(这是 SSH 的一个安全机制,就是用来防止上面的中间人攻击)
其实在我们使用 SSH 第一次连接我们的服务器时,就会出现以下提示:
The authenticity of host 'host (这里是ip)' can't be established.
ED25519 key fingerprint is SHA256:HfjeD/falhdDASEadgDE/ehoDAhgdsERdGAREdfseag.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no)?
这里给出了要连接的服务器的公钥的指纹,然后提示这个公钥是未知的,提示我们是否要继续连接,这里如果我们输入 yes 确认继续连接的话,我们的 .ssh 目录下就会出现一个文件:
这个文件中就记录着这个服务器的 ip 和服务器发送给我们的公钥(格式如下图):
同时命令行中出现提示:
Warning: Permanently added '这里是ip' (ED25519) to the list of known hosts.
也就是说这个服务器已经添加至已知的主机。
然后就要输入密码,进行登录。
3)基于公钥验证的免密登录(简化版)
对于上面的基于密码的登录方式,每次登录时都要输入密码,这样会比较麻烦,实际上还有一种基于公钥验证的免密登录方式:
这种方式就用于 github 和 gitee 等代码托管平台,我们可以在我们在这些平台上的账号上放置我们的公钥,然后就可以使用免密登录了(后面有示例)。
三、生成秘钥对
1、ssh-keygen 指令介绍
可以在有 git bash 中使用以下指令获取此指令的用法:
ssh-keygen --help
然后就会出现关于此指令的用法:
usage: ssh-keygen [-q] [-a rounds] [-b bits] [-C comment] [-f output_keyfile][-m format] [-N new_passphrase] [-O option][-t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa][-w provider] [-Z cipher]ssh-keygen -p [-a rounds] [-f keyfile] [-m format] [-N new_passphrase][-P old_passphrase] [-Z cipher]ssh-keygen -i [-f input_keyfile] [-m key_format]ssh-keygen -e [-f input_keyfile] [-m key_format]ssh-keygen -y [-f input_keyfile]ssh-keygen -c [-a rounds] [-C comment] [-f keyfile] [-P passphrase]ssh-keygen -l [-v] [-E fingerprint_hash] [-f input_keyfile]ssh-keygen -B [-f input_keyfile]ssh-keygen -D pkcs11ssh-keygen -F hostname [-lv] [-f known_hosts_file]ssh-keygen -H [-f known_hosts_file]ssh-keygen -K [-a rounds] [-w provider]ssh-keygen -R hostname [-f known_hosts_file]ssh-keygen -r hostname [-g] [-f input_keyfile]ssh-keygen -M generate [-O option] output_filessh-keygen -M screen [-f input_file] [-O option] output_filessh-keygen -I certificate_identity -s ca_key [-hU] [-D pkcs11_provider][-n principals] [-O option] [-V validity_interval][-z serial_number] file ...ssh-keygen -L [-f input_keyfile]ssh-keygen -A [-a rounds] [-f prefix_path]ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]file ...ssh-keygen -Q [-l] -f krl_file [file ...]ssh-keygen -Y find-principals -s signature_file -f allowed_signers_filessh-keygen -Y match-principals -I signer_identity -f allowed_signers_filessh-keygen -Y check-novalidate -n namespace -s signature_filessh-keygen -Y sign -f key_file -n namespace file [-O option] ...ssh-keygen -Y verify -f allowed_signers_file -I signer_identity-n namespace -s signature_file [-r krl_file] [-O option]
这里简要介绍几个:
-t type
- 描述: 指定要创建的密钥类型。
- 示例:
-t rsa
,-t dsa
,-t ecdsa
,-t ed25519
- 默认值: rsa(在ssh9.5之后默认值就是ed25519了)
-b bits
- 描述: 指定密钥的位数。对于RSA和DSA密钥,默认是2048位。对于ECDSA和ED25519密钥,长度是固定的。
- 示例:
-b 4096
(RSA密钥长度为4096位)
-C comment
- 描述: 提供一个新的密钥注释。
- 示例:
-C "user@example.com"
-f filename
- 描述: 指定文件名,存储生成的密钥。
- 示例:
-f ~/.ssh/id_rsa
-N new_passphrase
- 描述: 提供新密钥的密码短语。如果不想使用密码短语,可以留空。(密码短语用于使用私钥时输入,以防主机被入侵,私钥泄露,这样即使攻击者获得了私钥文件,没有密码短语,也使用不了秘钥)
- 示例:
-N "my_password"
2、生成秘钥的算法
1. RSA
- 优点:
- 广泛支持:几乎所有的SSH客户端和服务器都支持。
- 密钥长度可调:通常为2048或4096位,提供灵活的安全性。
- 缺点:
- 较大的密钥长度:2048或4096位的密钥在传输和存储时占用更多空间。
- 速度较慢:生成和验证过程中计算开销较大。
2. DSA
- 优点:
- 标准化:是早期SSH协议的一部分。
- 缺点:
- 固定密钥长度:通常只有1024位,不再被认为是安全的。
- 不推荐使用:现代安全标准中,DSA因其安全性不足已经被淘汰。
3. ECDSA
- 优点:
- 更高的安全性:在较短的密钥长度下提供与RSA相当的安全性。
- 快速:生成速度和验证速度较快。
- 缺点:
- 相对较新的算法:可能在一些老旧系统上不受支持。
4. ECDSA-SK
- 优点:
- 硬件安全支持:支持使用安全密钥(如硬件安全模块)。
- 增强的安全性:有效防止私钥被盗。
- 缺点:
- 需要额外的硬件支持:要求使用安全密钥设备。
5. ED25519
- 优点:
- 高性能:快速生成和验证。
- 强安全性:相比其他算法,在相对较短的密钥长度下提供高安全性。
- 缺点:
- 新算法:可能在某些非常老旧的系统或软件中不受支持。
6. ED25519-SK
- 优点:
- 硬件安全功能:支持安全密钥,提高私钥的物理安全性。
- 轻量且安全:结合ED25519的优势,提供可靠的安全性。
- 缺点:
- 需要硬件支持:要求使用支持的安全设备。
3、使用 ssh-keygen 指令生成公钥和私钥
1)秘钥对的生成
首先要到 ~/.ssh 目录下进行操作,因为生成的公钥和私钥一般放在这个目录下,Windows 就是在用户目录的 .ssh 目录下,如果没有 .ssh 可以自行创建。
然后执行 ssh-keygen 指令,使用相关的命令行参数,这里我使用以下指令:
ssh-keygen -t ed25519
然后就会出现以下提示:
总共有三个可以输入的地方,第一次,询问存储秘钥的路径以及存储秘钥的文件的名字,如果不输入的话,就是默认为括号中的,第二次,询问输入密码短语,如果不输入就是没有密码短语,第三次,确认密码短语。
我们在完成这三个输入之后,就会出现提示:
可以看到秘钥对已经生成完毕,id_ed25519 文件中存储的是私钥,一定要自己保存,id_ed25519.pub 中存储的是公钥,在需要用的地方使用。
我们在对应的目录下可以找到这两个文件:
2)公钥指纹和 randomart Image:
在命令行中,我们看到还有两个东西:
i.公钥指纹(Public Key Fingerprint)
第一个是公钥指纹,公钥指纹是对公钥进行哈希运算后得到的一个简短字符串,用来唯一标识该公钥,用来验证公钥的完整性和正确性。通过比较较短的指纹,可以验证和确认公钥的完整性和正确性,而无需检查整个公钥字符串。
公钥指纹一般有两种算法 MD5 和 SHA256。
MD5 由于存在已知的碰撞攻击(collisions)(就是两个不同的输入,使用算法计算出 Hash 序列相同,这样就没法通过这个 Hash 序列判断背后的输入是否是同一个了),MD5 已被认为不再安全,不推荐在新的系统中使用。
对于 SHA256 目前被认为是较为安全的选择,广泛用于现代的加密和验证系统。
ii.randomart Image
第二个就是这个 randomart Image 了,那这是个什么东西呢。
Randomart Image 是一种将公钥或其指纹转换为ASCII艺术的直观方式,用于帮助用户更容易地识别和验证公钥。它通过一种特定的算法(通常是“visual hash”算法)生成,能够将复杂的公钥信息转换为一幅易记的图像。这幅图像可以帮助用户在视觉上快速确认公钥是否一致,尤其是在手动比较指纹时非常有用。
那如何从公钥指纹生成这个 randomart image 呢,实际上基本的步骤是这样的:
-
获取公钥的指纹:首先获取到公钥的指纹。一般是 MD5(16字节的十六进制序列)或者 SHA256(SHA256 生成的散列长度为 256 位,也就是 32 字节,然后转换为 Base64 编码,会产生一个 43 字符的 Base64 编码序列,因为最后的填充字符 = 被去除了)。
-
准备指纹数据:将指纹转换为二进制数据。在OpenSSH中,这通常是通过将指纹的十六进制值转换为二进制来完成的。
-
定义画布:创建一个17x9的字符画布,这将是randomart图像的基础。画布的中心点(坐标(8, 4))是起始点,起始点用 S 字符标记,终止点使用 E 字符标记(这里可以看到图中都有 S 和 E,而且 S 在中间的位置)。
-
定义移动规则:根据指纹的二进制数据,定义移动规则。每个二进制数字(bit)将决定在画布上的移动方向。通常规则如下:
- "00" 向左上移动
- "01" 向右上移动
- "10" 向右下移动
- "11" 向左下移动
-
生成图像:从中心点开始,根据移动规则在画布上移动,并记录每一步。每次移动后,更新画布上相应位置的访问计数。
-
转换计数为字符:根据每个位置的访问次数,使用一组预定义的字符来表示。例如:
- 0次:空格(代表未访问)
- 1次:点(.)
- 2次:圈(o)
- 3次:加号(+)
- ...以此类推,直到最大次数。
-
打印图像:最后,将画布上的数据转换为ASCII艺术图像,并打印出来。
四、配置 Github 的 SSH
1、创建密钥对
使用上面的指令创建密钥对。
2、将公钥放置到 Github 的账户设置上
进入 Github 的设置页面,然后找到下图的选项:
然后点击 New SSH key 按钮:
然后会需要完成 2FA 验证,完成验证后公钥就成功放到 Github 上了。
3、测试连接
使用以下指令来测试能否通过 ssh 与 Github 成功建立连接:
ssh -i ~/.ssh/github/id_ed25519 -T git@github.com
这里使用 -i 参数指定私钥的位置,然后 -T 告诉 SSH 客户端不要分配一个伪终端(pseudo-terminal),这是因为在通过 GitHub 进行操作时,并不需要一个交互式的 shell,只需进行身份验证即可。使用 -T
可以提高连接的效率。
然后就会出现以下提示:
The authenticity of host 'github.com (20.205.243.166)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
这里就是我们上面提到的 SSH 的安全机制,每次连接一个新的服务器时,SSH 会提示用户确认服务器的身份,确定服务器是否是真正的要连接的服务器,防止中间人攻击。
然后我们选择 yes,就会出现以下提示:
Warning: Permanently added 'github.com' (ED25519) to the list of known hosts.
显示 github.com 已经永久添加到了已知的主机列表中。可以在 .ssh 目录下看到:
后面就会出现连接成功的提示:
Hi 用户名! You've successfully authenticated, but GitHub does not provide shell access.
这里我们就完成了连接,然后我们就可以进行对某个仓库的操作了。
4、配置文件
在对仓库进行某些操作时,我们可以先在 .ssh 目录下创建一个 config 文件,然后加上一些配置,就可以在每次连接前不必指定私钥的位置,这样会很方便。
首先在 git bash 中转到 .ssh 目录,然后在这里使用 vim 创建 config 然后编辑:
vim config
然后在 config 文件中输入:
IdentityFile 秘钥所在路径
例如:
IdentityFile ~/.ssh/github/id_ed25519
将文件保存后,
然后再进行连接,这里我们就不需要使用 -i 参数指定路径了:
ssh -T git@github.com
这样会方便许多。
5、对仓库的操作
在配置好配置文件后,我们就能进行对仓库的操作了,例如这里我们克隆一个仓库:
首先将通过 SSH 协议访问 GitHub 上的 Git 仓库的 URL 复制下来,然后就可以在 git bash 中输入以下命令了:
git clone git@github.com:nothings/stb.git
然后就会出现以下提示:
Cloning into 'stb'...
remote: Enumerating objects: 8089, done.
remote: Counting objects: 100% (213/213), done.
remote: Compressing objects: 100% (116/116), done.
remote: Total 8089 (delta 132), reused 131 (delta 96), pack-reused 7876 (from 1)
Receiving objects: 100% (8089/8089), 5.65 MiB | 2.67 MiB/s, done.
Resolving deltas: 100% (5359/5359), done.
然后我们就可以看到仓库克隆成功了: