分组密码实验

分组密码实验

写在前言:这个实验使用C语言实现了DES算法,基于DES算法实现了3DES算法,并在此基础上,实现了五种分组密码工作模式:ECB、CBC、CFB、OFB、CTR,运行的环境是Linux,这个代码在Windows系统跑会出现Bug,因为Windows的long类型是4个字节,而Linux的long是8字节,编译的时候会有一些Warnings。

实验说明

分组加密实验要求我们使用3DES加解密文件,理论上任何文件都可以加解密,因为文件的本质就是二进制序列,DES是基于Feistel密码结构体系的一个具体实现方案,它是一个很经典的加密算法,任何加密算法都是公开的,DES也不例外,这里有一个DES算法的一个具体例子,可以帮助理解DES的操作:🔗DES Example

Feistel密码结构

如下图所示,左半部分为Feistel为加密,右半部分为解密,实际上,Feistel密码结构有16轮的迭代过程,每一轮迭代具有相同的操作:

  • 先把数据分为大小相同的左右两部分L、R;
  • 把右半部分直接复制到下一轮的左半部分;
  • 右半部分R进入一个轮函数F,这个函数还有一个参数,就是轮密钥K;
  • 第三步输出的结果与左半部分L进行异或操作就得到了下一轮迭代的右半部分。
  • 16轮迭代结束后交换左右两部分,得到最终结果。

Feistel

加密的迭代过程的伪代码如下:

# Encrypt
for i=1 to 16
	LE[i]=RE[i-1]
	RE[i]=LE[i-1] xor F(RE[i-1], K[i])

LE[17]=RE[16]
RE[17]=LE[16]

# Decrypt
LD[0]=RE[16]
RD[0]=LE[16]
for i=1 to 16
	LD[i]=RD[i-1]
	RD[i]=LD[i-1] xor F(RD[i-1], K[16-i+1])

加密过程和解密过程是一一对应的,加密的最后左右互换,解密的开始左右互换,加密的过程的密钥使用顺序与解密时相反,值得注意的是,Feistel密码结构中的F函数,不要求是可逆函数,因为加解密过程中,没有用到其逆函数。

Feistel密码结构式许多密码算法采用的基础结构,其密码设计要素如下:

  1. 分组大小:分组越大,安全性越高,加解密速率越慢。
  2. 密钥大小:密钥越长,安全性越高,加解密速率也许会减慢。
  3. 迭代次数:多轮迭代次数能提供更高的安全性。
  4. 子密钥产生算法:算法复杂度越高,密码破译难度越高。
  5. 轮函数:越高的复杂度意味着对破译阻力越大。
  6. 快速软件加密/解密:容易嵌入到现有的应用程序或者工具中。
  7. 容易分析:容易分析该算法的弱点并给出强度更高的保障。

DES算法

DES(Data Encryption Standard),数据加密标准,是最广泛使用的对称加密方案,明文分组长度为64 bit,密钥有效长度为56 bit,在基于Feistel体系结构基础上,采用16轮迭代,由原始56 bit密钥产生16组子密钥,每一轮迭代使用一个子密钥。

下面介绍DES的三个操作:

  1. 初始(逆初始)置换
  2. 轮函数F的操作
  3. 子密钥产生算法

下图是DES算法框架

des

初始(逆初始)置换

初始(逆初始)置换:置换操作,就是通过一张置换表将原始的数据进行换位操作。而DES的加解密过程开始都要经过初始置换,最终进行逆初始置换。若对同一个64-bit依次进行置换、逆置换那么得到的结果与原始数据一致。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIygjd0l-1650028820073)(.assets/image-20220415195602549.png)]

轮函数F

下图是轮函数F的框架结构,轮函数接受一个32 bit的数据以及一个48 bit的子密钥,操作有4个:

  1. 扩展置换
  2. 轮密钥加
  3. 压缩置换
  4. 直接置换

permutation

扩展置换

由于子密钥是48 bit,轮密钥加操作需要两个数据的长度一致,才可以进行,因此,这一步使用一个扩展置换表将一个32 bit的数据扩展成48 bit的数据,下图为扩展置换的具体操作,实际上,它将32 bit的数据分成8块,每块4 bit,在每一块之间插入两个bit,是很有规律的,某一块扩展之后,它的第一个bit一定是前一块的最后一个bit,它的最后一个bit一定是后一块的第一个bit。

