相對安全方式保存SSH密碼并實現(xiàn)自動登錄

背景

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_rsa id_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)自動登錄

  • 通過該方式需要依賴的工具如下:openssl expect, 一般情況下 openssl都是自帶了的,往往只需要安裝一下expect

  • Mac下可以直接 brew install expect

  • Ubuntu 下可以通過apt安裝 sudo apt install expect

  • expect 是一種交互式的開源工具,用于實現(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,端口等信息。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容