DES

一、DES加密过程

  1. 64位秘钥经子秘钥产生算法产生16个子秘钥:K1、K2……、K16, 分别供第一次到第16次加密迭代使用。
  2. 64位明文首先经过初始置换IP,将数据打乱重新排列并分成左右两半。左边32位构成L0,右边32位构成R0.
  3. 由加密函数f实现子秘钥K1对R0的加密,结果为32位的数据组f(R0, K1) 。f(R0, K1)再与L0模2相加(即异或),又得到一个32位的数据组L0&f(R0, K1). 以L0&f(R0, K1)作为第二次加密迭代的R1,
    以R0作为第二次加密迭代的L1.至此,第一次加密迭代结束。
  4. 第二次到第16次加密迭代分别用子秘钥K2到K16进行,其过程如上。
  5. 第16次迭代结束,产生一个64的数据组。以其左边32位作为R16, 以其右边作为L16,两者合并再经过逆初始置换IP-1,将数据重新排列,便得到64位密文。加密结束。

:下面提到的二进制位数都是从左往右数的。

二、DES的算法细节

<1> 子秘钥的产生
64位秘钥经过置换选择1循环左移置换选择2等变换,产生16个48位长的子秘钥。
如图:
DES子秘钥生成
1. 64位秘钥中的每个字节的最后一位为奇偶校验位,所以秘钥的真实长度为56位,将这56位经置换选择1分为左右各28位,置换选择1的矩阵如下:

左部分C0                          右部分D0
57 49 41 33 25 17 9              63 55 47 39 31 23 15
1  58 50 42 34 26 18             7  62 54 46 38 30 22
10 2  59 51 43 35 27             14 6  61 53 45 37 29
19 11 3  60 52 44 36             21 13 5  28 20 12  4

C0的各位依次为原始秘钥的57, 49, ……..,36位;
D0的各位依次为原始秘钥的63 ,55, ……..,4位。
2. 将得到的C0,D0各做循环左移,每一轮的循环左移如下表:

迭代次数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
左移位数 1 1 2 2 2 2 2 2 1 2  2  2  2  2  2  1

:左移为循环左移
3. 右移后得到C1,D1,合并后经置换选择2得到子秘钥K1,置换选择2如下表:

14 17 11 24 1   5
3  28 15 6  21 10
23 19 12 4  26  8 
16 7  27 20 13  2
41 52 31 37 47 55
30 40 51 45 33 48
44 49 39 56 34 53 
46 42 50 36 29 32

C1、D1合并为56位数,从中选取第14位,17位……32位作为子秘钥K1的第1-32位。
4. 迭代上面2-3步,分别得到K2-K16.

<2> 初始置换IP
将64位明文打乱重排并分为左右两半。左32位为L0, 右32位为R0, 供后面的加密迭代使用。置换IP的矩阵如下

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

置换后64位数据的第1-64位依次为原明文的第58,50,… ,7位。

<3>加密函数
上面得到的L0,R0分别作为加密函数的输入,先经过选择运算将32位输入扩展为48位,以便与48的子秘钥进行异或,还有后续的S盒替换。
1. 选择运算
选择运算通过重复选择某些数据位来达到数据扩展的目的,选择运算的矩阵如下:

        32 1  2  3  4   5
        4  5  6  7  8   9 
        8  9  10 11 12 13
        12 13 14 15 16 17 
        16 17 18 19 20 21
        20 21 22 23 24 25
        24 25 26 27 28 29
        28 29 30 31 32  1

end
2. 模2加(异或) 。将选择运算得到的48位中间值与48位子秘钥异或。
3. S盒替换。将上面得到的48位数,分为8部分,1-6,7-12,…,43-48.分别经S1,S2,…,S8盒各转换为4位二进制数,合为32位数。
S盒内容看S盒等数据,太多不写了。。。
4. 置换运算P。置换运算把输出的32位数据打乱重排,得到32位的加密函数输出。用P置换来提供扩散,把S盒的混淆作用扩散开来。正是置换P和S盒的互相配合提高了DES的安全性。

三、DES算法实现

我使用的是位运算的方法来重排各个元素的,所以上面的各种位置矩阵我都转换为掩码的形式,方便位运算,为了方便转换为掩码,使用下面的转换程序:

#include <stdio.h>
#define ROW 8         // 矩阵行数
#define COL 8         // 矩阵列数
#define MAX (ROW*COL) // 最大位数

static int table[ROW][COL] = {...}; // 存放相应的位置矩阵
for(i = 0; i < ROW; i++)
{
   printf("{");
   for(j = 0; j < COL; j++)
   {
      printf("0x%lx", n << (MAX-table[i][j])); // 因为是从左往右数的位置,而这里的运算是从右往左的,要用数的最大位数去减
      if(j != COL-1)
         printf(", ");
   }
   if(i != ROW-1)
       printf("}, \n");
    else
        printf("}\n");
}    

类型定义

typedef short bool; // 布尔类型
enum{false, true};
typedef unsigned int bit_32; // 32位类型
typedef long long bit_64;    // 64位类型,64位机
typedef long bit_48;         // 48位类型
typedef int bit_28;          // 28位类型

子秘钥产生函数:

static bit_64 frlTable[4][7]; // 置换选择1的左边矩阵
static bit_64 frrTable[4][7]; // 置换选择1的右边矩阵
static bit_64 srTbale[8][6];  // 置换选择2的矩阵
static int bitTable[16];      // 循环左移的矩阵

