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