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中urllib.unquote乱码的原因与解决方法
Apr 24 Python
Python反射和内置方法重写操作详解
Aug 27 Python
tensorflow使用神经网络实现mnist分类
Sep 08 Python
Python设计模式之原型模式实例详解
Jan 18 Python
Python中print函数简单使用总结
Aug 05 Python
学python安装的软件总结
Oct 12 Python
python GUI库图形界面开发之PyQt5 Qt Designer工具(Qt设计师)详细使用方法及Designer ui文件转py文件方法
Feb 26 Python
Python Socketserver实现FTP文件上传下载代码实例
Mar 27 Python
Python之变量类型和if判断方式
May 05 Python
python如何查看网页代码
Jun 07 Python
Python爬取微信小程序Charles实现过程图解
Sep 29 Python
一些让Python代码简洁的实用技巧总结
Aug 23 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 Ajax中文乱码问题解决方法
2009/02/27 PHP
php读取文件内容的几种方法详解
2013/06/26 PHP
php selectradio和checkbox默认选择的实现方法详解
2013/06/29 PHP
PHP获取文件的MD5值并判断是否被修改的例子
2014/06/19 PHP
PHP7.1新功能之Nullable Type用法分析
2016/09/26 PHP
用php+ajax新建流程(请假、进货、出货等)
2017/06/11 PHP
PHP实现的简单路由和类自动加载功能
2018/03/13 PHP
PHP命名空间定义与用法实例分析
2019/08/14 PHP
Array的push与unshift方法性能比较分析
2011/03/05 Javascript
JS编程小常识很有用
2012/11/26 Javascript
关于javascript中的typeof和instanceof介绍
2012/12/04 Javascript
关于JavaScript对象的动态选择及遍历对象
2014/03/10 Javascript
22点关于jquery性能优化的建议
2014/05/28 Javascript
jquery实现的鼠标下拉滚动置顶效果
2014/07/24 Javascript
介绍一个简单的JavaScript类框架
2015/06/24 Javascript
AngularJS过滤器filter用法分析
2016/12/11 Javascript
JS实现图片预加载之无序预加载功能代码
2017/05/12 Javascript
jQuery+HTML5实现WebGL高性能烟花绽放动画效果【附demo源码下载】
2017/08/18 jQuery
JavaScript动态加载重复绑定问题
2018/04/01 Javascript
解决vue prop传值default属性如何使用,为何不生效的问题
2020/09/21 Javascript
[03:49]DOTA2 2015国际邀请赛中国区预选赛第二日现场百态
2015/05/27 DOTA
[01:00:17]DOTA2-DPC中国联赛 正赛 SAG vs Dynasty BO3 第二场 1月25日
2021/03/11 DOTA
浅析Python函数式编程
2018/10/06 Python
Python实现的微信红包提醒功能示例
2019/08/22 Python
Python代理IP爬虫的新手使用教程
2019/09/05 Python
Python使用selenium + headless chrome获取网页内容的方法示例
2019/10/16 Python
python查看矩阵的行列号以及维数方式
2020/05/22 Python
python中逻辑与或(and、or)和按位与或异或(&amp;、|、^)区别
2020/08/05 Python
python3将变量输入的简单实例
2020/08/19 Python
Python字符串三种格式化输出
2020/09/17 Python
婴儿鞋,独特的婴儿服装和配件:Zutano
2018/11/03 全球购物
表彰先进集体通报
2014/01/12 职场文书
教师评语大全
2014/04/28 职场文书
同学聚会祝酒词
2015/08/10 职场文书
微信小程序中wxs文件的一些妙用分享
2022/02/18 Javascript
Golang并发工具Singleflight
2022/05/06 Golang