前言
我在工作中经常要将一些文件传输到另外一个服务器上,而且都是Linux的命令行环境,那么对于我来讲scp就是最直接有效的方法了,其他诸如FTP、SMB以及Winscp这些有界面的文件传输工具到反而有些多余了。
使用过scp的都知道需要指定远端服务器的帐号并手动输入密码,那么如何避免每次都需要输入密码这个操作呢?下面就给出两种方案进行解决。
方法一:建立SSH的信任关系
在这里先介绍两个概念:SSH公钥(~/.ssh/id_rsa.pub)和公钥授权文件(~/.ssh/authorized_keys),这两个文件的作用具体可以参考ssh的man手册:
~/.ssh/id_rsa.pub
Contains the public key for authentication. These files are not sensitive and can (but need not) be readable by anyone.
~/.ssh/authorized_keys
Lists the public keys (DSA/ECDSA/RSA) that can be used for logging in as this user. The format of this file is described in the sshd(8) manual page. This file is not highly sensitive, but the recommended permissions are read/write for the user, and not accessible by others.
从描述中我们可以知道,~/.ssh/id_rsa.pub文件中包含了认证的公钥信息,而且该文件可以被任何人读取;而~/.ssh/authorized_keys文件中则列举了登录用户的公钥信息(换句话说就是使用这些公钥信息可以登录当前设备),而为了安全考虑,该文件一般建议只有本用户可以有读写权限。
公钥和私钥
提到公钥对应的就会有私钥,在谈这两个概念之前我们先了解另外一组概念:加密和认证。
加密是对数据进行处理,添加保护信息,如果非法用户得到被加密过的数据,也无法获取到原始的有效数据内容,所以加密的重点在于数据的安全性。
认证是对数据进行处理,添加鉴权信息,如果在传输的过程中被非法篡改,接收方就会校验失败并丢弃该非法数据,所以认证的重点在于数据的合法性。
上面是从业务概念来上描述了加密和认证的区别,但是从具体技术实现上,认证使用的是加密中的非对称加密算法来实现鉴权和认证的操作。
对称加密算法在加密和解密时使用的是同一个秘钥;
非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
下面就分别描述一下,采用非对称算法(即使用公钥和私钥)的加密和认证各自的过程。
基于公钥和私钥的加密过程
有两个用户Alice和Bob,Alice想把一段数据加密后发送给Bob(注意这里强调的是数据的安全性),那么如何保证除了Bob之外的人即使窃取了数据也无法解密得到原始的数据?基于公钥和私钥的加密可以完成这个需求,具体流程如下:
Bob将他的公钥发送给Alice
Alice用Bob的公钥加密需要传输的数据然后发送给Bob
Bob用他的私钥解密Alice的消息
基于公钥和私钥的认证过程
还是Alice和Bob,Alice想把一段数据发送给Bob(注意这里并不强调数据的安全性),当Bob收到数据时如何判断该数据确实是Alic发送的且传输过程中没有被篡改?基于公钥和私钥的认证可以完成这个需求,具体流程如下:
Alice用她的私钥对数据加密(或者对于数据的哈希值进行加密作为签名)
Alice将加密后的数据(或者明文数据+签名)发送给Bob
Bob用Alice的公钥解密数据(或校验签名),如果解密(校验)成功则可以保证数据的发送方肯定是Alice
基于公钥和私钥的信任关系
了解了公钥/私钥以及加密/认证这些概念后,我们就可以在scp中使用公钥/私钥来建立一个信任关系,从而在数据传输时完成自动认证而无需输入密码。
User在A主机将SCP请求使用自己的私钥进行加密,然后传输给B主机
B主机在收到SCP请求时,使用User的公钥进行解密
如果解密成功,则表示该请求确实是User发送的请求,则允许操作;如果解密失败,则表示该请求无效,直接丢弃
所以这里的需要做的就是:
User在A主机上需要创建一个公私钥对
在B主机上,将User在A主机的公钥加入到ssh的信任公钥列表中(即公钥授权文件)
创建公私钥对
在Linux上使用ssh-keygen工具来生成公私钥对,在man手册中关于ssh-keygen工具的说明如下:
ssh-keygen : authentication key generation, management and conversion
-t type
Specifies the type of key to create. The possible values are 'rsa1' for protocol version 1 and 'dsa', 'ecdsa', 'ed25519', or 'rsa' for protocol version 2.
在上面提到了ssh-keygen支持rsa1、dsa、ecdsa、ed25519和rsa这几种非对称加密算法,其中最常用的就是RSA和DSA,比如使用RSA算法来生成公私钥对的过程如下:
nfer@nfer-VirtualBox:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/nfer/.ssh/id_rsa):
Created directory '/home/nfer/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/nfer/.ssh/id_rsa.
Your public key has been saved in /home/nfer/.ssh/id_rsa.pub.
The key fingerprint is:
16:77:45:71:31:7c:67:4f:91:09:07:74:4d:30:83:48 nfer@nfer-VirtualBox
The key's randomart image is:
+--[ RSA 2048]----+
| .E..*@XO|
| . ..oBB|
| . . . o+|
| o . .|
| S |
| . |
| |
| |
| |
+-----------------+
nfer@nfer-VirtualBox:~$
注意上面有三处需要输入信息,分别是:
存储公私钥的文件夹位置,如果不输入,则默认为~/.ssh/,文件名则默认是id_rsa和id_rsa.pub
使用该公私钥时是否需要密码,如果不输入则表示不需要密码
再次确认是否需要密码
将User在A主机的公钥加入到ssh的信任公钥列表中
将刚才创建的~/.ssh/id_rsa.pub文件中的内容拷贝添加到B主机上的~/.ssh/authorized_keys文件中(如果没有则创建一个),这个时候就建立了一条A-->B的信任关系。注意这个信任关系是有方向性的,如果要建立从B-->A的信任关系,则操作步骤和上面的类似,只不过要反过来。
建立好信任关系后,这个时候使用任何ssh相关的工具则都无需输入远端的登陆密码(如果在创建公私钥对时输入了密码,那么这个时候还需要输入这个密码才能使用公私钥对)。
方法二:使用sshpass工具来自动输入密码
其实上面的建立信任关系的做法是最方便和安全的做法,但是在有些场景下(比如远端的authorized_keys是不能随意更改的),那么这个时候我们就可以借助sshpass这个第三方工具来完成ssh连接时的密码输入。先看一下sshpass的man手册中是如何描述的:
sshpass - noninteractive ssh password provider
从描述上就可以清晰的了解到,sshpass的设计就是为了使用非交互的场景下输入ssh连接的密码。
sshpass的使用比较简单,先看一下帮助文档:
nfer@nfer-VirtualBox:~$ sshpass
Usage: sshpass [-f|-d|-p|-e] [-hV] command parameters
-f filename Take password to use from file
-d number Use number as file descriptor for getting password
-p password Provide password as argument (security unwise)
-e Password is passed as env-var "SSHPASS"
With no parameters - password will be taken from stdin
-h Show help (this screen)
-V Print version information
At most one of -f, -d, -p or -e should be used
其中-p是直接指定密码,-f是从文件中读取密码。那么一个使用sshpass的简单例子就是:
sshpass -p nferzhuang scp a.txt nferzhuang@192.168.1.101:/home/nferzhuang/a.txt
使用sshpass的好处就是方便直接,无需了解公私钥、加密认证等相关知识,简单易懂;但是使用sshpass最大的坏处就是再使用时会涉及到明文密码,大大降低了安全性。
方法三:使用expect脚本来自动输入密码
expect用于自动化地执行linux环境下的命令行交互任务,例如scp、ssh之类需要用户手动输入密码然后确认的任务。有了这个工具,定义在scp过程中可能遇到的情况,然后编写相应的处理语句,就可以自动地完成scp操作了。
下面就是一个使用expect来完成scp时无需输入密码的脚本:
#!/usr/bin/expect
set timeout 10
set host [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set src_file [lindex $argv 3]
set dest_file [lindex $argv 4]
spawn scp $src_file $username@$host:$dest_file
expect {
"(yes/no)?"
{
send "yes\n"
expect "*assword:" { send "$password\n"}
}
"*assword:"
{
send "$password\n"
}
}
expect "100%"
expect eof
注意代码刚开始的第一行,指定了expect的路径,与shell脚本相同,这一句指定了程序在执行时到哪里去寻找相应的启动程序。代码刚开始还设定了timeout的时间为10秒,如果在执行scp任务时遇到了代码中没有指定的异常,则在等待10秒后该脚本的执行会自动终止。
从以上代码刚开始的几行可以看出,我为这个脚本设置了5个需要手动输入的参数,分别为:目标主机的IP、用户名、密码、本地文件路径、目标主机中的文件路径。如果将以上脚本保存为expect_scp文件,则在shell下执行时需要按以下的规范来输入命令:
./expect_scp 192.168.75.130 root 123456 /root/src_file /root/dest_file
以上的命令执行后,将把本地/root目录下的src_file文件拷贝到用户名为root,密码为123456的主机192.168.75.130中的/root下,同时还将这个源文件重命名为dest_file。
spawn代表在本地终端执行的语句,在该语句开始执行后,expect开始捕获终端的输出信息,然后做出对应的操作。expect代码中的捕获的(yes/no)内容用于完成第一次访问目标主机时保存密钥的操作。有了这一句,scp的任务减少了中断的情况。代码结尾的expect eof与spawn对应,表示捕获终端输出信息的终止。
使用expect需要了解的一点是:用expect速度会比较慢,因为需要等待返回的数据,然后输入命令执行,没有ssh密钥登录的快速。
注:关于expect部分详细请参考《shell结合expect写的批量scp脚本工具》
总结
在本文中提供了三种方法来实现scp的时候无需输入密码的需求,从安全性和速度上考虑建立信任关系都是最佳的方法,至于在具体的环境中选择什么则由你自己来决定。
————————————————
执行具体命令 -- 远程执行shell
ssh root@192.168.56.2 "sh /root/test.sh"