相對(duì)安全方式保存SSH密碼并實(shí)現(xiàn)自動(dòng)登錄

背景

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

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

  • Mac下可以直接 brew install expect

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

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

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

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

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