expand

轮密钥加

轮密钥加很简单,就是将扩展置换得到的48 bit数据直接和轮密钥进行简单的异或操作。

压缩置换

压缩置换是DES所有操作中唯一一个非线性的操作,并且它是不可逆的,压缩置换将48 bit的数据最终压缩成32 bit的数据,具体的操作就是将这48 bit的数据分成8个块,每一个块6 bit,然后通过这6 bit的数据,计算出一个坐标值,从对应的S盒压缩置换表中获取相应的4 bit数据,每一块都进行相同的操作,最终就可以得到32 bit的压缩值。

下面给出一个压缩置换的一个例子:

在这里插入图片描述

上图是一个压缩置换表,4行16列,表中的值的范围是[0, 15]共16种值,每一种值出现的概率相同,即在表中每一种值出现4次,不同的压缩置换表具有相同的属性,只不过位置不一样。

将6 bit数据以二进制形式表为 b 1 b 2 b 3 b 4 b 5 b 6 b_1b_2b_3b_4b_5b_6 b1b2b3b4b5b6,那么这个数据对应置换表中的坐标值为 ( b 1 b 6 , b 2 b 3 b 4 b 5 ) (b_1b_6,b_2b_3b_4b_5) (b1b6,b2b3b4b5),比如将6 bit二进制值 011001 011001 011001通过上面的压缩置换表进行压缩,首先计算其坐标值为: ( 1 , 12 ) (1, 12) (1,12),对应压缩值为0,因此压缩结果为: 0000 0000 0000

直接置换

直接置换很简单,就是通过一个32 bit的直接置换表,最终置换结果也为32 bit

子密钥生成算法

下图所示,为DES子密钥生成算法的图解,

在这里插入图片描述

首先64 bit的密钥经过一个置换选择PC1,变为56 bit,实际上,这个置换去掉了第8、16、24、32、40、48、56、64比特位,这就是为什么DES算法的有效密钥长度才为56 bit。

在这里插入图片描述

之后每一轮的处理根据一个子密钥循环移位表,进行循环移位,注意每一轮循环移位的对象不是整个56 bit数据,而是分别对左右28 bit数据的进行循环左移,然后将循环左移的结果合并得到一个56 bit数据,再通过一个置换选择PC2得到56 bit的子密钥,迭代16次就得到了DES的16轮迭代子密钥。

在这里插入图片描述

3DES算法

3DES,也叫三重DES,相比DES,它的安全性更强,因为它的密钥空间扩大了很多,从DES的 2 56 2^{56} 256,扩展到了 2 168 2^{168} 2168,使得穷举搜索变得不切实际,3DES的出现是因为计算机的计算速度越来越快,导致DES无法承受穷举搜索攻击,此时3DES就被提出来了,实际上,任何对称加密算法都可以进行多重的加解密操作,使得它更安全,但是这样做会使得加解密的效率降低,3DES的加解密做了3次,才完成64bit的加解密操作,相同的时间DES可以加解密192 bit的数据。

3DES算法有四种模式:

  1. EDE3
    • 加密过程: C = E K 3 ( D K 2 ( E K 1 ( M ) ) ) C=E_{K_3}(D_{K_2}(E_{K_1}(M))) C=EK3(DK2(EK1(M)))
    • 解密过程: M = D K 1 ( E K 2 ( D K 3 ( C ) ) ) M=D_{K_1}(E_{K_2}(D_{K_3}(C))) M=DK1(EK2(DK3(C)))
  2. EEE3
    • 加密过程: C = E K 3 ( E K 2 ( E K 1 ( M ) ) ) C=E_{K_3}(E_{K_2}(E_{K_1}(M))) C=EK3(EK2(EK1(M)))
    • 解密过程: M = D K 1 ( D K 2 ( D K 3 ( C ) ) ) M=D_{K_1}(D_{K_2}(D_{K_3}(C))) M=DK1(DK2(DK3(C)))
  3. EDE2
    • 加密过程: C = E K 1 ( D K 2 ( E K 1 ( M ) ) ) C=E_{K_1}(D_{K_2}(E_{K_1}(M))) C=EK1(DK2(EK1(M)))
    • 解密过程: M = D K 1 ( E K 2 ( D K 1 ( C ) ) ) M=D_{K_1}(E_{K_2}(D_{K_1}(C))) M=DK1(EK2(DK1(C)))
  4. EEE2
    • 加密过程: C = E K 1 ( E K 2 ( E K 1 ( M ) ) ) C=E_{K_1}(E_{K_2}(E_{K_1}(M))) C=EK1(EK2(EK1(M)))
    • 解密过程: M = D K 1 ( D K 2 ( D K 1 ( C ) ) ) M=D_{K_1}(D_{K_2}(D_{K_1}(C))) M=DK1(DK2(DK1(C)))

