DES算法的详细描述和C语言实现

news2024/11/29 4:53:00

访问www.tomcoding.com网站,学习Oracle内部数据结构,详细文档说明,下载Oracle的exp/imp,DUL,logminer,ASM工具的源代码,学习高技术含量的内容。

前言

很久以前用汇编语言实现过DES算法,时间久了细节都记不清楚了,后来想复习一下,到网上搜了一下,发现大部分都是转来转去的帖子和博文,原始的作者也都不知道是谁了。找了几个介绍比较详细的文章仔细研究了一下,原理和大部分细节都搞清楚了,于是想用C语言实现这个算法。还是在网上搜了一下,发现这些源代码都是拿数组来代替位操作,把C语言中很好的位操作给摒弃掉了,也没有耐心继续找下去,决定自己写一个,体现算法中的细节描述。

这个程序的目的是对照着文档中对DES算法的介绍,把每个步骤都用函数实现了,使得想学习DES算法的朋友可以很容易的理解每一步操作,也能体会编程中的一些细节。

算法介绍

DES算法是对称加密算法,以64位分组对数据进行加密,DES算法的加密和解密使用了相同的算法。算法中都是按位操作的,数据和密钥都是64位,8个字节。DES算法已经使用了几十年的时间,但是对于理解密码学中的基本概念和算法的设计还是有重要意义的,理解了DES算法,再去学习其他的加密算法就会容易一些。

加密算法

DES算法的输入是一组8字节(64位)的明文数据,这8个字节先进行一个初始置换(Initial Permutation),把数据原来的位顺序打乱,然后把数据分成左右两组,前4个字节(32位)是左边的组,后4个字节(32位)是右边的组。把右边的组进行一系列运算,然后与左边的组做异或操作,新生成的4字节作为下一次计算的右边组,原来右边的组直接作为下一次计算的左边组,这样的计算轮次一直循环进行16次,最后把左右两组互换,还组成一个64位的字节序列,再进行最后一次数据置换,这样生成的数据就是密文数据。画个图来说明一下。

从上面的图中可以看出每次迭代都是对右边的组进行的运算,先进行一个扩展置换,把32位数据扩展成48位,然后与相应轮的48位密钥异或操作,再通过S盒替换选择,把48位数据再变回32位数据,再通过P盒替换进一步混淆,最后与左边组的32位数据进行异或操作,作为新一轮的右边组数据,原来运算前的右边组数据直接作为左边组进行新一轮循环。

下面的章节中我们逐个分析一下每个步骤中都进行了什么样的操作。

初始置换

初始置换(Initial Permutation)是通过一个置换表把64位的位置重新排列了一次,生成了新的位顺序。置换表的定义如下。

表格中的数字是每一位在置换前的位置,取值范围是1-64,不能把它当做数据替换。从上表中看到,原来数据的第58位成为置换后数据的第1位,第50位成为置换后的第2位,一直到第7位成为置换后的第64位。在数据初始置换后就会分成两组,前面的4个字节一组是L0(Left Half),后面的4个字节一组是R0(Right Half),作为初始分组进行下面的迭代运算。

扩展置换

扩展置换(Expansion)是要把右边组的32位数据扩展成48位,因为接下来一步要与密钥进行异或运算,这里的密钥是由原始密钥生成的子密钥,是48位长度,所以右边组的32位数据也要变成48位才行。扩展置换也是通过一个表来替换完成的。

从上面可以很清楚的看到,中间的是原来的32位数据,两边红色的是扩展出来的数据,扩展的原则就是原来最右边的扩展下一位的数据,原来最左边的扩展上一位的数据,比如原来的08在第二行最右边,扩展下一位就是09。

与密钥异或

这里的密钥是生成的子密钥(生成的方法在下面讨论),长度是48位,我们上面的Ri已经扩展出了48位,正好一一对应进行异或操作就可以了。

S盒替换

在上面的扩展置换后,32位数据变成了48位,但是为了后面的运算,还要再变回32位,S盒(S-Box)替换就是实现这个功能的,S盒有6位输入4位输出,48位的输入就需要有8个S盒完成替换,替换后就形成了32位的数据。

8个S盒的定义如下。

每个S盒都是一个4行16列的表,输入是一个6位值,高位和低位组成一个0-3的数作为行坐标,中间的四位组成0-15的数作为列坐标,在对应的S盒中定位到一个数据,这个数值是4位的,作为输出。注意在这里S盒中的数是真正的数值,不是位置。举个例子,我们以第8个S盒进行替换,输入的6位二进制值是011001,高位和低位的组合是01,那么行值是1,中间4位是1100=0x0C=12,那么列值是12,查表S-box 8得到的值是0。

