ssh-keygen+ssh-keyscan+ssh-copy-id三条命令实现ssh免密 non interactive登录
如下代码实现从A机器(192.168.1.100)到B机器(192.168.1.111)的ssh免密non interactive登录。
## 在A机上操作如下三句
ssh-keygen -t rsa -b 4096 -N '' -m PEM <<<$'\ny\n'
ssh-keyscan -H 192.168.1.111 >> ~/.ssh/known_hosts
ssh-copy-id -i ~/.ssh/id_rsa.pub jerry@192.168.1.111
解释
ssh-keygen -t rsa -b 4096 -N '' -m PEM <<<$'\ny\n'
这句是生成密钥对,不产生交互,如果以前存在则删除重建。
参考https://stackoverflow.com/questions/43235179/how-to-execute-ssh-keygen-without-prompt
ssh-keyscan -H 192.168.1.111 >> ~/.ssh/known_hosts
这句是把ssh fingerprint加入到A机器~/.ssh/known_hosts。
参考https://www.techrepublic.com/article/how-to-easily-add-an-ssh-fingerprint-to-your-knownhosts-file-in-linux/
ssh-copy-id -i ~/.ssh/id_rsa.pub jerry@192.168.1.111
这句是附加公钥(即A机器的~/.ssh/id_rsa.pub)到B机器~/.ssh/authorized_keys,第一次提示输入密码。
20220826更新
想了以下,认为这个也不叫免密登陆,叫ssh 通过私钥实现无交互登陆
20220827更新
更简洁的使用两条命令就可以了,无需增加 ssh-keyscan 这一条,但是需要通过-o增加ssh参数。参考
## 测试用例
ssh-keygen -f "$HOME/.ssh/known_hosts" -R "192.168.1.111" > /dev/null;
ssh-copy-id -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_rsa.pub jim@192.168.1.111
第一次输入密码可以通过sshpass or expect方式实现自动完成。参考
20220929更新
脚本:批量根据密码拷贝公钥到目标机器的bash脚本
#!/bin/bash
# cat > hosts <<'EOF'
# #hostname user password port identity_file
# 192.168.1.111 root password 22
# EOF
auto_ssh_copy_id() {
#ssh-keygen -R "$1" -f "$HOME/.ssh/known_hosts" > /dev/null;
expect -c "set timeout -1;
spawn ssh-copy-id -p $3 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $1;
expect {
*(yes/no)* {send -- yes\r;exp_continue;}
*assword:* {send -- $2\r;exp_continue;}
eof {exit 0;}
}";
}
ssh_copy_id_to_all() {
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^\#.* ]]; then
continue
fi
server=$(echo "${line}" | awk -F " " '{print $1}')
user=$(echo "${line}" | awk -F " " '{print $2}')
password=$(echo "${line}" | awk -F " " '{print $3}')
port=$(echo "${line}" | awk -F " " '{print $4}')
echo "${line}" && auto_ssh_copy_id "${user}@${server}" "${password}" "${port}"
done < hosts
}
ssh_copy_id_to_all
authorized_keys and known_hosts
~/.ssh/authorized_keys vs ~/.ssh/known_hosts 两个文件一个在服务端,一个在客户端,不要混淆。
authorized_keys(在 SSH 服务端)
作用:允许哪些公钥可以免密登录到”我”这个账号
- 路径:~/.ssh/authorized_keys(在被登录的机器上)
- 内容:一行一条 SSH 公钥(ssh-rsa AAAA… user@host)
- 工作原理:你把客户端的公钥放进服务端的 authorized_keys 里,客户端用对应私钥登录时,SSH 服务端就能验证”这把私钥的持有者就是被允许的用户”,于是免去密码
典型场景:
- 你从 A 机器免密 ssh user@b → 把 A 的公钥加到 b 用户的 ~/.ssh/authorized_keys
-
GitHub 的 “Settings → SSH and GPG keys” 就是把你的公钥加到 GitHub 那个用户的 authorized_keys
权限要求(很多人踩坑):
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys # 必须严格600,否则 sshd 拒绝读取
known_hosts(在 SSH 客户端)
作用:记住”我连过的每台 SSH 服务器的指纹”,下次连的时候验证”这台机器还是当初那台吗”
- 路径:~/.ssh/known_hosts(在发起连接的机器上)
- 内容:每个 SSH 服务器的主机公钥(host key),格式类似:
github.com ssh-ed25519 AAAA…5d
192.168.1.10 ecdsa-sha2-nistp256 AAAA…xQ - 工作原理:
- 第一次连 ssh user@192.168.1.10,客户端会问”是否信任这台机器的 host key?”(Are you sure you want to continue connecting?),你输入 yes
- 这台机器的 host key 被记进 known_hosts
- 下次再连,客户端拿 known_hosts 里的指纹和服务器返回的指纹比对:
– ✅ 一致 → 放心连(说明还是当初那台机器)
– ❌ 不一致 → 警告 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!(可能中间人攻击,或机器重装了系统)
典型场景:
– 服务器重装系统 → known_hosts 里的旧指纹失效,需要手动删掉那一行再重连
– 自动化脚本里会用 ssh-keyscan host >> known_hosts 提前”信任”目标机,避免交互## 对比
┌──────────────┬─────────────────────┬──────────────────────────────┐
│ 维度 │ authorized_keys │ known_hosts │
├──────────────┼─────────────────────┼──────────────────────────────┤
│ 在哪 │ 被登录方(server) │ 发起连接方(client) │
├──────────────┼─────────────────────┼──────────────────────────────┤
│ 角色 │ 服务端决定”谁能来” │ 客户端决定”这是不是真服务端” │
├──────────────┼─────────────────────┼──────────────────────────────┤
│ 内容 │ 客户端的公钥列表 │ 服务端的 host key 指纹 │
├──────────────┼─────────────────────┼──────────────────────────────┤
│ 写时机 │ 管理员手工添加 │ 首次连上新主机时自动添加 │
├──────────────┼─────────────────────┼──────────────────────────────┤
│ 不一致时报错 │ 公钥不符 → 拒绝登录 │ host key 不符 → 警告中间人 │
└──────────────┴─────────────────────┴──────────────────────────────┘
一句话口诀
▎ authorized_keys 是门卫的名单(谁能进),known_hosts 是熟客名单(这人我认识)。
两者配合起来就是 SSH 公钥认证 + 主机验证的完整安全基础。
