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

访问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);

}

DES (Data Encryption Standard) 算法是一种对称密钥加密技术,由IBM在1970年代设计并广泛用于数据安全。它的工作原理基于迭代、替换、混合和异或操作,将明文分成固定大小的数据块,通过16轮的加密过程进行处理。 DES的加密过程涉及56位密钥,分为两个8位的子密钥(Key Schedule),每轮加密包括四个步骤:置换(Substitution)、行交换(Permutation)、混洗(Mixing Function)和奇偶校验(Parity Check)。 以下是简单的DES加密算法C语言中的概述: ```c #include <stdio.h> #include <stdlib.h> #define DES_BLOCK_SIZE 8 #define KEY_SIZE 8 #define S-boxes 48 // S-boxes (substitution boxes) const uint8_t S_boxes[4][S_boxes] = { // S-box data... }; // Round function (Feistel network) void feistel(uint8_t* block, const uint8_t* key, int round) { // Implement Feistel step... } int main() { uint8_t key[KEY_SIZE], plaintext[DES_BLOCK_SIZE], ciphertext[DES_BLOCK_SIZE]; // Key setup and plaintext initialization... for(int i = 0; i < 16; ++i) { // 16 rounds of encryption feistel(plaintext, key, i); memcpy(ciphertext, plaintext, DES_BLOCK_SIZE); // Swap places plaintext = ciphertext; } // Final round is different feistel(plaintext, key, 16); // Output ciphertext... return 0; } ``` 注意这只是一个非常基础的示例,实际的DES实现会更复杂,涉及到更多的细节和函数。在生产环境中,你会看到专门设计的库如OpenSSL来处理DES的加密和解密,而不是直接在C代码里实现所有的细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值