Python 实现RSA加解密文本文件

 更新时间:2020年12月30日 16:44:39   作者:dwBurning  
这篇文章主要介绍了Python 实现RSA加解密文本文件的方法,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下

近来在使用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 中使用 Luhn 算法验证数字

    如何在Python 中使用 Luhn 算法验证数字

    Luhn 算法验证器有助于检查合法数字并将其与不正确或拼写错误的输入分开,这篇文章主要介绍了在Python中使用Luhn算法验证数字,需要的朋友可以参考下
    2023-06-06
  • Python解析JSON对象的全过程记录

    Python解析JSON对象的全过程记录

    这篇文章主要给大家介绍了关于Python解析JSON对象的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 在Python中使用sort()方法进行排序的简单教程

    在Python中使用sort()方法进行排序的简单教程

    这篇文章主要介绍了在Python中使用sort()方法进行排序的简单教程,是Python学习中的基础知识,需要的朋友可以参考下
    2015-05-05
  • python创建学生管理系统

    python创建学生管理系统

    这篇文章主要为大家详细介绍了python创建学生管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-11-11
  • Python文件读写w+和r+区别解析

    Python文件读写w+和r+区别解析

    这篇文章主要介绍了Python文件读写w+和r+区别解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Python操作MongoDB的教程详解(插,查,改,排,删)

    Python操作MongoDB的教程详解(插,查,改,排,删)

    MongoDB是一个基于分布式文件存储的数据库。是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。本文将详细和大家聊聊Python操作MongoDB的方法,需要的可以参考一下
    2022-09-09
  • Python实现代码统计工具

    Python实现代码统计工具

    这篇文章主要为大家详细介绍了Python实现代码统计工具,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09
  • 基于Tensorflow的MNIST手写数字识别分类

    基于Tensorflow的MNIST手写数字识别分类

    这篇文章主要为大家详细介绍了基于Tensorflow的MNIST手写数字识别分类,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-06-06
  • python 多线程中子线程和主线程相互通信方法

    python 多线程中子线程和主线程相互通信方法

    今天小编就为大家分享一篇python 多线程中子线程和主线程相互通信方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-11-11
  • python numpy实现rolling滚动案例

    python numpy实现rolling滚动案例

    这篇文章主要介绍了python numpy实现rolling滚动案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-06-06

最新评论