P盒替换

P盒替换也是输入位的位置替换,是一个32位替换32位的操作,与初始置换类似。P盒的定义如下。

轮迭代

轮迭代就是把每一轮的右面32位分组进行上面的运算,然后与左边的32位分组进行异或,作为新一轮的右面分组,左边的分组拷贝运算前右面的分组数据作为新一轮左面分组。一直循环16轮,完成全部迭代。

左右互换

在进行完16轮迭代后,我们得到L16和R16两个分组,把这两个分组互换位置,连接成一个右面在前左面在后的顺序,形成新的64位数据。

最后置换

在上面的左右组互换后的64位数据还要经过一个最终置换(Final Permutation)的过程,才能产生密文数据,这个最终置换也可以说是初始置换的逆置换,与初始置换一样,只是置换表的差别而已。最终置换表定义如下。

密钥生成

原始的密钥有64位,从上面的加密算法中看到,要从原始密钥中产生出16组48位的工作密钥。64位密钥先进行一次置换,产生56位的中间Key,然后分为左右两组,每组28位,这两组Key按照一个表每轮进行循环移位(Rotation),合并后再通过一个置换表选出48位的工作密钥,移位后的每组数据进入下一个循环,一共进行16轮,产生出16组密钥。

密钥置换

密钥置换与数据的初始置换一样也是位置转变,只不过密钥置换后产生的数据是56位而已,密钥置换表如下。

这是一个56个元素的表,在输入时每个字节的第8位不参与(这里说的第8位是从左到右的最后一位),因此表中没有8的整数倍的位数。

密钥循环左移

密钥分组的循环左移,根据轮数不同,左移的位数也不同,有的移一位,有的移两位,根据下面的移位表来确定。

合并密钥

这一步很简单,就是把两个28位的分组,按照左边在前,右边在后的顺序合并成56位的中间值。

置换选择

这一步是把上面生成的56位中间密钥通过一个置换表选择出48位的工作子密钥。置换表的定义如下。

置换函数

从上面的算法描述中我们看到了大量的置换操作,这些操作的原理是一样的,都是通过一个表把原始数据的位转换到一个新的位置。

先定义几个位操作的宏:

#define GET_BIT(p, n)  (p)[(n)/8] &   (0x80>>((n)%8))

#define SET_BIT(p, n)  (p)[(n)/8] |=  (0x80>>((n)%8))

#define CLR_BIT(p, n)  (p)[(n)/8] &= ~(0x80>>((n)%8))

有了上面定义的位操作的宏,我们就可以写置换函数了,先看看初始置换函数。

static uint8_t data_ip[64] =

{  

  58, 50, 42, 34, 26, 18, 10, 2,

  60, 52, 44, 36, 28, 20, 12, 4,

  62, 54, 46, 38, 30, 22, 14, 6,

  64, 56, 48, 40, 32, 24, 16, 8,

  57, 49, 41, 33, 25, 17,  9, 1,

  59, 51, 43, 35, 27, 19, 11, 3,

  61, 53, 45, 37, 29, 21, 13, 5,

  63, 55, 47, 39, 31, 23, 15, 7

};

static void data_initial_permutation(uint8_t *in, uint8_t *out)

{

  int   i;

  for (i=0; i<64; i++)

  {

    if (GET_BIT(in, data_ip[i]-1))

      SET_BIT(out, i);

    else

      CLR_BIT(out, i);

  }

}

在程序的第一个版本中,为了看起来与文档中的描述符合,我们为每一个置换都写了一个函数,其实这些函数都是一样的,只是引用的置换表和置换的位数不一样而已,我们完全可以只写一个函数来代替,把置换表和置换位数当做参数传给函数即可。在程序的第二版中我们把置换函数简化成了一个,如下所示。

static void permutation(uint8_t *in, uint8_t *out, uint8_t *pc, int bits)

{

  int   i;

  for (i=0; i<bits; i++)

  {

    if (GET_BIT(in, pc[i]-1))

      SET_BIT(out, i);

    else

      CLR_BIT(out, i);

  }

}

密钥左移函数

密钥左移函数在描述中看起来很简单,但在实现时还是有些麻烦,因为28位是3个半字节,循环移位时半个字节也要通过一个字节的移位来实现,好在只有4个字节的操作,我们把4个字节都手工移一遍,也不用考虑什么循环了。

