1. 公钥与私钥
公钥(Public Key)与私钥(Private Key)是通过加密算法得到的一个密钥对(即一个公钥和一个私钥,也就是非对称加密方式)。公钥可对会话进行加密、验证数字签名,只有使用对应的私钥才能解密会话数据,从而保证数据传输的安全性。公钥是密钥对外公开的部分,私钥则是非公开的部分,由用户自行保管。用公钥加密的数据必须用对应的私钥才能解密;如果用私钥进行加密也必须使用对应的公钥才能解密,否则将无法成功解密。
比较有趣的例子是鲍勃的钥匙,附上转载的链接 图解公钥与私钥的使用
2. git 配置公钥与私钥
首先需要下载 git ,官方网站地址:Git - Downloads
-
安装完之后进入git终端,输入命令
ssh-keygen -t rsa
并按三次回车,会在默认系统用户目录下 .ssh 文件夹里面生成一个私钥 id_rsa 和一个 公钥 id_rsa.pub
ssh-keygen 会确认密钥的存储位置(默认是 .ssh/id_rsa),然后它会要求你输入两次密钥口令。 如果你不想在使用密钥时输入口令,将其留空即可。 然而,如果你使用了密码,那么请确保添加了 -o 选项,它会以比默认格式更能抗暴力破解的格式保存私钥。 你也可以用 ssh-agent 工具来避免每次都要输入密码。 -
查看公钥,使用的命令是
cat ~/.ssh/id_rsa.pub
,公钥类似于
$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx
NrRFi9wrf+M7Q== schacon@mylaptop.local
-
拷贝公钥到远程服务器上,输入命令
ssh-copy-id -i 公钥文件路径 usename@hostname
,然后会生成已知的主机公钥清单 know_hosts
-
git 终端进入虚拟机环境
ssh 用户名@服务器IP
git 详细中文教程: git 官方教程
3. paramiko 模块
通过ssh远程连接服务器并执行想要命令,类似于Xshell、ansible批量管理机器,ansible底层用的就是paramiko模块。
paramiko 模块官方文档:https://www.paramiko.org/
该模块属于第三方模块,需要下载使用
pip3 install paramiko
(3.1) 连接服务器
连接服务器执行命令的两种方式:
用户名和密码连接服务器执行命令
公钥私钥连接服务器执行命令
- 用户名密码方式
# 用户名和密码的方式
import paramiko
# 创建ssh对象
ssh = paramiko.SSHClient()
# 允许链接不在know_hosts文件中主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 链接服务器
ssh.connect(hostname='10.0.0.100', port=22, username='root', password='111')
# 执行命令
stdin, stdout, stderr = ssh.exec_command('ip a')
# stdin, stdout, stderr = ssh.exec_command('ls')
"""
stdin 输入的内容
stdout 输出的内容
stderr 错误的内容
"""
# 获取结果
res = stdout.read() # 基于网络传输 该结果是一个bytes类型
print(res.decode('utf-8'))
# 断开链接
ssh.close()
- 公钥私钥方式
# 公钥和私钥(需要先将公钥保存到服务器上)
import paramiko
# 读取本地私钥
private_key = paramiko.RSAKey.from_private_key_file('私钥路径')
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='服务器IP', port=22, username='用户', pkey=private_key)
# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls /')
# 获取命令结果
result = stdout.read()
print(result.decode('utf-8'))
# 关闭连接
ssh.close()
(3.2) 上传与下载文件
上传或下载文件的两种方式:
用户名和密码连接服务器上传下载文件
公钥私钥连接服务器上传下载文件
- 用户名密码方式
import paramiko
# 用户名和密码
transport = paramiko.Transport(('服务器IP', 22))
transport.connect(username='用户', password='密码')
sftp = paramiko.SFTPClient.from_transport(transport)
# 上传文件 如:将py文件相对路径下的a.txt文件上传到服务器的data文件夹下并重命名为tmp.txt
sftp.put("a.txt", '/data/tmp.txt') # 注意上传文件到远程某个文件下 文件必须存在
# 下载文件
sftp.get('/data/tmp.txt', 'b.txt') # 将远程文件下载到本地并重新命名
transport.close()
- 公钥私钥方式
import paramiko
private_key = paramiko.RSAKey.from_private_key_file('私钥路径')
transport = paramiko.Transport(('服务器IP', 22))
transport.connect(username='用户', pkey=private_key)
sftp = paramiko.SFTPClient.from_transport(transport)
# 将当前py文件相对路径下的location.py 上传至服务器 /tmp/test.py
sftp.put('/tmp/location.py', '/tmp/test.py')
# 将remove_path 下载到本地 local_path
sftp.get('remove_path', 'local_path')
transport.close()
注意点:
get
⽅法和put
方法⼀次只能传输⼀个⽂件,不能传输⽬录。- 上传和下载的路径参数都必须具体到⽂件名,不能是⽬录,⽂件名也不能⽤通配符
- 不管是
get
⽅法还是put
方法,若⽬标⽂件存在会直接覆盖。
(3.3) 方法整合
import paramiko
class SSHProxy(object):
def __init__(self, hostname, port, username, password):
self.hostname = hostname
self.port = port
self.username = username
self.password = password
self.transport = None
def open(self): # 给对象赋值一个上传下载文件对象连接
self.transport = paramiko.Transport((self.hostname, self.port))
self.transport.connect(username=self.username, password=self.password)
def command(self, cmd): # 正常执行命令的连接 至此对象内容就既有执行命令的连接又有上传下载链接
ssh = paramiko.SSHClient()
ssh._transport = self.transport
stdin, stdout, stderr = ssh.exec_command(cmd)
result = stdout.read()
return result
def upload(self, local_path, remote_path):
sftp = paramiko.SFTPClient.from_transport(self.transport)
sftp.put(local_path, remote_path)
sftp.close()
def close(self):
self.transport.close()
def __enter__(self):
print('with开始')
self.open()
return self # 该方法返回什么 with ... as 后面就拿到什么
def __exit__(self, exc_type, exc_val, exc_tb):
print('with结束')
self.close()
if __name__ == '__main__':
# 不用with上下文管理语句需要加上 open()和close()方法
# obj = SSHProxy(hostname='10.0.0.200',port=22,username='root',password='123')
# obj.open()
# print(obj.command('ip a'))
# print(obj.command('ls /'))
# print(obj.command('ls -l'))
# obj.upload(r'hhh.txt','/data/heihei.txt')
# obj.close()
# 由于在类中定义了 __enter__ 和 __exit__方法分别在with开始和结束分别执行 open()和close()
with SSHProxy(hostname='10.0.0.200', port=22, username='root', password='123') as obj:
print(obj.command('ip a'))
obj.upload(r'a.txt', '/data/b.txt')