【安全课堂】DES加解密算法详解
  

深信服安全产品研发 3917

{{ttag.title}}
本帖最后由 某公司安全产品研发 于 2016-3-30 09:44 编辑

作者:某公司移动应用部


DES加解密详解

一、DES算法描述        
                                                                                                                                       
DES算法总的说来可以两部分组成:

1、对密钥的处理。这一部分是把我们用的64位密钥(实际用的56位,去掉了8个奇偶校验位)分散成16个48位的子密钥。

2、对数据的加密。通过第一步生成的子密钥来加密我们所要加密的数据,最终生成密文。

下面就通过这两部分分别介绍DES算法的实现原理。

1.密钥分散——子密钥的生成
64比特的密钥生成16个48比特的子密钥。其生成过程见图:

64比特的密钥K,经过PC-1后,生成56比特的串。其下标如表所示:

1.png

该比特串分为长度相等的比特串C0和D0(分别为28比特)。然后C0和D0分别循环左移1位,得到C1和D1。C1和D1合并起来生成C1D1。C1D1经过PC-2变换后即生成48比特的K1。K1的下标列表为:

2.png

C1、D1分别循环左移LS2位,再合并,经过PC-2,生成子密钥K2……依次类推直至生成子密钥K16。
注意:Lsi (I =1,2,….16)的数值是不同的。具体见下表:

3.png

注:PC-1 和 PC-2是密钥的指定为置换。

至此,我们已成功的生成了16个48位的子密钥。

2.加密流程图
DES算法处理的数据对象是一组64比特的明文串。设该明文串为m=m1m2…m64(mi=0或1)。明文串经过64比特的密钥K来加密,最后生成长度为64比特的密文E。其加密过程图示如下:

4.png

3. DES算法加密过程
对DES算法加密过程图示的说明如下:待加密的64比特明文串m,经过IP置换后,得到的比特串的下标列表如下:

5.png

该比特串被分为32位的L0和32位的R0两部分。R0子密钥K1经过变换f(R0,K1)(f变换算法见下)输出32位的比特串f1,f1与L0做异或运算。

f1与L0做异或运算后的结果赋给R1,R0则原封不动的赋给L1。L1与R0又做与以上完全相同的运算,生成L2,R2…… 一共经过16次运算。最后生成R16和L16。其中R16为L15与f(R15,K16)做不进位二进制加法运算的结果,L16是R15的直接赋值。

R16与L16合并成64位的比特串。值得注意的是R16一定要排在L16前面。R16与L16合并后成的比特串,经过置换IP-1后所得比特串的下标列表如下:

6.png

经过置换IP-1后生成的比特串就是密文e.。

f 算法
变换f(Ri-1,Ki)的功能是将32比特的输入再转化为32比特的输出。其过程如图所示:

7.png

首先、输入Ri-1(32比特)经过变换E后,膨胀为48比特。膨胀后的比特串的下标列表如下:

8.png

其次、膨胀后的E和Ki异或的结果分为8组,每组6比特。各组经过各自的S盒后,变为4比特,

9.png


S盒的算法为:输入b1,b2,b3,b4,b5,b6,计算x=b1*2+b6,y=b5+b4*2+b3*4+b2*8,再从Si表(见下表)中查出x 行,y 列的值Sxy。将Sxy化为二进制,即得Si盒的输出。

10.png

最后、合并8组S盒输出成为32比特。该32比特经过P变换后,其下标列表如下:

11.png

经过P变换后输出的比特串才是32比特的f (Ri-1,Ki)。

以上介绍了DES算法的加密过程。DES算法的解密过程是一样的,区别仅仅在于第一次迭代时用子密钥K16,第二次K15、......,最后一次用K1,算法本身并没有任何变化。
二、目前使用的DES算法

      对于服务端apache,里面描述的DES加解密,只是最基本、最原始的加解密,而现在很多地方使用的DES都会有一些扩展。

      接来说下我们目前使用的DES的加解密的使用。

      函数des3_set_3keys设置key,如果是3DES,则需要设置3个key,这里说的key其实就是8字节的数组类型的密钥;

而函数des3_encrypt是处理的加密,void des3_encrypt( des3_context *ctx, uint8 input[8], uint8output[8] ),其中的input和output分别表示需要加密的和加密后的数据。这里提供的是8个字节的数据,如果要加密的数据比8个字节要长,则需要循环使用这个加密函数;而输出output,每次调用des3_encrypt,输出都是8位。比如说,需要加密的数据是个10字节长的数,那么加完密之后则是16位。

三、DES算法的两种模式

上面描述的只是是最基本的DES加密,而通常外界使用的DES很多都有模式以及填充方式的设置,如果设置不一样,将会导致一些接口信息不一致,加解密的数据就对不上。

比较常用的模式有:cbc和ecb。

这里主要介绍DES算法的数据补位问题、DES算法的两种模式ECB和CBC问题,以及更加安全的算法3DES。

