Python 实现RSA加解密文本文件


Posted in Python onDecember 30, 2020

近来在使用python写项目,特此记录一下项目中遇到的文件加解密问题。
关于python版本的加密算法,随便搜一搜还是可以检索出来很多的,不过大都是同一篇文章在不同的平台来回发布,或者就是转载,而且例举的都是最简单的情况,那么,实际项目中使用的话,肯定会比这个要稍微复杂一些,比如我的需求就是要加密一个使用mysqldump出来的数据库脚本文件,直接拿网上的例子过来调用肯定是不行的,所以不得不自己研究了一番,特此记录。

RSA算法

什么是RSA算法?

项目选型的算法是RSA非对称加密算法,关于这个算法不做过多的解释,咱们划重点:

  • 公钥用于加密
  • 私钥用于解密
  • len_in_byte(raw_data) = len_in_bit(key)/8 -11,如 1024bit 的密钥,一次能加密的内容长度为 1024/8 -11 = 117 byte

为何要减去11个byte?

因为我们使用的是PKCS1Padding占用了11个byte,那么它能加密的明文长度就必须减去这11个byte

可能会遇到什么问题?

基于以上三点,我们大概可以知道要完成文件加解密,我们可能会遇到什么问题?

一次性加密明文的长度是和密钥长度有关系的,那么我们要加密一个文件,不能一次性将文本内容读取出来,然后加密
如果文件很大,我们也不可能将文件内容一次性读取到内存当中,可能会直接导致服务器无法响应其他请求,这肯定是不合理的
文本被加密之后,回头解密,如果读取的长度有差异势必导致解密失败,那么这个数据库备份文件就废了,这个就比较危险了

Do It

安装依赖,python版本3.7.4

pip install pycryptodomex -i https://pypi.tuna.tsinghua.edu.cn/simple/

导入模块:

import base64
from Cryptodome import Random
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Cryptodome.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5

生成公钥+私钥,注意这里我们生成的公钥长度是1024bit

# 伪随机数生成器
random_generator = Random.new().read
# rsa算法生成实例
rsa = RSA.generate(1024, random_generator)
private_pem = str(rsa.exportKey(), encoding="utf-8")
with open("client-private.pem", "w") as f:
    f.write(private_pem)
  
