Des加密原理与简单实现

源码:https://gitee.com/Cheney822/programmes/blob/master/des.py

摘要

实验利用Python语言实现了des加密和解密的功能,并封装成一个类方便后续调用。程序可以读取由数字、符号、字母和中文组成的字符串,并将其转换成二进制在程序内部处理。程序需要的密钥为64位二进制,处理的明文为64的整数倍,对于读入的密钥和明文,转换后不足64位的补0补到64位,超过64位的密钥直接丢弃,超过64位的明文补0补到64的整数倍。

程序为了能够处理中文,采用utf-8编码,读入的明文经过编码转为二进制交由函数处理得到密文,由于加密后的密文不一定符合utf-8编码规则,若使用utf-8解码会得到不可识读、无法输出的字符,故对加密后的二进制密文做base64转换成字符串,输出给用户。解密过程是上述过程的逆过程,首先读入密文字符串,然后利用base64解码,得到的密文二进制序列交由函数处理得到明文字二进制。最后由utf-8编码输出原明文字符序列。

程序预留了调试信息的输出接口,可支持三级中间信息的输出。默认为0级不做任何调试信息的输出,1级仅输出少量重要信息,二级输出全部详细调试信息。

程序采用了多线程并行的设计,加密和解密的过程中要把源文本二进制每64位为一个部分投入一个独立的线程进行处理,最后各个线程处理的结果拼接为最终结果。并预留接口可以实现多线程模式的开/关。

背景

*数据加密*,指的是根据一定规则,将数据处理成不规则的数据,使得人们除非有了关键的钥匙以及得知这个规则,难于得知无规则数据的真实含义。这个一定规则 就是加密算法,这个钥匙就是密钥。

*数据加密分为对称密钥加密以及非对称密钥加密:*

对称密钥加密: 双方共同持有这个密钥,发送方用这个密钥按照指定的算法将数据加密,再发出去;接收方用这个密钥将接收到的数据解密,以得到真实的数据含义。由于双方都持有这个密钥,而且内容相同,所以叫对称密钥

非对称密钥加密:这种加密方式的密钥是一对,发送方用其中的一把钥匙将数据加密,再发出去;接收方用这对密钥的另一把钥匙将数据解密,以得到真实的数据含义。发送方持有密钥中的一把钥匙,接收方持有另外一把。接收方持有的钥匙叫 私钥, 而接收方持有的这把钥匙叫公钥 。两把钥匙不一样,所以叫做非对称密钥加密,也叫做公开密钥算法。

*对称密钥加密:*

对称密钥加密又叫专用密钥加密或共享密钥加密,即发送和接收数据的双方必使用相同的密钥对明文进行加密和解密运算。对称密钥加密算法主要包括:DES、3DES、IDEA、RC5、RC6等。

对称密钥加密是双方使用相同的密钥,必须以绝对安全的形式传送密钥才能保证安全。若果密钥泄露,加密数据将受到威胁,这点不如非对称密钥。

发送方和接收方必须用安全的方式来获得密钥和保存密钥,必须保证密钥的安全。如果有人发现了密匙,并知道了算法,则使用此密匙加密的所有数据便都是可被窃取。即使分析人员拥有一些密文和生成密文的明文,也不能译出密文或者发现密钥。加密算法应足以抵抗已知明文类型的破译。

des加密

DES是美国国家标准和技术局(NIST)在1977年才有的数据加密标准,DES的思路就是参照二战时期盟军缴获的德军恩格玛加密机,当然更为复杂和严谨,文件编号为FIPS PUB46。老牌而强悍的加密算法,广泛地使用使其颇具名气。算法本身称为DEA(数据加密算法)。DES是最常用的对称加密算法。DES密匙长度为56位,分组长度为64位。为了提高加密强度,后来又发展出三重DES加密,即3DES。

其加密运算、解密运算使用的是同样的密钥,信息的发送者和信息的接收者在进行信息的传输与处理时,必须共同持有该密码(称为对称密码),是一种对称加密算法。

DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行“异或”运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。

*主要流程是:*

  • 将明文分组

  • 对每个分组进行初始字节置换

  • 生成对应的子密钥

  • 对每一个迭代进行置换,异或运算等运算,得到分组密文

  • 迭代直到完成全部迭代过程

DES算法具有极高安全性,到目前为止,除了用穷举搜索法对DES算法进行攻击外,还没有发现更有效的办法。而56位长的密钥的穷举空间为256,这意味着如果一台计算机的速度是每一秒种检测一百万个密钥,则它搜索完全部密钥就需要将近2285年的时间,可见,这是难以实现的,当然,随着科学技术的发展,当出现超高速计算机后,我们可考虑把DES密钥的长度再增长一些,以此来达到更高的保密程度。

Des类概要设计

接口

类支持的参数列表如下:

:param key: 加密或解密的密钥 只取输入密钥对应二进制的前64位 其他丢弃
:param plaintext: 明文 任意长度的任意内容
:param secrettext: 密文 任意长度的经过本程序加密的内容
:param is_debug: 是否输出调试信息 0表示不输出 1仅输出重要信息 2输出详细信息  默认不输出
:param use_threading: 是否使用多线程的方式 0表示不用  默认使用

其中只有密钥key是必要参数(因为需要在构造函数内执行16个子密钥的初始化工作),其他参数均设有默认值。明文和密文两个参数也可以在调用加密/解密函数的时候给出,如果两处都给出那么函数内的参数会覆盖初始化的参数。

函数

*Des类的成员函数如下:*

image-20220323212338470

执行逻辑

image-20220323212358069

详细设计

初始化__init__函数

在函数里内进行一系列的初始化操作。

参数列表:
:param key: 加密或解密的密钥 只取输入密钥对应二进制的前64位 其他丢弃
:param plaintext: 明文 任意长度的任意内容
:param secrettext: 密文 任意长度的经过本程序加密的内容
:param is_debug: 是否输出调试信息 0表示不输出 1仅输出重要信息 2输出详细信息  默认不输出
:param use_threading: 是否使用多线程的方式 0表示不用  默认使用
  • 首先处理类的初始化时接收到的参数,将其依次赋给成员变量。

  • 然后定义再定义两个成员变量:lock和key_48_mat,分别表示多线程时用到的锁以及十六个48位的子密钥。

  • 最后调用成员函数get_key_48用来根据参数key生成十六个子密钥,即完成对key_48_mat的初始化工作。

置换功能的实现

由于程序中多处用到置换功能(比如PC1置换、IP置换、E拓展等),故将此功能抽象成函数方便复用。

用table表对source做置换 得到 置换后的列表ans
:param source: 要进行置换的列表(不会直接影响这个列表的内容)
:param table: 里面储存的是置换的规则 如:PC1,IP
:return: 新的 置换后的列表        

函数replace的功能是把source表内的明文按照table表中的规则进行替换,生成替换后的新表。

子密钥生成

用来初始化key_48_mat,由输入的密钥生成每轮需要用的密钥
:param key: 用户输入的密钥
:return: 每轮需要用的密钥key_48_mat

子密钥生成分为以下几步:

  • 第一步,先把密钥中的奇偶校验为去掉,然后根据选择置换PC-1讲剩下的密钥分成两块C0和D0;
  • 第二步,将C0和D0进行循环左移变换,变换后生成C1和D1,然后C1和D1合并,通过选择置换PC-2生成子密钥K1;
  • 第三步,C1和D1再次经过循环左移变换,生成C2和D2,C2和D2合并,通过选择置换PC-2生成子密钥K2;
  • 第四步,以此类推,需要注意其中循环左移的位数,一共是循环左移十六次,其中LS1(第一次),LS2(第二次),LS9,LS16是循环左移一位,其他的都是左移两位。
image-20220323212501344

轮函数

对输入的32位数据, 做当前轮对应的操作 E拓展/异或/S盒/P置换