1、数据补位

DES数据加解密就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,按照需求补足8个字节(通常补00或者FF,根据实际要求不同)进行计算,之后按照顺序将计算所得的数据连在一起即可。

很多地方默认的补位方式是以PKCS7补位的,如果C#默认的就是PKCS7补位:补位补到8位的整数倍,差几位补几。

这里有个问题就是为什么要进行数据补位?主要原因是DES算法加解密时要求数据必须为8个字节。

2、ECB模式

DES ECB(电子密本方式)其实非常简单,就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,按照需求补足8个字节进行计算,之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。

3、CBC模式
DES CBC(密文分组链接方式)有点麻烦,它的实现机制使加密的各段数据之间有了联系。其实现的机理如下:

加密步骤如下:

1)首先将数据按照8个字节一组进行分组得到D1D2......Dn(若数据不是8的整数倍,用指定的PADDING数据补位)

2)第一组数据D1与初始化向量I异或后的结果进行DES加密得到第一组密文C1(初始化向量I为全零)

3)第二组数据D2与第一组的加密结果C1异或以后的结果进行DES加密,得到第二组密文C2

4)之后的数据以此类推,得到Cn

5)按顺序连为C1C2C3......Cn即为加密结果。

解密是加密的逆过程,步骤如下:

1)首先将数据按照8个字节一组进行分组得到C1C2C3......Cn

2)将第一组数据进行解密后与初始化向量I进行异或得到第一组明文D1(注意:一定是先解密再异或)

3)将第二组数据C2进行解密后与第一组密文数据进行异或得到第二组数据D2

4)之后依此类推,得到Dn

5)按顺序连为D1D2D3......Dn即为解密结果。

这里注意一点,解密的结果并不一定是我们原来的加密数据,可能还含有你补得位,一定要把补位去掉才是你的原来的数据。

4、3DES 算法

3DES算法顾名思义就是3次DES算法,其算法原理如下:

