相对安全方式保存SSH密码并实现自动登录

背景

Linux或者Mac上一般都是直接用的终端来连接SSH,基本上很少有类似Windows上用XShell之类的客户端。所以在终端上直接登录都必须输入密码,但是如果密码比较复杂就更难记住了。这时候可以通过SSH-Key来实现秘钥登录。

SSH-Key登录远程服务器

  • SSH-Key是一种基于密钥的安全认证,远程服务器持有公钥,本地持有私钥,在客户端向服务器发送请求之后,服务端在用户主目录下查找用户的公钥,然后对比用户发送过来的公钥,如果一致则用公钥加密”质询“并发送给客户端。客户端收到”质询“后用私钥解密,在发送给服务端,认证结束。

  • 要实现这种方式的登录首先需要创建ssh-key:

  • ssh-keygen -t rsa
    
  • 在交互界面中可以按默认的直接回车,最后会在${USER_HOME}/.ssh/下保存公钥和私钥文件:id_rsa id_rsa.pub

  • 然后需要将公钥保存到服务器上,执行以下命令即可将公钥发送到服务器上,需要输入登录密码

  • ssh-copy-id -i ~/.ssh/id_rsa.pub ${user}@${host}
    
  • 以上命令会将公钥文件保存到服务器用户目录下的 .ssh/authorized_keys

  • 配置完成之后便可以直接免密码登录了 ssh ${user}@${host}

其他扩展配置

  • 有时候可能会需要创建多个不同的秘钥对,用于不同的服务器登陆,或者用于Github的免密操作

  • 通过ssh-keygen创建新的文件,此时直接定义新的名字,比如:

  • ssh-keygen -t rsa -C '另一个服务器' -f ~/.ssh/my_id_rsa
    
  • 然后同样的将公钥发送到需要登录的服务器

  • 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 # 指定私钥文件
      

另一种情况

  • 但是也有些情况下,无法将本地的公钥发送到服务器上,比如登录客户的服务器,或者登录一个IP或者端口可能会变化的服务器,比如我使用了免费的内网穿透来连接我的树莓派,它的端口就会经常变化。
  • 在这种情况下,登录的时候都得去手动输入密码了。当用户名或者密码很难记住的时候,往往会特别需要一个能够记住用户名密码的客户端。Mac下免费的客户端较少,比如Termius就是一个不错的客户端,但是不知为何它有时候会卡死。所以我选择了自己实现记住密码的方式,可以在登录时只记住一个密码,将不同服务器的密码通过加密保存,登录的时候通过输入解密秘钥来自动解密登录。

通过加密文件保存服务器密码实现自动登录

  • 通过该方式需要依赖的工具如下:openssl expect, 一般情况下 openssl都是自带了的,往往只需要安装一下expect

  • Mac下可以直接 brew install expect

  • Ubuntu 下可以通过apt安装 sudo apt install expect

  • expect 是一种交互式的开源工具,用于实现自动化的功能

第一步创建加密方法,保存密码的密文

  • 创建一个func.sh文件,内容如下

  • #!/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

第二步创建解密方法,和自动登录的方法

  • 在func.sh中补充解密和登录方法

  • ## 解密方法
    decrypt() {
        local encrypted=$1
        local pass=$2
        echo $encrypted | openssl enc -aes-256-cfb -a -d -pass pass:$pass -iter 12 -nosalt
    }
    
    ## 登录方法,输入参数有 加密文件路径,用户名,服务器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
    }
    
  • 然后创建自动执行脚本,_ssh.exp,用于根据输入参数自动登录到服务器上

  • #!/usr/bin/expect
    ## 读取参数
    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" # 发送yes
        expect {
          "password:" { send "${password}\r" } # 发送密码
        }
      }
      "password:" { send "${password}\r" } # 发送密码
    }
    interact
    
  • 然后只需要在创建一个针对某一服务器的登录脚本,在里面配置一些信息

  • 比如 ssh_my_server.sh

  • source ./func.sh ## 用于加载预定义的方法
    ssh_target ${host}.pass ${用户名} ${服务器host} ${端口}
    
  • 然后对以上两个文件赋予可执行权限

  • chmod a+x ssh_my_server.sh _ssh.exp

第三步,登录服务器

  • 此时要登录到服务器时,只需要执行 ssh_my_server.sh 即可
  • ./ssh_my_server.sh 然后根据提示输入加密秘钥,这个秘钥牢记于心即可。一般不知道秘钥无法解密出具体的登录密码,所以是比较安全的,在脚本中也不会暴露密码信息。

额外实现

  • 以上方式,每次执行 ./ssh_my_server.sh 都需要输入一遍密码,有时候又觉得有些麻烦。可以稍微再改造一下,在当前终端中不再需要输入密码。实现方式是得到和终端相关的数据,用它作为加密密钥,将记在心里的那个秘钥保存下来。

  • 在func.sh中增加有些方法,并修改ssh_target

  • ## 根据终端的信息创建临时秘钥,该方法创建的秘钥只要在当前终端执行,得到的都是同样的内容
    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=''
        # 判断是否存在加密文件,不存在则返回空内容
        if test -e $temp_pass_dir ; then
            local encrypted_aes_pass=`cat $temp_pass_dir`
            local temp_aes_pass=`create_temp_pass`
            # 判断密文解密的合法性,我在明文中加入了_123后缀,只有后缀匹配才能确定解密是成功的,否则解密失败返回空内容
            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`
            # 第二次判断,如果解密出的内容为空,则需要重新输入解密的秘钥
            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
    }
    
  • 然后在同一个终端中,只在第一次执行 ssh_my_server.sh 的时候需要输入密码,在后续的操作中不再需要输入密码。当重新打开一个终端时,才会要求再次输入密码。

  • 当需要登录多个不同的服务器时,可以创建多个不同的ssh_my_server.sh文件,顺序分别是先创建登录密码的加密文件,然后在ssh_my_server.sh文件中配置加密文件位置和服务器登录名,host,端口等信息。

你可能感兴趣的:(相对安全方式保存SSH密码并实现自动登录)