在实际的应用中,EDE2模式应用最广泛,因为它兼容DES,当 K 1 = K 2 K_1=K_2 K1=K2时,EDE2模式退化成DES。

分组密码工作模式

分组密码工作模式共有五种:

  1. 电子密码本模式(ECB)
  2. 密码分组链接模式(CBC)
  3. 密码反馈模式(CFB)
  4. 输出反馈模式(OFB)
  5. 计数器模式(CTR)

能被任意分组密码使用。

电子密码本模式

消息被独立分成分组进行加密,每一个分组是一个值,将会被替换,就像一个密码本一样,因此叫做电子密码本模式,每一个分组都是独立其它分组进行编码。

在这里插入图片描述

在这里插入图片描述

64-bit明文分组重复是在密文中也会重复出现,特别是处理高度结构化的消息,很可能被破译;主要是因为这些加密的消息是独立处理而造成的,在发送少量消息分组时可用。

密码分组链接模式

消息被分成多个分组,在加密操作时每一个明文分组与前面密文分组相链接,使得同一明文分组将会产生不同的密文分组。

在这里插入图片描述

在这里插入图片描述

密码反馈模式

密码反馈模式能将任意分组密码转化成流密码,从而不需要对消息进行填充为分组的整数倍,还可以实时操作,充分利用传输信道。

在这里插入图片描述

在这里插入图片描述

输出反馈模式

输出反馈模式类似于密码反馈模式,主要区别在于反馈的内容识加密器输出的随机数,而不是密文,因此不具有错误传播的特性。

在这里插入图片描述
在这里插入图片描述

计数器模式

计数器模式在ATM(异步传输模式)网络安全和IPSEC早有应用。类似于密码反馈模式,但不是加密反馈值,而是加密每个计数值,然后再对铭文分组进行异或操作,每个明文分组必须有不同的密钥和计数值,而且从不重复使用。

在这里插入图片描述

在这里插入图片描述

DES算法实现

由于DES的算法实现使用的是64 bit的long类型,在对文件进行加解密时,往往只能读取和写入字节流,所以需要编写64 bitlong和字节数组之间的转换函数:

util.h

#ifndef __UTIL_H__
#define __UTIL_H__

/* Convert string to long (8 bytes) */
long charstol(char* s);

/* Convert long to string (9 bytes) */
char* ltochars(long l);

/* Convert long to 8 bytes */
char* ltobytes(long l);


#endif

util.c:实际文件的加解密只用到了charstolltobytes函数,其中ltochars函数是用来debug用的,它可以得到为一个长度为8的字符串。

#include "util.h"
#include <stdlib.h>
#include <stdio.h>
/* Only use in Extract key, plaintext and ciphertext */
long charstol(char* s)
{
    int i;
    int len = 8;
    long d;
    long res = 0;
    for (i = 0; i < len; i++) {
        d = (long)s[i] & 0xff;
        res |= (d << (i * 8));
    }
    return res;
}

/* long to chars(outputstring with \0) */
char* ltochars(long l)
{
    int i;
    int len = 8;
    char* s = (char*)malloc((len + 1) * sizeof(char));

    for (i = 0; i < len; i++) {
        s[i] = (l >> (8 * i)) & 0xff;
    }

    s[len] = '\0';
    return s;
}