设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,P代表明文,C代表密表,这样,
      3DES加密过程为:C=Ek3(Dk2(Ek1(P)))
      3DES解密过程为:P=Dk1((EK2(Dk3(C)))

这里可以K1=K3,但不能K1=K2=K3(如果相等的话就成了DES算法了)

3DES with 2 diffrent keys(K1=K3),可以是3DES-CBC,也可以是3DES-ECB,3DES-CBC整个算法的流程和DES-CBC一样,但是在原来的加密或者解密处增加了异或运算的步骤,使用的密钥是16字节长度的密钥,将密钥分成左8字节和右8字节的两部分,即k1=左8字节,k2=右8字节,然后进行加密运算和解密运算。

3DES with 3 different keys,和3DES-CBC的流程完全一样,只是使用的密钥是24字节的,但在每个加密解密加密时候用的密钥不一样,将密钥分为3段8字节的密钥分别为密钥1、密钥2、密钥3,在3DES加密时对加密解密加密依次使用密钥1、密钥2、密钥3,在3DES解密时对解密加密解密依次使用密钥3、密钥2、密钥1。

四、DES两种模式的算法实现
下面提供几个代码例子,来描述DES算法两种模式的实现,均以PKCS7来进行补位(代码仅做参考)。

1.ECB模式以PKCS7方式填充的DES加密
int des_ecb_pkcs7_encrypt(uchar* from, int nLength,  uchar * to, uchar key[])
file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image023.png{   
int nSize = nLength % 8 ?(nLength + 7) / 8 * 8 : nLength + 8;
if(to == NULL)
    file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image024.png{
        //计算长度
        return nSize;
    }
    else
    file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image025.png{
        deskey(key,EN0);
        uchar endBuf[8];   
        int i=0;
        for(; i < nSize; i+=8)
        file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image026.png{
            uchar* ps = NULL,* pd = NULL;
            if(nLength - i >= 8)
            file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image027.png{
                ps = from + i;
                pd = to + i;
            }
            else
            file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image028.png{
                memset(&endBuf, i + 8 - nLength, sizeof(endBuf));
                memcpy(&endBuf,from + i,nLength - i);
                ps = endBuf;
                pd = to + i;
            }
            des(ps,pd);
        }
        return i;
}
}

2.ECB模式以PKCS7方式填充的DES解密
int des_ecb_pkcs7_decrypt(uchar* from,int nLength,  uchar * to, uchar key[])
{
   if(nLength % 8)
       return 0;    //数据不正确
   deskey(key,DE1);
   int i = 0;
   for(; i < nLength; i+=8)
   {
       if(nLength - i > 8)
       {
            des(from + i,to + i);
       }
       else
       {
            uchar endBuf[8];
            des(from + i,endBuf);
            //去除数据尾
            uchar chEnd = endBuf[7];
            if(chEnd > 0 && chEnd< 9)
            {
                //有可能是填充字符,去除掉
                for(int j = 7; j >= 8 -chEnd; --j)
                {
                    if(endBuf[j] != chEnd)
                        return 0;
               }
                memcpy(to + i, endBuf, 8 -chEnd);
               
                return i +  8 - chEnd;
            }
            else
            {
                return 0;
            }
       }
   }
   return 0;
}
3.CBC模式以PKCS7方式填充的DES加密
      其中iv就是上面提到的初始化向量。
intdes_cbc_pkcs7_encrypt(uchar* from, int nLength, uchar * to, uchar key[],uchar iv[])
{
    //uchar buffer[8];
    int nSize = nLength % 8 ?(nLength + 7) / 8* 8 : nLength + 8;
    if(to == NULL)
    {
        //计算长度
        return nSize;
    }
    else
    {
        deskey(key,EN0);
        uchar preEnc[8];
        memcpy(preEnc,iv,8);
        
        //加密块
        int i=0;
        for(; i < nSize; i+=8)
        {
            uchar*     ps = from + i;
            uchar*     pd = to + i;
            if(nSize - i > 8)
            {
                //XOR
                for(int j = 0; j < 8; ++j)
                {
                    preEnc[j] ^= *(ps + j);
                }
            }
            else
            {
                //XOR
                for(int j = 0; j < nLength -i; ++j)
                {
                    preEnc[j] ^= *(ps + j);
                }
                for(int j = nLength - i; j <8; ++j)
                {
                    preEnc[j] ^= nSize - nLength;
                }
            }
            des(preEnc,pd);
            //保存前一个输出
            memcpy(preEnc, pd,8);
        }
        return i;
    }
}
4.CBC模式以PKCS7方式填充的DES解密
intdes_cbc_pkcs7_decrypt(uchar* from, int nLength, uchar * to, uchar key[], uchar iv[])
{
    if(nLength % 8)
        return 0;    //数据不正确
    //uchar* toBackup = to;
    //to = (uchar*)malloc(nLength);    //申请内存
    //XOR
    uchar preEnc[8],buffer[8];
    memcpy(preEnc,iv,8);
    deskey(key,DE1);
    int i = 0;
    for(;i < nLength; i+=8)
    {
        uchar*     ps = from + i;
        uchar*     pd = to + i;
        des(ps,buffer);
        //XOR
        for(int j = 0; j < 8; ++j)
        {
            buffer[j] ^= preEnc[j];
        }
        if(nLength - i > 8)
        {
            //保存前一个输出
            memcpy(preEnc, ps,8);
            memcpy(pd,buffer,sizeof(buffer));
        }
        else
        {
            //去除数据尾
            uchar chEnd = buffer[sizeof(buffer)- 1];
            if(chEnd > 0 && chEnd <9)
            {
                //有可能是填充字符,去除掉
                for(int j = sizeof(buffer) - 1;j >= (int)(sizeof(buffer) - chEnd); --j)
                {
                    if(buffer[j] != chEnd)
                        return 0;
                }
                int nSize =nLength - chEnd;
                memcpy(pd, buffer,sizeof(buffer) - chEnd);
               
                return nLength - chEnd;
            }
            else
            {
                //数据格式不正确
                return 0;
            }
            
        }
    }
    return 0;
}

打赏鼓励作者,期待更多好文!

打赏
暂无人打赏

Sangfor_闪电回_朱丽 发表于 2016-4-5 20:08
  
感谢分享!
中国是我家 发表于 2019-5-4 22:56
  
求助大神解答下,啊
新手085652 发表于 2019-5-7 10:27
  
一般的问题都能在这里解决。
发表新帖
热门标签
全部标签>
每日一问
技术盲盒
技术笔记
每周精选
干货满满
技术咨询
信服课堂视频
标准化排查
秒懂零信任
自助服务平台操作指引
新版本体验
产品连连看
安装部署配置
功能体验
GIF动图学习
玩转零信任
2023技术争霸赛专题
技术晨报
安全攻防
每日一记
深信服技术支持平台
天逸直播
华北区交付直播
社区帮助指南
畅聊IT
答题自测
专家问答
技术圆桌
在线直播
MVP
网络基础知识
升级
上网策略
测试报告
日志审计
问题分析处理
流量管理
运维工具
云计算知识
用户认证
原创分享
解决方案
sangfor周刊
VPN 对接
项目案例
SANGFOR资讯
专家分享
技术顾问
信服故事
SDP百科
功能咨询
终端接入
授权
设备维护
资源访问
地址转换
虚拟机
存储
迁移
加速技术
排障笔记本
产品预警公告
信服圈儿
S豆商城资讯
技术争霸赛
「智能机器人」
追光者计划
答题榜单公布
纪元平台
通用技术
卧龙计划
华北区拉练
以战代练
山东区技术晨报
文档捉虫活动
齐鲁TV

本版达人

新手68983...

本周分享达人

零和一网络

本周提问达人