SSH
本文有一定ai辅助
Secure Shell
在不安全的网络中为计算机之间提供安全的远程登录和其它网络安全服务
如何保证安全
结合了:
- 非对称加密(如RSA、Ed25519)
建立连接初期密钥交换,客户端和服务端各自拥有公钥私钥 - 对称加密(如AES)
数据传输过程用对称加密进行,速度快 - 哈希算法(如SHA-256)
验证数据完整性(MAC,消息认证码),防止篡改
登录认证方式
- 普通密码登录
- 密钥登录
- 在自己电脑上生成一对钥匙
- 把公钥放在服务器上(一般在
~/.ssh/authorized_keys文件中 - 私钥留在自己电脑
- 登陆时,服务器通过公钥向你发起挑战,你电脑用私钥解密并回应,直接免密登录
- 密钥本身也可以有密码,这样即使私钥泄露,其他人也无法使用私钥
核心功能
- 远程敲代码
- 远程命令行控制
- 安全文件传输(SCP/SFTP)
- 端口转发/SSH隧道
- 非常强大的FQ和内网穿透工具,利用ssh把本地计算机端口通过加密隧道映射到远程服务器或内网其它机器
- git托管
使用
bash
ssh 用户名@服务器IP地址
ssh root@192.168.1.100
# 指定端口
ssh -p 2222 root@192.168.1.100
ssh参数
-p(port):指定连接的端口号。- 默认情况下 SSH 连接 22 端口。但为了防范恶意扫描,很多服务器会修改默认端口(例如改为 2222)。
- 使用示例:
ssh -p 2222 user@server_ip。
-i(identity file):指定使用的私钥文件。- 默认情况下,SSH 会自动去读取
~/.ssh/id_rsa或~/.ssh/id_ed25519等默认路径的密钥。但如果你有多个不同的密钥,可以通过此参数手动指定。 - 使用示例:
ssh -i ~/.ssh/my_aws_key user@server_ip。
- 默认情况下,SSH 会自动去读取
-v(verbose):详细模式(调试模式)。- 排错神器!当你遇到"SSH连不上"、"密码明明是对的却被拒绝"等问题时,加上这个参数会打印出连接过程中的所有握手和验证细节。
- 可以叠加使用来增加详细程度,通常遇到疑难杂症时直接用最高级别:
ssh -vvv user@server_ip。
-L(local forward):本地端口转发(正向代理)。- 非常实用的内网访问工具。可以将远程服务器上的某个端口,映射到你本地电脑的端口上。常用于安全访问服务器内网的数据库或 Web 面板。
- 使用示例:
ssh -L 8080:localhost:3306 user@server_ip(将服务器本地的 3306 MySQL 端口,映射到你电脑的 8080 端口,之后你在电脑上连接127.0.0.1:8080实际上连的就是服务器的数据库)。
-J(jump host):指定跳板机(堡垒机)。- 现代 SSH 强烈推荐 的参数。当目标服务器在内网,你必须先登录一台"跳板机"才能访问时,以前配置很麻烦,现在直接用
-J即可一键穿透。 - 使用示例:
ssh -J jump_user@jump_ip target_user@target_ip。
- 现代 SSH 强烈推荐 的参数。当目标服务器在内网,你必须先登录一台"跳板机"才能访问时,以前配置很麻烦,现在直接用
-A(agent forwarding):开启密钥代理转发。- 当你连上服务器后,如果还需要在服务器上继续 SSH 登录其他内网机器,或者从 GitHub 拉取私有仓库代码,带上
-A可以让服务器直接**"借用"**你本地电脑的私钥进行验证。 - 这样你就不需要把私钥复制一份放在服务器上,大大提高了私钥的安全性
- 场景:从跳板机登录内网服务器,但是跳板机放私钥极度危险
- 当你连上服务器后,如果还需要在服务器上继续 SSH 登录其他内网机器,或者从 GitHub 拉取私有仓库代码,带上
-R(remote forward):远程端口转发(反向代理/内网穿透)。- 将你本地电脑(或你所在内网)的某个端口,反向暴露给远程服务器。
- 常用于开发调试,比如把你本机正在运行的 Vue/React 测试项目(3000端口),临时暴露给公网服务器,让客户可以访问查看:
ssh -R 8080:localhost:3000 user@server_ip。
-D(dynamic forward):动态端口转发。- 将本地电脑变成一个 SOCKS5 代理服务器。你的浏览器只要设置该代理,所有的网页流量就会通过远程服务器加密转发(相当于一个轻量级的FQ或安全隧道工具)。
- 使用示例:
ssh -D 1080 user@server_ip。
-N(no execute):不执行任何远程命令。- 纯粹为了建立 SSH 连接,但不打开远程终端(不弹出命令行提示符)。
- 端口转发的黄金搭档 :通常与
-L、-R、-D配合使用,当你只需要建立一条转发隧道,而不想真的去敲命令时使用。
-f(background):在后台运行。- 让 SSH 连接在输入密码后立刻潜入后台静默运行,不会霸占你当前的终端窗口。
- 经常和
-N一起组合使用来挂载后台隧道:ssh -f -N -L 8080:localhost:3306 user@server_ip。
-o(option):临时指定覆盖配置选项。- 等同于在
~/.ssh/config配置文件里写规则,但适合临时使用。 - 最常用的场景是跳过首次连接陌生服务器时的"指纹确认(yes/no)"提示,在写自动化脚本时必用:
ssh -o StrictHostKeyChecking=no user@server_ip。
- 等同于在
ssh-keygen参数
生成、管理和转换ssh密钥的命令行工具
-t(type):指定加密算法的类型。- 以前默认是
rsa,比较老旧且生成的文件很大。 - 现在强烈推荐使用
ed25519,它是目前公认最安全、速度最快、而且生成的密钥长度最短的算法。
- 以前默认是
-C(comment):添加一行注释(标签)。- 通常填你的邮箱地址。因为当你有很多台设备时,这行注释能帮你快速认出"这个公钥是谁的、是哪台电脑生成的"。
-b(bits):指定密钥长度。- 如果你非要用传统的
rsa算法,建议加上-b 4096(默认是 2048)来增加破解难度:ssh-keygen -t rsa -b 4096。对于ed25519则不需要这个参数,它有固定的超高强度。
- 如果你非要用传统的
-N(new passphrase):指定密钥的密码(保护私钥)。- 默认情况下,生成密钥时会交互式地提示你输入密码。如果你想在自动化脚本中生成密钥,或者明确不需要密码 (登录时免密),可以加上
-N ""来直接跳过密码设置步骤。 - 例如全自动生成:
ssh-keygen -t ed25519 -f ~/.ssh/my_key -N ""。
- 默认情况下,生成密钥时会交互式地提示你输入密码。如果你想在自动化脚本中生成密钥,或者明确不需要密码 (登录时免密),可以加上
-p(password/passphrase):修改已有私钥的密码。- 如果你之前生成密钥时设置了密码,后来想修改密码,或者想取消密码(新密码直接敲回车为空),可以使用这个参数。
- 使用示例:
ssh-keygen -p -f ~/.ssh/id_ed25519,它会提示你输入旧密码并设置新密码,而不会改变密钥本身。
-y(yield public key):从已有私钥中导出对应的公钥。- 非常实用 的一个参数:如果你不小心把公钥文件(
.pub后缀)删了或者弄丢了,只要私钥还在,不用重新生成!可以用这个参数把公钥重新提取出来。 - 使用示例:
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub。
- 非常实用 的一个参数:如果你不小心把公钥文件(
-R(remove):从known_hosts文件中删除某个主机的公钥记录。- 经常遇到服务器重装系统或 IP 发生变化后,再次 SSH 登录会报错一段红字:"
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!"。 - 此时不需要手动打开文本去删,直接运行
ssh-keygen -R 服务器IP或域名,就能轻松清除旧的冲突记录。
- 经常遇到服务器重装系统或 IP 发生变化后,再次 SSH 登录会报错一段红字:"
-l(list/fingerprint):显示公钥的指纹(Fingerprint)信息。- 服务器上通常只保存公钥,当你有很多密钥并想核对"本地的某个私钥是否对应服务器上的公钥"时,直接看文件内容太长了。用这个参数可以查看密钥的哈希指纹(一串较短的字符)和那个花里胡哨的 ASCII 图形(Randomart)。
- 使用示例:
ssh-keygen -l -f ~/.ssh/id_ed25519.pub。
-q(quiet):静默模式。- 隐藏生成过程中的绝大部分提示信息(比如生成成功的提示文字和图形指纹)。
- 常用于编写自动化脚本时,保持终端输出界面的整洁。
-f(file):指定不同文件名- 常用于多密钥的分级管理,后文详细解释
-m(format):指定密钥的输出格式。- 较新版本的
ssh-keygen默认使用 OpenSSH 的新格式生成密钥。但有些老旧的系统、特定的软件或 CI/CD 工具(如旧版 Jenkins 或某些云平台的导入工具)可能无法识别新格式并报错。 - 这时可以加上
-m PEM,强制以兼容性最强的经典 PEM 格式(以-----BEGIN RSA PRIVATE KEY-----开头)来生成或转换密钥。
- 较新版本的
-E(hash algorithm):指定显示指纹的哈希算法。- 现在默认显示的指纹是
sha256格式的。如果有时候你需要配合比较老的系统(老系统可能只显示 MD5 指纹),为了对比两者是否一致,你可以指定显示为 MD5。 - 经常配合
-l参数使用:ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519.pub。
- 现在默认显示的指纹是
ssh-agent参数
SSH 密钥管理器(用于把有密码的私钥暂时保存在内存中,避免每次连接都重复输密码)
-s(Bourne shell format):强制生成适用于 Bourne 系列 Shell(如bash、zsh、sh)的环境变量命令。ssh-agent启动时并不会自动修改你当前的终端环境,而是会在屏幕上打印出两行命令(定义SSH_AUTH_SOCK和SSH_AGENT_PID)。- 在绝大多数现代 Linux 和 macOS 中这是默认格式。
- 正确用法 :通常配合
eval命令让其直接在当前终端生效,即eval "$(ssh-agent -s)"。
-c(C-shell format):强制生成适用于 C-shell 家族(如csh、tcsh)的环境变量命令。- 如果你使用的终端是 C-shell,需要用到这个参数,它会输出
setenv格式的配置。 - 正确用法 :
evalssh-agent -c``(注意不同的引号包裹方式)。
- 如果你使用的终端是 C-shell,需要用到这个参数,它会输出
-k(kill):杀掉(关闭)当前正在运行的ssh-agent进程。- 当你不再需要代理,或者准备离开电脑,为了安全起见想彻底清理内存中保存的免密私钥时使用。它会读取环境变量中的
SSH_AGENT_PID并结束该进程。 - 正确用法 :同样需要包裹起来以清理环境变量,
eval "$(ssh-agent -k)"。
- 当你不再需要代理,或者准备离开电脑,为了安全起见想彻底清理内存中保存的免密私钥时使用。它会读取环境变量中的
-t(timeout):设置密钥在代理中保存的最大有效时间(生命周期)。- 非常实用的安全参数! 默认情况下,只要
ssh-agent不被关闭,通过ssh-add存入的私钥就会一直保存在内存中。如果你希望一定时间后让密钥自动从代理中过期清除,可以使用此参数。时间单位可以是秒,也可以跟上m(分)、h(时)等。 - 使用示例:启动代理并规定存入的密钥默认 4 小时后失效:
eval "$(ssh-agent -t 4h)"。(注:在后续使用ssh-add添加密钥时,也可以单独为某个密钥指定有效时间,会覆盖此默认值)。
- 非常实用的安全参数! 默认情况下,只要
-a(bind_address):指定通信用的 Unix 套接字(Socket)文件的绑定路径。- 默认情况下,
ssh-agent会在/tmp/目录下生成一个随机名字的 Socket 文件(例如/tmp/ssh-xxxx/agent.1234)。 - 但在某些高级场景中(例如:跨多终端窗口共享同一个 agent、将 Socket 挂载映射进 Docker 容器内、或者配合
tmux防止会话断开后找不到 agent),你可能希望固定这个套接字文件的路径。 - 使用示例:
ssh-agent -a ~/.ssh/fixed_agent.sock。
- 默认情况下,
-d(debug):开启调试(Debug)模式。- 在此模式下,
ssh-agent不会自动潜入后台,而是霸占当前终端并保持在前台运行。 - 当它处理任何密钥请求时,都会把详细的操作日志输出到屏幕上。主要用于排查"明明添加了代理,但
ssh却无法借用私钥"这类底层错误。
- 在此模式下,
ssh-add参数
真正把私钥装进内存、或者查看内存里有哪些私钥的操作
无参数(默认行为):自动添加默认密钥。- 如果你直接敲
ssh-add而不加任何参数和路径,它会自动去寻找并添加~/.ssh/id_rsa、~/.ssh/id_ed25519等系统默认名称的私钥。 - 使用示例:直接执行
ssh-add。如果是自定义名称的密钥,则需要在后面跟上路径,如ssh-add ~/.ssh/my_key。
- 如果你直接敲
-l(list/fingerprint):列出当前代理中已加载的所有私钥的指纹(Fingerprint)。- 最常用的查询命令。用来确认你的私钥是否已经成功放进了内存。
- 如果返回
The agent has no identities.,说明代理是空的,什么密钥都没加载。 - 使用示例:
ssh-add -l。
-L(List public keys):列出当前代理中已加载的所有公钥的完整文本内容。- 比
-l更进一步,它会直接在屏幕上打印出ssh-ed25519 AAAAC3N...这样的长串公钥文本。 - 超级实用技巧 :当你需要把公钥添加到 GitHub 或新服务器的
authorized_keys时,不用费劲去cat ~/.ssh/id_ed25519.pub,直接运行ssh-add -L复制屏幕内容即可。
- 比
-d(delete):从代理中删除指定的私钥。- 如果你临时加载了一个高权限的私钥,用完后想立刻把它从内存中"拿出来",而不是粗暴地关闭整个 agent 进程,就可以用这个参数。
- 使用示例:
ssh-add -d ~/.ssh/my_high_secure_key。
-D(Delete all):一键清空代理中所有的私钥。- 清理内存中所有身份凭证。当你在公用电脑、或者临时借用同事的电脑操作完毕后,执行此命令能确保绝对的安全,防止别人后续"借用"你的权限。
- 使用示例:
ssh-add -D。
-t(time/lifetime):为当前添加的私钥设置"保质期"(生命周期)。- 虽然
ssh-agent -t可以设置全局默认时间,但用ssh-add -t可以为某一把特定的密钥精准设置过期时间。到达指定时间后,该密钥会自动从内存中销毁。 - 使用示例:
ssh-add -t 1h ~/.ssh/id_ed25519(设定这把密钥放入内存后,1 小时自动失效需要重新输入密码)。
- 虽然
-c(confirm):每次使用该密钥前,强制要求用户进行物理确认。- 极高安全级别的特性! 当你在服务器开启了代理转发(
ssh -A),或者担心后台有恶意程序偷偷调用你的私钥时使用。每次发生 SSH 验证,系统都会弹窗(通常配合ssh-askpass软件)要求你手动点击确认。 - 使用示例:
ssh-add -c ~/.ssh/id_ed25519。
- 极高安全级别的特性! 当你在服务器开启了代理转发(
-x(lock):锁定整个ssh-agent。- 用一个临时设定的密码,将内存中的代理整体锁死。锁定期间,所有已经加载的私钥都会"冻结"无法被使用。适合你短暂去开会或上厕所离开工位,但又不想清空密钥(不想回来重新输冗长的私钥密码)的场景。
- 使用示例:
ssh-add -x(回车后会提示你设置一个临时锁屏密码)。
-X(unlock):解锁被锁定的ssh-agent。- 配合
-x使用,输入刚才设置的临时密码,唤醒内存里的所有密钥继续工作。 - 使用示例:
ssh-add -X。
- 配合
--apple-use-keychain(苹果钥匙串集成,旧版本为-K):将私钥密码存入 Mac 的钥匙串(Keychain)。
苹果系统独有。加上这个参数添加密钥后,私钥的保护密码会被保存在 macOS 安全系统中。
以后哪怕电脑重启了,只要后台运行了 agent,系统就会自动从钥匙串读取密码完成加载,彻底实现"一次输入,终生免密"。
使用示例:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519。(注:如果是 macOS Monterey 12.0 以前的旧系统,请使用ssh-add -K ~/.ssh/id_ed25519)。
~/.ssh/config中配置
Host * AddKeysToAgent yes UseKeychain yes
免密登录
bash
# 方法1
ssh-copy-id root@192.168.1.100
## 老cmd/powershell可能没有
# 方法2
cat $env:USERPROFILE\.ssh\id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
- 方法2不负责设置权限,ssh可能会拒绝导致配置失败,而方法1强行将
.ssh目录权限设置为700(仅自己可读写),把authorized_keys权限设置为600(仅自己可读写执行) - 方法1可防止重复添加
密钥管理
ssh-agent解决每次都要输入密钥的密码的痛点
它是一个运行在你电脑后台的独立进程。你只需要在每天早上打开电脑时,把私钥交给 ssh-agent,并输入一次密码 解锁。
接下来的一整天,只要有程序(比如 Git 或 SSH 命令)需要用到这把私钥,ssh-agent 就会在内存中直接帮它们完成签名认证。你再也不用输入任何密码,同时又保持了私钥加密的绝对安全。
环境
- Linux / macOS:系统自带,而且 macOS 还把它和系统的"钥匙串(Keychain)"做了深度绑定,体验极其丝滑。
- Windows 10 / 11:
- 如果你用的是 Git Bash 或 WSL,它和 Linux 下的体验一模一样。
- 如果你用的是 Windows 原生的 CMD / PowerShell ,Windows 也内置了
OpenSSH Authentication Agent服务。
使用
Linux/macOS/Windows Git Bash
bash
eval "$(ssh-agent -s)"
#解释:ssh-agent -s 会启动这个后台服务并输出一些环境变量。外层的 eval "$(...)" 是为了让当前终端窗口认识这些变量,知道去哪里找这个管家。你会看到类似 Agent pid 12345 的输出,代表启动成功。
ssh-add ~/.ssh/id_ed25519_work
#此时,终端会提示:Enter passphrase for /home/user/.ssh/id_ed25519_work:。
#输入你的私钥密码(输入时屏幕不显示),按下回车。 只要提示 Identity added,大功告成!今天你再用这把钥匙,全是免密!
Windows 原生PowerShell(将ssh-agent做成了Windows系统服务)
powershell
#管理员打开PowerShell
# 设置服务为自动启动(开机自启 另:Disabled => 完全禁用 or Manual => 手动启动)
Get-Service ssh-agent | Set-Service -StartupType Automatic
# 启动服务
Start-Service ssh-agent
# 添加私钥
ssh-add $env:USERPROFILE\.ssh\id_ed25519_work
文件
| 文件名 | 存在哪? | 里面装了什么? | 它的作用是什么? |
|---|---|---|---|
id_rsa / id_ed25519 |
本地电脑 | 你的私钥 | 你的密码/钥匙,打死不能给别人。 |
id_rsa.pub / id_ed25519.pub |
本地电脑 | 你的公钥 | 你的锁,要传给服务器。 |
authorized_keys |
远程服务器 | 你的公钥 | 服务器用它来验证"你是不是真主人"。 |
known_hosts |
本地电脑 | 服务器的公钥 | 你用它来验证"服务器是不是真服务器"(防劫持)。 |
多密钥分级管理
-
生成密钥
bash# 工作用密钥 ssh-keygen -t ed25519 -C "work@company.com" -f ~/.ssh/id_ed25519_work # 私人用密钥 ssh-keygen -t ed25519 -C "personal@gmail.com" -f ~/.ssh/id_ed25519_personal -
配置
~/.ssh/config(建立智能路由规则)详情看后文
-
分发公钥
使用
ssh-copy-id -i ~/.ssh/id_ed25519_personal.pub ubuntu@203.0.113.10命令进行分发
known_hosts文件
通讯录
路径~/.ssh/known_hosts
当第一次连接时,提示
The authenticity of host '192.168.1.100 (192.168.1.100)' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
这是电脑询问你是否连接这个新服务器,这是他的公钥,yes之后将ip和指纹都记录下来
当第二次连接时,检测指纹和公钥一致,则直接放行
常见场景
防黑客红色警报
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
ssh发现这个ip的指纹与记录的不一样,认为有黑客在中间拦截了网络(中间人MITM)伪装成服务器,为了保护你,ssh会强制中断连接
但
现实一般有两个原因
- 你重装了服务器的系统:系统重装后,服务器会生成全新的身份指纹,旧指纹作废了。
- IP地址被分配给了另一台机器:比如云服务器的公网 IP 漂移了。
解决方法
-
删除
known_hosts里的记录,重新认识 -
运行下面的命令自动删除
bashssh-keygen -R 192.168.1.100
config配置文件
"去什么地方,就自动拿什么参数"
避免每次都使用-i进行指定
tex
# ==========================================
# 1. 公司的代码仓库 (GitLab)
# ==========================================
Host gitlab.company.com
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_work
# ==========================================
# 2. 个人的代码仓库 (GitHub)
# ==========================================
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
# ==========================================
# 3. 生产环境核心服务器 (高权限)
# ==========================================
Host prod-server
HostName 198.51.100.88
User root
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
# ==========================================
# 4. 个人测试服务器 (别名快捷登录)
# ==========================================
Host my-test
HostName 203.0.113.10
User ubuntu
IdentityFile ~/.ssh/id_ed25519_personal
Host:这是一个别名(外号),也是触发这套规则的暗号。HostName:真正的服务器地址(IP 地址或域名)。User:登录时使用的用户名。Port:端口号(如果是默认的 22 可以不写)。IdentityFile:最关键的一项 ,告诉 SSH 连这台机器时,去哪里拿私钥!
效果
bash
ssh -i ~/.ssh/id_ed25519_personal -p 22 ubuntu@203.0.113.10
# ======>>>
ssh my-test
玩法
通过-R参数将本地端口反向暴露给服务器,别人可以访问服务器的端口访问到本地的端口
也就是,内网穿透!!
可以用来本地开服或者远程操控别人电脑