char* ltobytes(long l)
{
    int i;
    int len = 8;
    char* s = (char*)malloc(len * sizeof(char));

    for (i = 0; i < len; i++) {
        s[i] = (l >> (8 * i)) & 0xff;
    }

    return s;
}

接着,定义一些常量表、宏定义以及DES的函数:

#ifndef __DES_H__
#define __DES_H__

/**
 * @brief Des.h, defines Des tables
 * and some basic functions.
 *
 */
/* 1<= pos <= 64*/
#define SET(val, pos)   (val |= ((long)1 << (pos - 1)))
#define GET(val, pos)   ((val >> (pos - 1)) & 0x1)


 /* Des Block Size- 64 bits */
#define BLOCK_SIZE 64
#define HALF_BLOCK_SIZE BLOCK_SIZE/2

/* Des Cipher key size- 56 bits*/
#define CIPHER_KEY_SIZE 56
#define ROUND_KEY_SIZE 48
#define S_BOX_SIZE 8

/* Des Iteration times */
#define ITERATION_TIMES 16


/* Initial Permutation Table */
static int IP[BLOCK_SIZE] = {
    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
};

/* Inverse Initial Permutation Table */
static int INV_IP[BLOCK_SIZE] = {
    40, 8, 48, 16, 56, 24, 64, 32,
    39, 7, 47, 15, 55, 23, 63, 31,
    38, 6, 46, 14, 54, 22, 62, 30,
    37, 5, 45, 13, 53, 21, 61, 29,
    36, 4, 44, 12, 52, 20, 60, 28,
    35, 3, 43, 11, 51, 19, 59, 27,
    34, 2, 42, 10, 50, 18, 58, 26,
    33, 1, 41, 9, 49, 17, 57, 25
};

/* Expansive Permutation Table- 32 bits -> 48 bits*/
static int EP[ROUND_KEY_SIZE] = {
    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
};

/* Straight Permutation- 48 bits -> 32 bits */
static int P[HALF_BLOCK_SIZE] = {
    16, 7, 20, 21, 29, 12, 28, 17,
    1, 15, 23, 26, 5, 18, 31, 10,
    2, 8, 24, 14, 32, 27, 3, 9,
    19, 13, 30, 6, 22, 11, 4, 25
};