public_pem = str(rsa.publickey().exportKey(), encoding="utf-8")
with open("client-public.pem", "w") as f:
    f.write(public_pem)'''

加密,这里对传入的明文长度做了切分,因为我们生成的密钥长度为1024bit,所以我们一次加密的明文长度不能超过117个byte

def rsa_encrypt(plaintext, pub_key):
    '''
    rsa 加密
    :param plaintext: 明文
    :param pub_key:公钥
    '''
    message = plaintext.encode("utf-8")
    length = len(message)
    default_length = 117  # 1024/8 - 11 1024为密钥长度
    rsakey = RSA.importKey(pub_key)
    cipher = Cipher_pkcs1_v1_5.new(rsakey)
    # 不需要切分
    if length <= default_length:
        return default_rsa_encrypt(cipher, message)
    # 需要切分
    offset = 0
    result = []
    while length - offset > 0:
        if length - offset > default_length:
            result.append(default_rsa_encrypt(
                cipher, message[offset:offset+default_length]))
        else:
            result.append(default_rsa_encrypt(cipher, message[offset:]))
        offset += default_length
    return "\n".join(result)
  
def default_rsa_encrypt(cipher, message):
    ciphertext = base64.b64encode(cipher.encrypt(message))
    # print(b"ciphertext:"+ciphertext)
    ciphertext_decode = ciphertext.decode("utf-8")
    # print("ciphertext_decode:"+ciphertext_decode)
    return ciphertext_decode

解密

def rsa_decrypt(ciphertext, priv_key):
    '''
    rsa 解密
    :param ciphertext:密文
    :param priv_key:私钥
    '''
    message = base64.b64decode(ciphertext)
    length = len(message)
    default_length = 128
    rsakey = RSA.importKey(priv_key)
    cipher = Cipher_pkcs1_v1_5.new(rsakey)
    if length <= default_length:
        return default_rsa_decrypt(cipher, message)
    # 需要分段
    offset = 0
    result = []
    while length - offset > 0:
        if length - offset > default_length:
            result.append(rsa_decrypt(
                cipher, message[offset:offset+default_length]))
        else:
            result.append(rsa_decrypt(cipher, message[offset:]))
        offset += default_length
    decode_message = [x.decode("utf-8") for x in result]
    return "".join(decode_message)
  
def default_rsa_decrypt(cipher, message):
    plaintext = cipher.decrypt(message, random_generator)
    # print(b"plaintext:"+plaintext)
    plaintext_decode = plaintext.decode("utf-8")
    # print("plaintext_decode:"+plaintext_decode)
    return plaintext_decode

加解密文件,考虑开头我们提出的问题,采用了逐行读取,逐行加密,加密后密文也逐行写入

def rsa_encrypt_file(file_path, save_path, pub_key):
    '''
    rsa 加密文件
    :param file_path:需要加密文件路径
    :param save_path:加密之后存放的文件路径
    :param pub_key:公钥
    '''
    with open(file_path, "r", encoding="utf-8") as f:
        line = f.readline()  # 读取一行
        while line:
            context = rsa_encrypt(line, pub_key)  # 加密切割后的字符
            with open(save_path, "a", encoding="utf-8") as w:
                w.write(context+"\n")
        line = f.readline()
def rsa_decrypt_file(file_path,save_path,priv_key):
    '''
    rsa 解密文件
    :file_path:需要解密的文件路径
    :save_path:解密之后存放的文件路径
    :priv_key:私钥
    '''
    with open(file_path,"r",encoding="utf-8") as f:
        line = f.readline()
        while line:
            context = rsa_decrypt(line.strip("\n"),priv_key)
            with open(save_path,"a",encoding="utf-8") as w:
                w.write(context)
            line = f.readline()

测试,一开始我使用的是自己随便输入的一行很长的数字文本,亲测没有问题,但是当我直接使用我的数据库脚本文件的时候,加密可以成功,但是会遇到解密后解码失败的情况,当时百思不得其解,我以为是字符集的问题,于是我将utf-8,换成了gb2312,加解密成功了,当时心花怒放,直到我重新加解密了另一个备份文件,又遇到解码失败,当时就睡不着觉了~

直到我看到了这句话不完整的多字节序列(incomplete multibyte sequence)我瞬间明白了,因为我的脚本文件中含有中文,utf8 编码一个汉字是3个byte,gb2312编码一个汉字是2个byte,只要是多字节,那么做切割的时候,就有可能一个汉字被切割成了两部分,那么自然会导致无法解码成正确的汉字了,问题已经明了,就看怎么解决了。

因为是脚本文件,处理不好就有可能导致脚本执行失败,最终导致数据库还原失败,这就违背项目初衷了~

所以我想了一个办法,先对每一行文本做字符编码判断,超过了117,最后一个字符就不累计上去,代码如下:

def cut_string(message,length = 117):
    result = []
    temp_char = []
    for msg in message:#遍历每一个字符
        msg_encode = msg.encode("utf-8")#对每一个字符编码
        temp_encode = "".join(temp_char).encode("utf-8")#累计编码之后的字节数
        if len(temp_encode) + len(msg_encode) <= length:#如果小于约定的长度,加添加入结果集
            temp_char.append(msg)
        else:#如果已经超过了约定的长度,就添加入下一个结果集
            result.append("".join(temp_char))
            temp_char.clear()
            temp_char.append(msg)
    result.append("".join(temp_char))
    return result

加密方法需要重新调整一下:

def rsa_encrypt_file(file_path,save_path,pub_key):
    '''
    rsa 加密文件
    :param file_path:需要加密文件路径
    :param save_path:加密之后存放的文件路径
    :param pub_key:公钥
    '''
    with open(file_path,"r",encoding="utf-8") as f:
        line = f.readline() #读取一行
        while line:
            cut_lines = cut_string(line) # 切割字符 保证汉字不被切割
            for cut_line in cut_lines:
                context = rsa_encrypt(cut_line,pub_key) #加密切割后的字符
                with open(save_path,"a",encoding="utf-8") as w:
                    w.write(context+"\n")
            line = f.readline()

到此问题就已经解决了,其实有了这个cut_string方法之后,之前写的加解密方法中不需要再做切分,但是代码保留。

上面的方法,加解密的效率非常的低,因为是逐行加解密,一个300M的脚本文件,加密完成耗时40分钟,这个实在是太难受了,所以调整了策略,先压缩再加密,所以就涉及到二进制文件的读取与写入,最后的实现代码如下:

def rsa_encrypt_binfile(file_path,save_path,pub_key):
  '''
  rsa 加密二进制文件
  :param file_path:需要加密文件路径
  :param save_path:加密之后存放的文件路径
  :param pub_key:公钥
  '''
  with open(file_path, 'rb') as f:
    message = f.read()
  length = len(message)
  default_length = 117 # 1024/8 - 11 1024为密钥长度
  rsakey = RSA.importKey(pub_key)
  cipher = Cipher_pkcs1_v1_5.new(rsakey)
  # 不需要切分
  result = []
  if length <= default_length:
    result.append(base64.b64encode(cipher.encrypt(message)))

  # 需要切分
  offset = 0
  while length - offset > 0:
    if length - offset > default_length:
      result.append(base64.b64encode(cipher.encrypt(message[offset:offset+default_length])))
    else:
      result.append(base64.b64encode(cipher.encrypt(message[offset:])))
    offset += default_length
  
  with open(save_path,"ab+") as w:
    for ciphertext in result:
      ciphertext += b"\n"
      w.write(ciphertext)
def rsa_decrypt_binfile(file_path,save_path,priv_key):
  '''
  rsa 解密二进制文件
  :file_path:需要解密的文件路径
  :save_path:解密之后存放的文件路径
  :priv_key:私钥
  '''
  with open(file_path,"rb") as f:
    line = f.readline()
    while line:
      message = base64.b64decode(line.strip(b"\n"))
      rsakey = RSA.importKey(priv_key)
      cipher = Cipher_pkcs1_v1_5.new(rsakey)
      plaintext = cipher.decrypt(message, random_generator)
      with open(save_path, 'ab+') as w: #追加写入
        w.write(plaintext)
      line = f.readline()

以上就是Python 实现RSA加解密文本文件的详细内容,更多关于python rsa加解密的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
在Python中使用next()方法操作文件的教程
May 24 Python
Python编程之Re模块下的函数介绍
Oct 28 Python
使用python实现ANN
Dec 20 Python
python实现堆和索引堆的代码示例
Mar 19 Python
Python 删除连续出现的指定字符的实例
Jun 29 Python
python实现旋转和水平翻转的方法
Oct 25 Python
PyQt5下拉式复选框QComboCheckBox的实例
Jun 25 Python
详解python实现小波变换的一个简单例子
Jul 18 Python
浅谈Python 递归算法指归
Aug 22 Python
基于Django框架的权限组件rbac实例讲解
Aug 31 Python
PyQt5的相对布局管理的实现
Aug 07 Python
Python根据URL地址下载文件并保存至对应目录的实现
Nov 15 Python
python之随机数函数的实现示例
Dec 30 #Python
利用Python实现学生信息管理系统的完整实例
Dec 30 #Python
使用gunicorn部署django项目的问题
Dec 30 #Python
pyspark对Mysql数据库进行读写的实现
Dec 30 #Python
python实现无边框进度条的实例代码
Dec 30 #Python
python中的列表和元组区别分析
Dec 30 #Python
python实现xml转json文件的示例代码
Dec 30 #Python
You might like
基于PHP 面向对象之成员方法详解
2013/05/04 PHP
深入PHP与浏览器缓存的分析
2013/06/03 PHP
PHP中实现生成静态文件的方法缓解服务器压力
2014/01/07 PHP
PHP在网页中动态生成PDF文件详细教程
2014/07/05 PHP
Thinkphp搜索时首页分页和搜索页保持条件分页的方法
2014/12/05 PHP
php curl批处理实现可控并发异步操作示例
2018/05/09 PHP
jquery 选择器引擎sizzle浅析
2013/02/06 Javascript
JS子父窗口互相操作取值赋值的方法介绍
2013/05/11 Javascript
在javascript中实现函数数组的方法
2013/12/25 Javascript
jQuery过滤HTML标签并高亮显示关键字的方法
2015/08/07 Javascript
AngularJS实现全选反选功能
2015/12/08 Javascript
JavaScript中的原始值和复杂值
2016/01/07 Javascript
AngularJs学习第五篇从Controller控制器谈谈$scope作用域
2016/06/08 Javascript
node.js中 stream使用教程
2016/08/28 Javascript
Ionic2调用本地SQlite实例
2017/04/22 Javascript
JavaScript纯色二维码变成彩色二维码
2020/07/23 Javascript
JS排序算法之冒泡排序,选择排序与插入排序实例分析
2017/12/13 Javascript
浅谈webpack对样式的处理
2018/01/05 Javascript
解决layui 表单元素radio不显示渲染的问题
2019/09/04 Javascript
Vue3新特性之在Composition API中使用CSS Modules
2020/07/13 Javascript
uniapp微信小程序实现一个页面多个倒计时
2020/11/01 Javascript
python字符串连接方式汇总
2014/08/21 Python
Flask框架中密码的加盐哈希加密和验证功能的用法详解
2016/06/07 Python
Python随机生成均匀分布在三角形内或者任意多边形内的点
2017/12/14 Python
python并发2之使用asyncio处理并发
2017/12/21 Python
Python中asyncio与aiohttp入门教程
2018/10/16 Python
简单介绍python封装的基本知识
2019/08/10 Python
python 判断txt每行内容中是否包含子串并重新写入保存的实例
2020/03/12 Python
如何解决python多种版本冲突问题
2020/10/13 Python
新闻专业本科生的自我评价分享
2013/11/20 职场文书
财务分析个人的自荐书范文
2013/11/24 职场文书
手机被没收检讨书
2014/02/22 职场文书
社区公民道德宣传日活动总结
2015/03/23 职场文书
Golang获取List列表元素的四种方式
2022/04/20 Golang
微软团队与 NASA 科学家和惠普企业(HPE)的工程师合作
2022/04/21 数码科技
css中有哪些方式可以隐藏页面元素及区别
2022/06/16 HTML / CSS