背景
Linux或者Mac上一般都是直接用的終端來連接SSH,基本上很少有類似Windows上用XShell之類的客戶端。所以在終端上直接登錄都必須輸入密碼,但是如果密碼比較復雜就更難記住了。這時候可以通過SSH-Key來實現(xiàn)秘鑰登錄。
SSH-Key登錄遠程服務器
SSH-Key是一種基于密鑰的安全認證,遠程服務器持有公鑰,本地持有私鑰,在客戶端向服務器發(fā)送請求之后,服務端在用戶主目錄下查找用戶的公鑰,然后對比用戶發(fā)送過來的公鑰,如果一致則用公鑰加密”質詢“并發(fā)送給客戶端??蛻舳耸盏健辟|詢“后用私鑰解密,在發(fā)送給服務端,認證結束。
要實現(xiàn)這種方式的登錄首先需要創(chuàng)建ssh-key:
ssh-keygen -t rsa
在交互界面中可以按默認的直接回車,最后會在
${USER_HOME}/.ssh/下保存公鑰和私鑰文件:id_rsaid_rsa.pub然后需要將公鑰保存到服務器上,執(zhí)行以下命令即可將公鑰發(fā)送到服務器上,需要輸入登錄密碼
ssh-copy-id -i ~/.ssh/id_rsa.pub ${user}@${host}以上命令會將公鑰文件保存到服務器用戶目錄下的
.ssh/authorized_keys中配置完成之后便可以直接免密碼登錄了
ssh ${user}@${host}
其他擴展配置
有時候可能會需要創(chuàng)建多個不同的秘鑰對,用于不同的服務器登陸,或者用于Github的免密操作
通過ssh-keygen創(chuàng)建新的文件,此時直接定義新的名字,比如:
ssh-keygen -t rsa -C '另一個服務器' -f ~/.ssh/my_id_rsa然后同樣的將公鑰發(fā)送到需要登錄的服務器
ssh-copy-id -i ~/.ssh/my_id_rsa ${user}@${host}-
再在登錄的時候指定私鑰文件,或者通過
~/.ssh/config自動帶上指定的文件直接指定的方式:
ssh ${user}@${host} -i ~/.ssh/my_id_rsa-
通過配置文件的方式:
# ~/.ssh/config # 指定某一服務器所使用的私鑰文件 Host serverAlias # 服務器的別名,可以隨便起一個 或者直接按ip也可以 HostName ${服務器的ip} User ${user} # 指定登錄用戶名 PreferredAuthentications publickey IdentityFile ~/.ssh/my_id_rsa # 指定私鑰文件
另一種情況
- 但是也有些情況下,無法將本地的公鑰發(fā)送到服務器上,比如登錄客戶的服務器,或者登錄一個IP或者端口可能會變化的服務器,比如我使用了免費的內(nèi)網(wǎng)穿透來連接我的樹莓派,它的端口就會經(jīng)常變化。
- 在這種情況下,登錄的時候都得去手動輸入密碼了。當用戶名或者密碼很難記住的時候,往往會特別需要一個能夠記住用戶名密碼的客戶端。Mac下免費的客戶端較少,比如Termius就是一個不錯的客戶端,但是不知為何它有時候會卡死。所以我選擇了自己實現(xiàn)記住密碼的方式,可以在登錄時只記住一個密碼,將不同服務器的密碼通過加密保存,登錄的時候通過輸入解密秘鑰來自動解密登錄。
通過加密文件保存服務器密碼實現(xiàn)自動登錄
通過該方式需要依賴的工具如下:
opensslexpect, 一般情況下 openssl都是自帶了的,往往只需要安裝一下expectMac下可以直接
brew install expectUbuntu 下可以通過apt安裝
sudo apt install expectexpect 是一種交互式的開源工具,用于實現(xiàn)自動化的功能
第一步創(chuàng)建加密方法,保存密碼的密文
創(chuàng)建一個func.sh文件,內(nèi)容如下
#!/bin/bash ## 加密方法 encrypt() { local content=$1 local pass=$2 cmd="echo $content | openssl enc -aes-256-cfb -a -e -pass pass:$pass -iter 12 -nosalt" echo $content | openssl enc -aes-256-cfb -a -e -pass pass:$pass -iter 12 -nosalt } ## 加密工具方法 create_encrypted_pass() { read -s -p "Enter origin password:" content echo '' read -s -p "Enter aes password:" pass echo '' encrypt $content $pass }然后在終端中
source func.sh加載方法,然后調用create_encrypted_pass在交互界面中輸入密碼和加密秘鑰,加密秘鑰需要牢記于心,以后登錄時只需要輸入它即可完成后會打印加密后的密文,將密文保存下來,比如保存到
${host}.pass
第二步創(chuàng)建解密方法,和自動登錄的方法
在func.sh中補充解密和登錄方法
## 解密方法 decrypt() { local encrypted=$1 local pass=$2 echo $encrypted | openssl enc -aes-256-cfb -a -d -pass pass:$pass -iter 12 -nosalt } ## 登錄方法,輸入?yún)?shù)有 加密文件路徑,用戶名,服務器host,(端口,解密秘鑰【這兩個可選】) ssh_target() { local pass_path=$1 local user=$2 local host=$3 local port=$4 local aes_pass=$5 if [ "$port" == "" ]; then port=22 fi encrypted=`cat $pass_path` pass=`decrypt $encrypted $aes_pass` # echo "decrypted pass is ${pass}" ./_ssh.exp $host $user $pass $port }然后創(chuàng)建自動執(zhí)行腳本,_ssh.exp,用于根據(jù)輸入?yún)?shù)自動登錄到服務器上
#!/usr/bin/expect ## 讀取參數(shù) set host [lindex $argv 0] set user [lindex $argv 1] set password [lindex $argv 2] set port [lindex $argv 3] set timeout 3000 spawn ssh -l $user $host -p $port expect { # 判斷是否有記住hosts的交互信息 "(yes/no?" { send "yes\r" # 發(fā)送yes expect { "password:" { send "${password}\r" } # 發(fā)送密碼 } } "password:" { send "${password}\r" } # 發(fā)送密碼 } interact然后只需要在創(chuàng)建一個針對某一服務器的登錄腳本,在里面配置一些信息
比如 ssh_my_server.sh
source ./func.sh ## 用于加載預定義的方法 ssh_target ${host}.pass ${用戶名} ${服務器host} ${端口}然后對以上兩個文件賦予可執(zhí)行權限
chmod a+x ssh_my_server.sh _ssh.exp
第三步,登錄服務器
- 此時要登錄到服務器時,只需要執(zhí)行 ssh_my_server.sh 即可
-
./ssh_my_server.sh然后根據(jù)提示輸入加密秘鑰,這個秘鑰牢記于心即可。一般不知道秘鑰無法解密出具體的登錄密碼,所以是比較安全的,在腳本中也不會暴露密碼信息。
額外實現(xiàn)
以上方式,每次執(zhí)行
./ssh_my_server.sh都需要輸入一遍密碼,有時候又覺得有些麻煩。可以稍微再改造一下,在當前終端中不再需要輸入密碼。實現(xiàn)方式是得到和終端相關的數(shù)據(jù),用它作為加密密鑰,將記在心里的那個秘鑰保存下來。在func.sh中增加有些方法,并修改ssh_target
## 根據(jù)終端的信息創(chuàng)建臨時秘鑰,該方法創(chuàng)建的秘鑰只要在當前終端執(zhí)行,得到的都是同樣的內(nèi)容 create_temp_pass() { local tty_info=`tty` tty_info=${tty_info#/dev/*} local ps_info=`ps -ef | grep $tty_info | awk 'NR==1{print $2,$3,$5,$6}'` local aes_pass=`echo $ps_info | md5` echo $aes_pass } ## 從加密文件中解密出明文密碼 get_session_aes_pass() { local work_dir=`pwd` local temp_pass_dir="$work_dir/.pass" local aes_pass='' # 判斷是否存在加密文件,不存在則返回空內(nèi)容 if test -e $temp_pass_dir ; then local encrypted_aes_pass=`cat $temp_pass_dir` local temp_aes_pass=`create_temp_pass` # 判斷密文解密的合法性,我在明文中加入了_123后綴,只有后綴匹配才能確定解密是成功的,否則解密失敗返回空內(nèi)容 local decrypted_aes_pass=`decrypt $encrypted_aes_pass $temp_aes_pass` if [ "${decrypted_aes_pass#*_}" == "123" ]; then aes_pass=${decrypted_aes_pass%_123} fi fi echo $aes_pass } ## 將明文密碼保存到加密文件中 save_session_aes_pass() { local work_dir=`pwd` local temp_pass_dir="$work_dir/.pass" local aes_pass=$1 local temp_aes_pass=`create_temp_pass` # 在明文中加入_123后綴,然后加密到加密文件中 local encrypted_aes_pass=`encrypt "${aes_pass}_123" $temp_aes_pass` echo $encrypted_aes_pass > $temp_pass_dir } ssh_target() { local pass_path=$1 local user=$2 local host=$3 local port=$4 local aes_pass=$5 if [ "$port" == "" ]; then port=22 fi encrypted=`cat $pass_path` # 這里增加判斷,如果傳入的解密密碼為空, if [ "$aes_pass" == "" ]; then aes_pass=`get_session_aes_pass` # 第二次判斷,如果解密出的內(nèi)容為空,則需要重新輸入解密的秘鑰 if [ "$aes_pass" == "" ]; then read -s -p 'please enter aes password:' aes_pass echo '' # 將秘鑰明文加密保存 `save_session_aes_pass $aes_pass` fi fi pass=`decrypt $encrypted $aes_pass` # echo "decrypted pass is ${pass}" ../libs/_ssh.exp $host $user $pass $port }然后在同一個終端中,只在第一次執(zhí)行
ssh_my_server.sh的時候需要輸入密碼,在后續(xù)的操作中不再需要輸入密碼。當重新打開一個終端時,才會要求再次輸入密碼。當需要登錄多個不同的服務器時,可以創(chuàng)建多個不同的ssh_my_server.sh文件,順序分別是先創(chuàng)建登錄密碼的加密文件,然后在ssh_my_server.sh文件中配置加密文件位置和服務器登錄名,host,端口等信息。