/* Des  S box- each box: 6 bits -> 4 bits */
static int S[S_BOX_SIZE][BLOCK_SIZE] = {
    /* Box-1 */
    {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
    0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
    4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
    15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13},

    /* Box-2 */
    {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
    3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
    0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
    13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9},

    /* Box-3 */
    {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
    13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
    13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
    1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12},

    /* Box-4 */
    {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
    13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
    10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
    3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14},

    /* Box-5 */
    {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 19, 14, 9,
    14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
    4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},

    /* Box-6 */
    {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
    10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
    9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
    4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13},

    /* Box-7 */
    {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
    13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
    1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
    6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12},

    /* Box-8 */
    {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
    1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
    7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
    2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
};

/* Permutation Choice- 1*/
static int PC_1[CIPHER_KEY_SIZE] = {
    57, 49, 41, 33, 25, 17, 9,
    1, 58, 50, 42, 34, 26, 18,
    10, 2, 59, 51, 43, 35, 27,
    19, 11, 3, 60, 52, 44, 36,
    63, 55, 47, 39, 31, 23, 15,
    7, 62, 54, 46, 38, 30, 22,
    14, 6, 61, 53, 45, 37, 29,
    21, 13, 5, 28, 20, 12, 4,
};

/* Permutation Choice- 2*/
static int PC_2[ROUND_KEY_SIZE] = {
    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
};

/* Rotate Left size table (bits) */
static int RL[ITERATION_TIMES] = {
    1, 1, 2, 2, 2, 2, 2, 2,
    1, 2, 2, 2, 2, 2, 2, 1
};

/* Generate 16 sub keys by key. */
long* generateKey(long key);

/* Encrypt 64 bits plaintext. */
long encryptdes(long plaintext, long* keys);

/* Decrypt 64 bits ciphertext. */
long decryptdes(long ciphertext, long* keys);


/*
 * Round function- 32bits->48bits->32bits
 * expansive permuatation(EP Box) ->
 * round key add ->
 * substitue(S Box) ->
 * permutate(P Box)
 */
static long f(long r, long subkey);


#endif

下面是des.c:轮函数f、子密钥生成函数generateKey、加解密函数encryptdes、decryptdes。

#include "des.h"
#include <stdlib.h>
#include <stdio.h>
/* Extract a number's left or right part, Only Used in Round function */
#define GET_RIGHT32(val) ((val >> 32) & 0xffffffff)
#define GET_LEFT32(val) (val & 0xffffffff)


/* Only use in generateKey. , keys*/
#define ROTATE(val, k)      (val = ((1 << 28) - 1) & ((val << k) | (((val << k) & ((1 << k) - 1) << 28) >> 28)))
#define COMBINE(val, a, b, size)  (val = ((a) | ((long)b << size)))
#define GET_RIGHT28(val)	((val >> 28) & 0xfffffff)
#define GET_LEFT28(val)	    (val & 0xfffffff)

/**
 * @brief Generate 16 keys by key.
 *
 * @param key cipher key
 * @return long* sub keys array
 */
long* generateKey(long key)
{
    int i, j;
    int pos;
    long pkey = 0;
    long subkey;
    int l, r;
    long* keys = (long*)malloc(ITERATION_TIMES * sizeof(long));

    /* PC_1 - Check */
    for (i = 0; i < CIPHER_KEY_SIZE; i++) {
        if (GET(key, PC_1[i])) {
            SET(pkey, (i+1));
        }
    }
	
    /* Extract left and right 28 bits of pkey.- Check  */
    l = GET_LEFT28(pkey);
    r = GET_RIGHT28(pkey);
	
    /* Generate 16 sub keys */
    for (i = 0; i < ITERATION_TIMES; i++) {
        /* Rotate left- Check */
        ROTATE(l, RL[i]);
        ROTATE(r, RL[i]);
        /* Combine into 56 bits sub key */
        COMBINE(pkey, l, r, 28);
        subkey = 0;
        /* PC_2- Check */
        for (j = 0; j < ROUND_KEY_SIZE; j++) {

            if (GET(pkey, PC_2[j])) {
                SET(subkey, (j+1));
            }
        }
        keys[i] = subkey;
    }

    return keys;
}

/**
 * @brief Des Round function
 *
 * @param r the right 28 bits
 * @param subkey Des 48 bits sub key
 * @param iter Iteration time, to determine use which S Box.
 * @return int f result
 */
long f(long r, long subkey) {
    int i;
    long tmp;
    long res;
    /* Use in Substitue */
    int num;
    int row, col;

    tmp = 0;
    /* Expansive Permutation- Check */
    for (i = 0; i < ROUND_KEY_SIZE; i++) {
        if (GET(r, EP[i])) {
            SET(tmp, (i+1));
        }
    }
    /* Round key add- Check */
    res = tmp ^ subkey;

    tmp = 0;
    /* Substitute-Check */
    for (i = 0; i < 8; i++) {
        /* Extract 6 bits- Check */
        num = (res >> (i * 6)) & 0x3f;
        /* Get row and col in S Box- Check */
        col = (num >> 1) & 0xf;
        row = ((num >> 4) & 0x2) | (num & 0x1);

        /* Combine each 4 bits- Check */
        tmp |= (S[i][row*16+col] << (i * 4));


    }

    res = 0;
    /* Permutation- Check */
        for (i = 0; i < HALF_BLOCK_SIZE; i++) {
            if (GET(tmp, P[i])) {
                SET(res, (i+1));
            }
        }

    return res;
}

/**
 * @brief Encrypt 64 bits data
 *
 * @param plaintext plaintext 64 bits
 * @param keys 16 subkeys (each 48bits)
 * @return long ciphertext 64 bits
 */
long encryptdes(long plaintext, long* keys)
{
    int i, j;
    long l, r, tmp;
    long ptext;
    long ciphertext;

    ptext = 0;
    /* Initial Permutation- Check */
    for (i = 0; i < BLOCK_SIZE; i++) {
        if (GET(plaintext, IP[i])) {
            SET(ptext, (i+1));
        }
    }

    /* Extract left and right 32 bits- Check */
    l = GET_LEFT32(ptext);
    r = GET_RIGHT32(ptext);

    /* Iterate 16 times- Check */
    for (i = 0; i < ITERATION_TIMES; i++) {
        tmp = r;
        r = l ^ f(r, keys[i]);
        l = tmp;
    }

    COMBINE(ptext, l, r, 32);
    ciphertext = 0;
    /* Inverse Initial Permutation */
    for (i = 0; i < BLOCK_SIZE; i++) {
        if (GET(ptext, INV_IP[i])) {
            SET(ciphertext, (i+1));
        }
    }
    /* free memory */
    //free(keys);

    return ciphertext;
}

long decryptdes(long ciphertext, long* keys)
{
    int i, j;
    long l, r, tmp;
    long ptext;
    long plaintext;

    ptext = 0;
    /* Initial Permutation */
    for (i = 0; i < BLOCK_SIZE; i++) {
        if (GET(ciphertext, IP[i])) {
            SET(ptext, (i+1));
        }
    }
    l = GET_LEFT32(ptext);
    r = GET_RIGHT32(ptext);

    /* Iterate 16 times */
    for (i = 0; i < ITERATION_TIMES; i++) {
        tmp = l;
        l = r ^ f(l, keys[ITERATION_TIMES - i - 1]);
        r = tmp;
    }

    COMBINE(ptext, l, r, 32);
    plaintext = 0;
    /* Inverse Initial Permutation */
    for (i = 0; i < BLOCK_SIZE; i++) {
        if (GET(ptext, INV_IP[i])) {
            SET(plaintext, (i+1));
        }
    }
    return plaintext;
}

3DES算法实现

首先是定义3DES的4中模式:

#ifndef __TDES_H__
#define __TDES_H__

#include "des.h"

typedef enum
{
    EDE2, EDE3, EEE2, EEE3
} TripleDesMode;

/* Encrypt plaintext by 3DES */
long encrypt3des(long plaintext, long** keys, TripleDesMode mode);

/* Decrypt plaintext by 3DES */
long decrypt3des(long ciphertext, long** keys, TripleDesMode mode);

#endif

tdes.c:根据mode判断执行哪一种3DES模式,3DES很简单,就是基于DES的3重加解密。

#include "tdes.h"

/**
 * @brief 3DES
 * E(K1, D(K2, E(K1, M)))
 * 
 */

/**
 * @brief Encrypt plaintext by 3DES
 * 
 * @param plaintext plaintext
 * @param keys keys
 * @param mode 3DES Mode
 * @return long ciphertext
 */
long encrypt3des(long plaintext, long** keys, TripleDesMode mode)
{
    if (mode == EDE2) {
        return encryptdes(decryptdes(encryptdes(plaintext, keys[0]), keys[1]), keys[0]);
    } else if (mode == EDE3) {
        return encryptdes(decryptdes(encryptdes(plaintext, keys[0]), keys[1]), keys[2]);
    } else if (mode == EEE2) {
        return encryptdes(encryptdes(encryptdes(plaintext, keys[0]), keys[1]), keys[0]);
    } else {
        /* EEE3_MODE */
        return encryptdes(encryptdes(encryptdes(plaintext, keys[0]), keys[1]), keys[2]);
    }

}

/**
 * @brief decrypt ciphertext by 3DES
 * 
 * @param ciphertext plaintext
 * @param key1 keys
 * @param key2 
 * @return long 
 */
long decrypt3des(long ciphertext, long** keys, TripleDesMode mode)
{
    if (mode == EDE2) {
        return decryptdes(encryptdes(decryptdes(ciphertext, keys[0]), keys[1]), keys[0]);
    } else if (mode == EDE3) {
        return decryptdes(encryptdes(decryptdes(ciphertext, keys[2]), keys[1]), keys[0]);
    } else if (mode == EEE2) {
        return decryptdes(decryptdes(decryptdes(ciphertext, keys[0]), keys[1]), keys[0]);
    } else {
        /* EEE3_MODE */
        return decryptdes(decryptdes(decryptdes(ciphertext, keys[2]), keys[1]), keys[0]);
    }
}

分组密码工作模式的实现

blockcipher.h:定义五种分组密码工作模式的加解密函数,以及五种工作模式类型BlockCipherMode,以及定义一个初始向量值IV=0x123456789abcdef0,这个初始向量的值是可以改变的。

#ifndef __BLOCKCIPHER_H__
#define __BLOCKCIPHER_H__

#include <stdio.h>
#include "tdes.h"

/**
 * @brief Block Cipher Working Mode:
 * ECB: Electronic CodeBook Mode (Basic)
 * CBC: Code Block cipher
 * CFB: Code FeedBack
 * OFB: Ouput FeeBack
 * CTR: Counter Mode
 */


/* Define some */

typedef enum
{
    ECB, CBC, CFB, OFB, CTR
} BlockCipherMode;

/* Initial Vector, 64 bits */
#define IV 0x123456789abcdef0


/* Encrypt file by 3DES using block cipher mode- ECB/CBC/CFB/OFB/CTR */
void encrypt(FILE* src, FILE* dst, BlockCipherMode bcmode, long** keys, TripleDesMode tripleDesMode);

/* Decrypt file by 3DES using block cipher mode- ECB/CBC/CFB/OFB/CTR */
void decrypt(FILE* src, FILE* dst, BlockCipherMode bcmode,  long** keys, TripleDesMode tripleDesMode);


/* Encrypt file in ECB Mode(Basic) */
void ecbEncrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Decrypt file in ECB Mode(Basic) */
void ecbDecrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Encrypt file in CBC Mode */
void cbcEncrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Decrypt file in CBC Mode */
void cbcDecrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);