static void key_bits_rotation(uint8_t *half, int r)

{

  uint8_t       hb;    /* 最高位标记 */

  /* half[0], half[1], half[2], half[3] */

  hb = half[0] & 0x80;

  /* 移位的原则就是,下一个字节的高位是1,那么移位后低位补1 */

  half[0] = (half[1] & 0x80) ? (half[0]<<1 | 1) : (half[0]<<1);

  half[1] = (half[2] & 0x80) ? (half[1]<<1 | 1) : (half[1]<<1);

  half[2] = (half[3] & 0x80) ? (half[2]<<1 | 1) : (half[2]<<1);

  half[3] = (hb) ? (half[3]<<1 | 0x10) : (half[3]<<1);

  /* 由于移位表中只有1和2两种情况,所以为2时,再移一次 */

  if (key_rotation[r] == 2)

  {

    hb = half[0] & 0x80;

    half[0] = (half[1] & 0x80) ? (half[0]<<1 | 1) : (half[0]<<1);

    half[1] = (half[2] & 0x80) ? (half[1]<<1 | 1) : (half[1]<<1);

    half[2] = (half[3] & 0x80) ? (half[2]<<1 | 1) : (half[2]<<1);

    half[3] = (hb) ? (half[3]<<1 | 0x10) : (half[3]<<1);

  }

}

密钥生成函数

密钥生成函数流程比较清晰,根据文档中的图,把函数组合起来就可以了。输出时要定义一个6*16字节大小的数组存放生成的子密钥。

void key_generation(uint8_t *key, uint8_t *subkey)

{

  int           i;

  uint8_t       left[4], right[4];      /* left & right half data */

  uint8_t       pk[7];                  /* permuted key */

  key_permutation(key, pk);

  memcpy(left, pk, 4);

  left[3] &= 0xF0;

  /* 0 1 2 3  4 5 6 */

  right[0] = pk[3]<<4 | pk[4]>>4;

  right[1] = pk[4]<<4 | pk[5]>>4;

  right[2] = pk[5]<<4 | pk[6]>>4;

  right[3] = pk[6]<<4;

  for (i=0; i<16; i++)

  {

    key_bits_rotation(left,  i);

    key_bits_rotation(right, i);

    memcpy(pk, left, 4);

    pk[3] |= right[0]>>4;

    pk[4]  = right[0]<<4 | right[1]>>4;

    pk[5]  = right[1]<<4 | right[2]>>4;

    pk[6]  = right[2]<<4 | right[3]>>4;

    key_selection(pk, &subkey[i*6]);

  }

}

加密函数

加密函数的流程也很简单,也有文档中的流程图做参照。

void des_encrypt(uint8_t *data, uint8_t *subkey)

{

  int           i, j;

  uint8_t       middle[8], temp[4];

  uint8_t       left[4], right[4];

  /* 初始置换 */

  data_initial_permutation(data, middle);

  /* 中间数据分为左右两个初始分组 */

  memcpy(left, middle, 4);

  memcpy(right, middle+4, 4);

  for (i=0; i<16; i++)

  {

    /* 右分组扩展,从32位到48位*/

    data_bits_expansion(right, middle);

    /* 与这一轮的子密钥进行异或操作 */

    for (j=0; j<6; j++)

    {

      middle[j] ^= subkey[i*6+j];

    }

        /* 通过S盒置换,从48位到32位*/

    data_substitute(middle, temp);

        /* P盒置换,从32位到32位 */

    data_permutation(temp, middle);

        /* 与左分组进行异或操作 */

    for (j=0; j<4; j++)

    {

      temp[j] = middle[j] ^ left[j];

    }

/* 左分组直接从原来的右分组拷贝过来,右分组从上面的运算获得 */

    memcpy(left, right, 4);

    memcpy(right, temp, 4);

  }

  /* 左右分组互换 */

  memcpy(middle, right, 4);

  memcpy(middle+4, left, 4);

  /* 进行最后置换 */

  data_final_permutation(middle, data);

}

解密函数

解密函数与加密函数一样,只是与子密钥异或的顺序倒过来,从K[15]到K[0]。

void des_decrypt(uint8_t *data, uint8_t *subkey)