void firstReplace(bit_64 key, bit_28 *left, bit_28 *right ) // 置换选择1,key为64位的原始秘钥, left, right分别指向存放左右部分秘钥

void secondReplace(bit_28 l, bit_28 r, bit_48 *subkey) // 置换选择2, l, r为打乱后的左右部分秘钥, subkey为子秘钥存放位置

void leftmove(bit_28 *value, int bits) // 循环左移,value指向要处理的值,bits为左移位数

void geneSubKeys(bit_64 key, bit_48 *subkeys) // 产生子秘钥,key为原始秘钥,subkeys为子秘钥数组

初始置换函数:

static bit_64 ipTable[8][8]; // 初始置换IP表

void initReplaceIp(bit_64 text, bit_32 *left, bit_32 *right); // 初始置换IP函数, text为64位明文,left指向置换后的左部,right指向置换后的右部

加密函数:

static bit_32 selectTable[8][6]; // 选择运算表
static int Sbox1[4][16]; // 八个S盒
static int Sbox2[4][16];
static int Sbox3[4][16];
static int Sbox4[4][16];
...
static int Sbox8[4][16];
static int** Sbox[8]; // 存放八个S盒的首地址指针
static bit_32 replTable[8][4]; // 置换运算的矩阵
static bit_64 ripTable[8][8]; // 逆IP置换矩阵

void selectOperator(bit_32 value, bit_48 *result);// 选择运算函数,value为32位输入值,result指向运算结果值
void SboxOperator(bit_48 value, bit_48 subkey, bit_32 *result); // S盒运算函数,value为48位输入,subkey为子秘钥,result指向运算结果值
void ReplaceOperator(bit_32 *value)// 置换运算函数,将S盒输出的32位数据打乱重排,再存回value指向的值
void reverseInitReplace(bit_32 left, bit_32 right, bit_64 *ciphertext) // 逆初始置换函数,left为16次迭代后的左部,right为16次迭代后的右部,ciphertext指向打乱后的密文
bit_64 encrypt(bit_32 left, bit_32 right, bit_48 *subkeys) // p输入处理后明文的左右部分left为左部,right为右部,subkeys指向子秘钥数组,返回密文

遇到的问题:

  1. 书中说的第几位,是从左往右数的位数,我搞错了,以为是二进制数的实际位数。。。
  2. 很久没用移位了,这次遇到了几个小问题。
    <1> 移位的优先级低于四则运算,高于逻辑运算,所以bit_64 value = (uleft << 32) + right;要加括号,否则会出错。。。再比如:
    row = ((sixbits&0x20) >> 4) + (sixbits&0x1);

    <2>右移时尽量使用无符号数。因为右移分逻辑右移算术右移,逻辑右移直接在左端补0,算术右移在左端补最高位有效位的值。而无符号数只有逻辑右移,有符号数两者都可以,C语言没有明确说明。。。

    <3>左移时要注意被左移数类型是否适合,会不会有溢出的可能,例如

    bit_64 key;
    
    key = (l << 28) + r;// error: 这里的l变量为bit_28类型,所以肯定会溢出
    bit_64 ul = l;
    key = (ul << 28) + r; // OK: 这样就不会溢出了。。

    <4> 循环左移时要把用掩码限定在要求的位数内(例如这里的0xFFFFFFF,限定在28位),不然如果类型的长度比你要设定的循环长度长的话,就会出错:

    inline void leftmove(bit_28 *value, int bits) // 循环左移
    { // 因为bit_28其实是int类型,存储位数为32位                   
       int left = *value;  
       int right = *value; 
       left = left << bits;
       left = left & 0xFFFFFFF;  // 没有这步的话,超过28位部分会被保留                                                                                               
       right = right >> (28-bits);
       *value = left | right;
    } 

    注:使用inline函数,gcc编译时要加-O2选项,即gcc -O2 -o des main.c des.c
    不然会出现错误:

    $ gcc -o des main.c des.c
    /tmp/ccyn4UUe.o:在函数‘geneSubKeys’中:
    des.c:(.text+0x209):对‘leftmove’未定义的引用
    des.c:(.text+0x223):对‘leftmove’未定义的引用
    collect2: error: ld returned 1 exit status

    原因是:


    C语言原本是不支持inline的,但C++中原生对inline的支持让很多C编译器也为C语言实现了一些支持inline语义的扩展。C99将inline正式放入到标准C语言中,并提供了inline关键字。和C++中的inline一样,C99的inline也是对编译器的一个提示,提示编译器尽量使用函数的内联定义,去除函数调用带来的开销。inline只有在开启编译器优化选项时才会生效。

    引用自:查看原文

  3. 二维数组的首地址不能直接作为两重指针的值。

    static int Sbox1[4][16] = {...};
    static int Sbox2[4][16] = {...};
    
    static int **Sbox[2] = {Sbox1, Sbox2}; // warning: 类型不匹配
    typedef int (*p)[16]; // p 为指向长度为16的一维数组的指针类型
    static p Sbox[2] = {Sbox1, Sbox2}; // OK: Sbox1, Sbox2其实就是这样的类型,要指明第二维度的长度,这样Sbox1+1等操作才有明确的意义

猜你喜欢

转载自blog.csdn.net/linda_ds/article/details/80188316
DES