/* Encrypt file in CFB Mode */
void cfbEncrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Decrypt file in CFB Mode */
void cfbDecrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Encrypt file in OFB Mode */
void ofbEncrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Decrypt file in OFB Mode */
void ofbDecrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);


/* Encrypt file in CTR Mode */
void ctrEncrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);

/* Decrypt file in CTR Mode */
void ctrDecrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode);


#endif

接下来是blockcipher.c,由于这个文件的代码量比较大,并且实际上每一种工作模式的某些操作都是类似的,因此下面只展示ECB的加解密实现:

#include "util.h"
#include "blockcipher.h"
#include <stdlib.h>
#include <math.h>

/* Encrypt in Block Cipher Mode */
void encrypt(FILE* src, FILE* dst, BlockCipherMode bcmode, long** keys, TripleDesMode tripleDesMode)
{
    switch (bcmode)
    {
    case CBC:
        cbcEncrypt(src, dst, keys, tripleDesMode);
        break;
    case CFB:
        cfbEncrypt(src, dst, keys, tripleDesMode);
        break;
    case OFB:
        ofbEncrypt(src, dst, keys, tripleDesMode);
        break;
    case CTR:
        ctrEncrypt(src, dst, keys, tripleDesMode);
        break;
    default:
        ecbEncrypt(src, dst, keys, tripleDesMode);
        break;
    }
}