:param text_32: 输入的32位数据
:param round: 轮次
:return: 最后处理完的32位数据

*轮函数加密过程分为以下几步:*

  • 第一步,将R0通过位选择函数E置换,其实这是一个扩充的置换,因为R0本身是32位的,而生成的子密钥是48位的,因此需要扩充一下,方能按位运算。
  • 第二步,将扩充完的R0和子密钥K1进行模2加运算,得到48位的一个串,把这个串从左到右分为8组,每组6个字符。这里设8组分别为B1,B2,B3,B4,B5,B6,B7,B8。其中Bj=b1b2b3b4b5b6。
  • 第三步,通过S盒来收缩,把每组中的b1b6放一块,换算为十进制,b2b3b4b5放一块,也换算为十进制。b1b6代表S盒中的行标,b2b3b4b5代表列标。比如说B1=011111,那么b1b6就等于十进制的1,b2b3b4b5等于十进制的15,也就是对应表中的S1块中的第1行(注意不是0行),15列,也就是8,然后把8变为二进制1000。这就完成了S盒收缩变换,然后通过S盒输出的就是32位的一个串。
  • 第四步,把32位的串经过置换函数P的置换得到的结果就是这个核心函数的产物了。
image-20220323212514302

主循环

:param sourcetext_64: 源操作文本  targettext目标文本
:return: 返回对原文本处理的结果 即targettext

在这个函数内部做加/解密的主操作,包括:IP置换、16轮迭代(调用轮函数)、异或、前后颠倒、IP逆置换。(传入的参数和产生的结果都是64位二进制)

*具体的步骤:*

  • 首先对64位的二进制源操作文本进行IP置换。
  • 然后将其分离成左右两部分,并将右半部分投入轮函数运行,左半部分的内容保留下来。
image-20220323212528603
  • 轮函数的返回值仍然是32位的二进制,用上述分离出的左半部分与其异或得到最终的右半部分,然后与保留的左半部分结合。循环执行上述操纵,直至完成十六轮操作。
  • 十六轮操作结束之后,将前后两半部分颠倒。
  • 最后IP逆置换 得到二进制密文
image-20220323212544039

二进制与字符串转换

程序涉及到的二进制转文本和文本转二进制分别由bit64_to_text以及text_to_64bit完成。

text_to_64bit完成的任务是将传入的text转换成若干个长度为64的由0/1组成的字符串(不足的补0),并由列表的形式返回。操作步骤:

  • 首先判断传入的是不是bytes类型,不是则用encode方法将其解码为bytes

  • 然后遍历整个bytes类型的text,逐元素转为二进制(每个元素为8bit)

  • 判断处理完64个bit则将64bit拼接成一个字符串并加入列表

  • 如果bytes不足64整数倍,则在最后一个元素内补0补到64位

  • 重复上述步骤直至遍历结束返回列表

bit64_to_text完成的任务是将二进制列表转化成可读的句子。此函数主要为密文的显示服务,由于加密的结果不再符合utf8的编码规则,所以每八个bit转一个八进制,然后再将其转为byte类型并返回,外围函数最终会将此bytes类型变量转为可读的字符串。

加/解密

加密函数(encryption):首先将用户传入的明文(字符串)用text_to_64bit函数转为二进制列表,然后遍历整个列表,将元素逐个用main_frame函数处理,如果开启了多线程则还要将main_frame投入一个新的线程中去运行。

由于得到的二进制密文较长,而且由于加密的结果是混乱不可读的,无法用utf8解码,所以使用bit64_to_text函数进行转换的话会得到不可识别的字符,故这里采用base64编码将二进制密文转换成较短的文本返回给优化。

等待所有子线程后,将得到的二进制用base64.b64encode函数转换为base64编码,然后以字符串的格式返回给用户。

image-20220323212608354

解密(decryption)的过程就是上述过程的逆过程,核心部分与DES的加密过程完全相同,只不过将16轮的子密钥序列K1,K2……K16的顺序倒过来变成K16,K15……K ,代码实现也十分简单,只需运行reverse()方法即可。