{

  int           i, j;

  uint8_t       middle[8], temp[4];

  uint8_t       left[4], right[4];

  data_initial_permutation(data, middle);

  memcpy(left, middle, 4);

  memcpy(right, middle+4, 4);

  for (i=15; i>=0; i--)

  {

    /* 32 bits to 48 bits */

    data_bits_expansion(right, middle);

    /* exclusive or with subkey */

    for (j=0; j<6; j++)

    {

      middle[j] ^= subkey[i*6+j];

    }

    /* 48 bits to 32 bits */

    data_substitute(middle, temp);

    /* 32 bits to 32 bits */

    data_permutation(temp, middle);

    for (j=0; j<4; j++)

    {

      temp[j] = middle[j] ^ left[j];

    }

    memcpy(left, right, 4);

    memcpy(right, temp, 4);

  }

  memcpy(middle, right, 4);

  memcpy(middle+4, left, 4);

  data_final_permutation(middle, data);

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE4 材质学习笔记02(数据类型/扭曲着色器)

一.什么是数据类型 首先为啥理解数据类型是很重要的。一些节点的接口插槽只接受特定类型的数据&#xff0c;如果连接了不匹配的数据就会出现错误&#xff0c;有些接口可以接受任何数据类型&#xff0c;但是实际上只会使用到其中的一些。并且有时可以将多个数据流合并成一个来编…

《python语言程序设计》2018版第8章19题几何Rectangle2D类(上)--原来我可以直接调用

2024.9.29 玩了好几天游戏。 感觉有点灵感了。还想继续玩游戏。 2024.10.4 今天练习阿斯汤加练完从早上10点睡到下午2点.跑到单位玩游戏玩到晚上10点多. 现在回家突然有了灵感 顺便说一句,因为后弯不好,明天加练一次. 然后去丈母娘家. 加油吧 第一章、追求可以外调的函数draw_r…

Spring Boot集成encache快速入门Demo

1.什么是encache EhCache 是一个纯 Java 的进程内缓存框架&#xff0c;具有快速、精干等特点&#xff0c;是 Hibernate 中默认的 CacheProvider。 Ehcache 特性 优点 快速、简单支持多种缓存策略&#xff1a;LRU、LFU、FIFO 淘汰算法缓存数据有两级&#xff1a;内存和磁盘&a…

【Git】vscode链接github拉去镜像

1.拉取别人的项目到自己的仓库 2.回到自己的仓库拉取文件到vscode里面下载 使用vscode进入虚拟机 推送到自己的仓库上面 在 github 页面将修改的内容 PR 到 Tutorial 创建一个个人仓库 代码如下 cd demo git clone https://github.com/3154067760/Tutorial.git cd Tutorial/…

贴吧软件怎么切换ip

在网络使用中&#xff0c;有时我们需要切换IP地址来满足特定的需求&#xff0c;比如需要切换贴吧软件IP以进行不同的操作。本文将介绍几种贴吧切换IP地址的方法&#xff0c;帮助用户更好地管理自己的网络身份和访问权限。 1、更换网络环境‌ 通过连接到不同的Wi-Fi网络或使用移…

HBuilderX连接MuMu模拟器最简单的方法

1、在MuMu官网下载MuMu模拟器官网_安卓12模拟器_网易手游模拟器 跟随步骤安装 2、安装后打开MuMu多开器&#xff0c;查看ADB端口号 或者启动MuMu模拟器在问题诊断中查看 3、在HBuilder中配置模拟器端口号和adb路径 4.配置环境变量 5、adbl连接端口 打开cmd运行以下命令 adb …

系统架构设计师-下午案例题(2022年下半年)

1.试题-(共25分):阅读以下关于软件架构设计与评估的叙述在答题纸上回答问题1和问题2。 【说明】某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提…

随笔(四)——代码优化

文章目录 前言1.原本代码2.新增逻辑3.优化逻辑 前言 原逻辑&#xff1a;后端data数据中返回数组&#xff0c;数组中有两个对象&#xff0c;一个是属性指标&#xff0c;一个是应用指标&#xff0c;根据这两个指标展示不同的多选框 1.原本代码 getIndicatorRange(indexReportLi…

逗比大神(ToyoDAdoubi)的ShadowsocksR/SSR一键搭建脚本

逗比大神(ToyoDAdoubi)的ShadowsocksR/SSR一键搭建脚本&#xff08;推荐&#xff09; 我推荐新手小白用户使用逗比大神(ToyoDAdoubi)的SSR一键搭建脚本&#xff0c;因为全中文界面&#xff0c;操作更加简单方便。 此一键安装脚本支持 CentOS 6、Debian 7、Ubuntu 12 及以上系…

10.4学习

1.Transactional 注意事项&#xff1a; ①事务函数中不要处理耗时任务&#xff0c;会导致长期占有数据库连接。 ②事务函数中不要处理无关业务&#xff0c;防止产生异常导致事务回滚。 ●事务传播属性 ①REQUIRED&#xff08;默认属性&#xff09; 如果存在一个事务&#…

ROS基础入门——实操教程

ROS基础入门——实操教程 前言 本教程实操为主&#xff0c;少说书。可供参考的文档中详细的记录了ROS的实操和理论&#xff0c;只是过于详细繁杂了&#xff0c;看得脑壳疼&#xff0c;于是做了这个笔记。 Ruby Rose&#xff0c;放在这里相当合理 本文初编辑于2024年10月4日 C…

云原生(四十五) | ECS服务器项目部署实战

文章目录 ECS服务器项目部署实战 一、ECS服务器项目部署说明 二、下载WordPress 三、部署WordPress需要哪些应用 ECS服务器项目部署实战 一、ECS服务器项目部署说明 案例&#xff1a;为了让大家更好的理解ECS服务器的使用场景&#xff0c;我们通过一个比较经典的WordPres…

红日靶机(三)笔记

VulnStack-红日靶机三 概述 相较于前边两个靶场环境&#xff0c;靶场三的难度还是稍难一点&#xff0c;有很多兔子洞&#xff0c;这就考验我们对已有信息的取舍和试错&#xff0c;以及对渗透测试优先级的判断。涉及到对数据库操作的试错&#xff0c;对 joomla 框架 cve 的快速…

vSAN01:vSAN简介、安装、磁盘组、内部架构与调用关系

目录 传统的共享存储vSAN存储OSA的系统要求vSAN安装vSAN集群vSAN skyline healthvSAN与HA磁盘组混合磁盘架构全闪磁盘架构 vSAN对象vSAN内部架构 传统的共享存储 通过隔离的存储网络使得不同的ESXi主机访问独立的存储设备。需要前期投入较高的资金单独采购存储、网络可以单独规…

OAuth2.0 设备授权流程

OAuth2.0设备授权流程&#xff08;Device Authorization Grant&#xff09;是一种为缺乏输入能力的设备&#xff08;例如智能电视、游戏机、物联网设备等&#xff09;设计的授权模式。这些设备通常不具备复杂的键盘或指定输入方式&#xff0c;无法直接进行OAuth2.0标准的交互授…

8c语言基础文件

关于文件你必须了解的一些基本概念 什么是文件&#xff1f; 文件是计算机文件&#xff0c;属于文件的一种&#xff0c;与普通文件的载体不同&#xff0c;计算机文件是以计算机硬盘为载体存储在计算机上的信息集合。 在程序设计中&#xff0c;我们一般关注的文件有两类&#x…

【C++】空指针和野指针

文章目录 1.空指针2.野指针总结 1.空指针 概念&#xff1a;指针变量指向内存中编号为0的空间。 用途&#xff1a;初始化指针变量。 注意&#xff1a;空指针指向的内存是不可以访问的。 示例&#xff1a; int main(){//指针变量p指向内存地址编号为0的空间int *PNULL&#…

从零开始学cv-15:图像分割

文章目录 前言一、全局阈值分割&#xff1a;二、自适应阈值分割&#xff1a;三、分水岭算法&#xff1a; 前言 在当代计算机视觉领域&#xff0c;图像分割技术扮演着至关重要的角色&#xff0c;它为图像理解、目标识别和场景解析等高级视觉任务提供了基础。OpenCV&#xff0c;…

Redis:hash类型

Redis&#xff1a;hash类型 hash命令设置与读取HSETHGETHMGET 哈希操作HEXISTSHDELHKEYSHVALSHGETALLHLENHSETNXHINCRBYHINCRBYFLOAT 内部编码ziplisthashtable 目前主流的编程语言中&#xff0c;几乎都提供了哈希表相关的容器&#xff0c;Redis自然也会支持对应的内容&#xf…

李宏毅深度学习-循环神经网络RNN

Recurrent Neural Network 这个问题可以使用一个前馈神经网络&#xff08;feedforward neural network&#xff09;来解&#xff0c;如图5.2 所示&#xff0c; 输入是一个单词&#xff0c;把“上海”变成一个向量&#xff0c;“丢”到这个神经网络里面。输入是一个单词&#x…