/* Decrypt in Block Cipher Mode */
void decrypt(FILE* src, FILE* dst, BlockCipherMode bcmode, long** keys, TripleDesMode tripleDesMode)
{
    switch (bcmode)
    {
    case CBC:
        cbcDecrypt(src, dst, keys, tripleDesMode);
        break;
    case CFB:
        cfbDecrypt(src, dst, keys, tripleDesMode);
        break;
    case OFB:
        ofbDecrypt(src, dst, keys, tripleDesMode);
        break;
    case CTR:
        ctrDecrypt(src, dst, keys, tripleDesMode);
        break;
    default:
        ecbDecrypt(src, dst, keys, tripleDesMode);
        break;
    }
}

/* Encrypt in ECB Mode(Basic) */
void ecbEncrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode)
{
    /* Buffer 8 bytes*/
    char* buf;

    long fsize;
    long ciphertext;
    long plaintext;

    /* Allocate Memory */
    buf = (char *)malloc(sizeof(char)*8);

    // obtain file size:
    fseek(src , 0 , SEEK_END);
    fsize = ftell(src);
    rewind(src);

    /* Print initial file size. */
    fwrite(ltobytes(fsize), 1, 8, dst);

    /* Read and Encrypt 8 bytes data each time. */
    
    while(!feof(src)) {
        fread(buf, 1, 8, src);
        plaintext = charstol(buf);
        /* Encrypt plaintext by 3DES. */
        ciphertext = encrypt3des(plaintext, keys, tripleDesMode);
        /* Convert ciphertext in bytes and write to dst file. */
        fwrite(ltobytes(ciphertext), 1, 8, dst);
    }


    free(buf);
}