需要注意的是程序内部处理的均为二进制(字符串格式的01序列),而读入和输出的都是具有一定可读性的字符串。所以在读入后和输出前都要做一定的处理。具体流程如上图所示。

多线程的实现

import threading
threading.Thread(target=self.main_frame, args=[plaintext_64_bit_mat[ii], ii, ]).start()
threading.Thread(target=self.main_frame, args=[secrettext_64_bit_mat[ii], ii, ]).start()

由于程序主循环每次只能处理64位的数据,而需要处理的数据往往远大于64。所以采用切片分别处理的方式来处理,即把要处理的数据切成大小为64bit的片段(不够的末尾补0),分别投入函数处理然后再把处理完的结果拼接起来作为最终结果输出(如下图)

image-20220323212634530

由于多个片段的处理是互相独立的,所以采用多线程并行的方式来加快运行速度。即每次将一个64位的片段作为参数传递给main_frame处理,然后将这个函数投入新的线程中去运行,注意python中threading.Thread传参的格式。

由于线程之间的资源是共享的所以存在临界资源访问的问题,注意要互斥的访问公共的资源。设置类的成员lock表示锁的值,除此以外本程序中不涉及到公共资源的写入问题。Lock的初始值为子线程的总数,在生成子线程之前确定,每次有一个线程完成任务就要主动的将锁的值减一。在程序执行到后续的步骤时,需要主线程等待所有子线程将任务处理完毕,只需要在主线程中循环判断lock的值是否为0即可。

程序预留了多线程的开关,只需要在实例化对象的时候传入参数use_threading=0/1即可,实现多线程的关/开。注意如果打开了程序输出调试信息的选项,那么就会自动关闭多线程模式,因为多线程下会乱序输出调试信息,导致信息不可读。

运行测试与使用手册

首先调用接口,实例化des对象,然后使用des对象内的加密和解密方法进行数据的加密和加密。

加密

加密要调用encryption函数,若在实例化对象时已经输入了明文和密钥,则调用函数时不再需要给参数,如果实例化时没有给出明文,则需要再给出明文参数,如果两处都给出了参数,则encryption函数处传入的参数会覆盖掉实例化时给出的参数,如果两处都没有参数,则会采用程序内置的默认参数。

本程序在测试时明文和密钥都在实例化的时候传入参数,在调用函数时不再传入参数。

PlainText = input("请输入要加密的明文:\t")
DES = des(Key, plaintext=PlainText)  # 定义DES对象
SecretText = DES.encryption()
image-20220323220358719

正确解密

只需要先定义des对象,然后调用对象的decryption方法即可。解密的参数调用和加密相同。

SecretText = input("请输入要解密的密文:\t")
DES = des(Key, secrettext=SecretText)  # 定义DES对象
PlainText = DES.decryption()
image-20220323220423794

错误密钥解密

密钥错误会无法还原出可读的明文,程序会提示错误

图片1

错误密文解密

密文错误会无法还原出可读的明文,程序会提示错误

img

关闭多线程模式

加入参数 use_threading=0

SecretText = input("请输入要解密的密文:\t")
DES = des(Key, secrettext=SecretText, use_threading=0)  # 定义DES对象
PlainText = DES.decryption()
image-20220323220942362

输出简略调试信息

加入is_debug=1参数。

SecretText = input("请输入要解密的密文:\t")
DES = des(Key, secrettext=SecretText)  # 定义DES对象
PlainText = DES.decryption()

程序会输出生成的十六个子密钥,以及每一轮操作的中间结果

image-20220323221011409 image-20220323221024674

输出详细调试信息

参数列表同上,但is_debug=2。

输出上述所有中间结果的同时会输出轮函数内部每一步的中间结果。

img img

因为输出内容较多,整个算法用时也会大幅上升。

img

猜你喜欢

转载自blog.csdn.net/weixin_46291251/article/details/126423713
今日推荐