背景
Linux或者M(jìn)ac上一般都是直接用的終端來連接SSH,基本上很少有類似Windows上用XShell之類的客戶端。所以在終端上直接登錄都必須輸入密碼,但是如果密碼比較復(fù)雜就更難記住了。這時(shí)候可以通過SSH-Key來實(shí)現(xiàn)秘鑰登錄。
SSH-Key登錄遠(yuǎn)程服務(wù)器
SSH-Key是一種基于密鑰的安全認(rèn)證,遠(yuǎn)程服務(wù)器持有公鑰,本地持有私鑰,在客戶端向服務(wù)器發(fā)送請(qǐng)求之后,服務(wù)端在用戶主目錄下查找用戶的公鑰,然后對(duì)比用戶發(fā)送過來的公鑰,如果一致則用公鑰加密”質(zhì)詢“并發(fā)送給客戶端??蛻舳耸盏健辟|(zhì)詢“后用私鑰解密,在發(fā)送給服務(wù)端,認(rèn)證結(jié)束。
要實(shí)現(xiàn)這種方式的登錄首先需要?jiǎng)?chuàng)建ssh-key:
ssh-keygen -t rsa
在交互界面中可以按默認(rèn)的直接回車,最后會(huì)在
${USER_HOME}/.ssh/下保存公鑰和私鑰文件:id_rsaid_rsa.pub然后需要將公鑰保存到服務(wù)器上,執(zhí)行以下命令即可將公鑰發(fā)送到服務(wù)器上,需要輸入登錄密碼
ssh-copy-id -i ~/.ssh/id_rsa.pub ${user}@${host}以上命令會(huì)將公鑰文件保存到服務(wù)器用戶目錄下的
.ssh/authorized_keys中配置完成之后便可以直接免密碼登錄了
ssh ${user}@${host}
其他擴(kuò)展配置
有時(shí)候可能會(huì)需要?jiǎng)?chuàng)建多個(gè)不同的秘鑰對(duì),用于不同的服務(wù)器登陸,或者用于Github的免密操作
通過ssh-keygen創(chuàng)建新的文件,此時(shí)直接定義新的名字,比如:
ssh-keygen -t rsa -C '另一個(gè)服務(wù)器' -f ~/.ssh/my_id_rsa然后同樣的將公鑰發(fā)送到需要登錄的服務(wù)器
ssh-copy-id -i ~/.ssh/my_id_rsa ${user}@${host}-
再在登錄的時(shí)候指定私鑰文件,或者通過
~/.ssh/config自動(dòng)帶上指定的文件直接指定的方式:
ssh ${user}@${host} -i ~/.ssh/my_id_rsa-
通過配置文件的方式:
# ~/.ssh/config # 指定某一服務(wù)器所使用的私鑰文件 Host serverAlias # 服務(wù)器的別名,可以隨便起一個(gè) 或者直接按ip也可以 HostName ${服務(wù)器的ip} User ${user} # 指定登錄用戶名 PreferredAuthentications publickey IdentityFile ~/.ssh/my_id_rsa # 指定私鑰文件
另一種情況
- 但是也有些情況下,無法將本地的公鑰發(fā)送到服務(wù)器上,比如登錄客戶的服務(wù)器,或者登錄一個(gè)IP或者端口可能會(huì)變化的服務(wù)器,比如我使用了免費(fèi)的內(nèi)網(wǎng)穿透來連接我的樹莓派,它的端口就會(huì)經(jīng)常變化。
- 在這種情況下,登錄的時(shí)候都得去手動(dòng)輸入密碼了。當(dāng)用戶名或者密碼很難記住的時(shí)候,往往會(huì)特別需要一個(gè)能夠記住用戶名密碼的客戶端。Mac下免費(fèi)的客戶端較少,比如Termius就是一個(gè)不錯(cuò)的客戶端,但是不知為何它有時(shí)候會(huì)卡死。所以我選擇了自己實(shí)現(xiàn)記住密碼的方式,可以在登錄時(shí)只記住一個(gè)密碼,將不同服務(wù)器的密碼通過加密保存,登錄的時(shí)候通過輸入解密秘鑰來自動(dòng)解密登錄。
通過加密文件保存服務(wù)器密碼實(shí)現(xiàn)自動(dòng)登錄
通過該方式需要依賴的工具如下:
opensslexpect, 一般情況下 openssl都是自帶了的,往往只需要安裝一下expectMac下可以直接
brew install expectUbuntu 下可以通過apt安裝
sudo apt install expectexpect 是一種交互式的開源工具,用于實(shí)現(xiàn)自動(dòng)化的功能
第一步創(chuàng)建加密方法,保存密碼的密文
創(chuàng)建一個(gè)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加載方法,然后調(diào)用create_encrypted_pass在交互界面中輸入密碼和加密秘鑰,加密秘鑰需要牢記于心,以后登錄時(shí)只需要輸入它即可完成后會(huì)打印加密后的密文,將密文保存下來,比如保存到
${host}.pass
第二步創(chuàng)建解密方法,和自動(dòng)登錄的方法
在func.sh中補(bǔ)充解密和登錄方法
## 解密方法 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ù)有 加密文件路徑,用戶名,服務(wù)器host,(端口,解密秘鑰【這兩個(gè)可選】) 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)建自動(dòng)執(zhí)行腳本,_ssh.exp,用于根據(jù)輸入?yún)?shù)自動(dòng)登錄到服務(wù)器上
#!/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)建一個(gè)針對(duì)某一服務(wù)器的登錄腳本,在里面配置一些信息
比如 ssh_my_server.sh
source ./func.sh ## 用于加載預(yù)定義的方法 ssh_target ${host}.pass ${用戶名} ${服務(wù)器host} ${端口}然后對(duì)以上兩個(gè)文件賦予可執(zhí)行權(quán)限
chmod a+x ssh_my_server.sh _ssh.exp
第三步,登錄服務(wù)器
- 此時(shí)要登錄到服務(wù)器時(shí),只需要執(zhí)行 ssh_my_server.sh 即可
-
./ssh_my_server.sh然后根據(jù)提示輸入加密秘鑰,這個(gè)秘鑰牢記于心即可。一般不知道秘鑰無法解密出具體的登錄密碼,所以是比較安全的,在腳本中也不會(huì)暴露密碼信息。
額外實(shí)現(xiàn)
以上方式,每次執(zhí)行
./ssh_my_server.sh都需要輸入一遍密碼,有時(shí)候又覺得有些麻煩??梢陨晕⒃俑脑煲幌?,在當(dāng)前終端中不再需要輸入密碼。實(shí)現(xiàn)方式是得到和終端相關(guān)的數(shù)據(jù),用它作為加密密鑰,將記在心里的那個(gè)秘鑰保存下來。在func.sh中增加有些方法,并修改ssh_target
## 根據(jù)終端的信息創(chuàng)建臨時(shí)秘鑰,該方法創(chuàng)建的秘鑰只要在當(dā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 }然后在同一個(gè)終端中,只在第一次執(zhí)行
ssh_my_server.sh的時(shí)候需要輸入密碼,在后續(xù)的操作中不再需要輸入密碼。當(dāng)重新打開一個(gè)終端時(shí),才會(huì)要求再次輸入密碼。當(dāng)需要登錄多個(gè)不同的服務(wù)器時(shí),可以創(chuàng)建多個(gè)不同的ssh_my_server.sh文件,順序分別是先創(chuàng)建登錄密碼的加密文件,然后在ssh_my_server.sh文件中配置加密文件位置和服務(wù)器登錄名,host,端口等信息。