void ecbDecrypt(FILE* src, FILE* dst, long** keys, TripleDesMode tripleDesMode)
{
    /* Buffer 8 bytes*/
    char* buf;
    
    int i;
    int count;
    long fsize;
    long lastBlockBytes;
    long ciphertext;
    long plaintext;

    /* Allocate Memory */
    buf = (char *)malloc(sizeof(char)*8);

    /* Get Decyrpted(initial) file size. and block amounts. */
    fread(buf, 1, 8, src);
    fsize = charstol(buf);
    lastBlockBytes = fsize - 8*(fsize/8);
    count = lastBlockBytes == 0 ? (fsize/8) : (fsize/8 + 1);

    i = 0;
    /* Read and Decrypt 8 bytes data each time. */
    fread(buf, 1, 8, src);
    while(!feof(src)) {
        ciphertext = charstol(buf);
        /* Encrypt plaintext by 3DES. */
        plaintext = decrypt3des(ciphertext, keys, tripleDesMode);
        /* Convert ciphertext in bytes and write to dst file. */
        if (++i == count) {
            /* Reach the last block */
            fwrite(ltobytes(plaintext), 1, lastBlockBytes, dst);
            
        } else {
            fwrite(ltobytes(plaintext), 1, 8, dst);
        }
        
        fread(buf, 1, 8, src);
    }

    free(buf);
}

完整代码

DES的算法实现部分只展示了部分的代码,完整的代码在我的github里,可以点击这个链接🔗获取:DES Code,里面实现了一个最基本的3DES加解密算法,里面还有一个可执行程序des,在linux环境下,下面展示如何使用des这个程序进行加解密操作,由于实现的简便性,加解密都会同时做,如果想要分开,可以修改main.c代码,重新编译。

[struggle@localhost Des]$ ./des -h
Usage:        ./des [-h] [-t] -dm <3DES-Mode> -bm <Block-Cipher-Mode> -k2 <key1> <key2> -k3 <key1> <key2> <key3> -f <srcfile> -ef <encryptfil0e> -df <decryptfile>
Options:
  -h                         Print Usage
  -t                         Print encrypt time
  -dm  <3DES-Mode>           Select 3des mode[EDE2, EDE3, EEE2, EEE3
  -bm  <Block-Cipher-Mode>   Select Block cipher mode[ECB, CBC, CFB, OFB, CTR]
  -k2  <key1> <key2>         input 2 key
  -k3  <key1> <key2> <key3>  input 3 keys
  -f   <srcfile>             input srcfile path
  -ef  <encryptfile>         input encryptfile path
  -df  <decryptfile>         input decryptfile path
[struggle@localhost Des]$ ./des -t -dm EDE2 -bm CTR -k2 shenzhen facebook -f videos.mp4 -ef encrypt.mp4 -df decrypt.mp4
srcfile size: 22249980 bytes.
Encrypt time: 73.269997 s
srcfile size: 22249992 bytes.
Decrypt time: 74.330002 s

上面这个例子是加解密一个20MB大小的一个视频文件,加解密的平均时间为74秒左右,还是比较慢的,如果使用3DES加解密更大的文件就需要大量的时间,此时可以选择一些其它加密算法,如AES,AES的加解密速度比3DES块很多,并且其安全性与3DES相当。

实验总结

DES作为一个经典的分组加密算法,基于Feistel密码结构,但是由于超级计算机的诞生,计算机的算力提升了不止一个档次,DES面对超算的穷举搜索攻击,其安全性变得很差,很容易就被攻破,这时候使用多重加密的方式,于是出现了3DES,它的强度是足够的,密钥可以有92位,甚至168位,它的安全性上升了几十个上百个数量级,就是因为密钥空间变大了很多,但是随之而来的就是加密的效率低的问题。因此,现代加密算法出现了一个新的算法,AES(高级加密标准),它基于字节的运算,编程实现起来更加简单,但是其安全强度却与3DES相当。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值