目录
(5)生成密码本(unsigned char *KeyGen)
密码学实验一、二
随机全排列生成程序极其应用开发
两种基本古典密码设计与实现
一、实验目的
该实验为验证性实验。
通过本实验,使学生对于两种基本的古典密码编码方法(“代替”与“移位”)产生深刻的感性认识,体验清楚二者之间的本质差异,为理解和掌握现代密码的相应知识打下良好基础。
二、实验内容
1. 设计一个周期3的多表代替密码并予以实现,要求:第1个表由密钥字法产生(密钥字自拟),第2个表由洗牌法产生(注意,字母a~z与数字0~25对应,洗牌法即相当于实验一的方法1(n=25)),第三个表由公式法产生(数学公式自拟,注意它须是Z26上的一个一一变换)。
2.设计一个周期5的16-置换移位密码并予以实现,要求:5个16-置换至少有一个是由实验一(n=15)提供的两个方法以外、自行设计的其它方法产生。
三、实验过程
1.概要设计
说明本程序中用到的所有数据结构的定义、主程序的流程以及程序模块之间的层次(调用)关系。可用流程图等图形化工具描述,并辅以对各模块功能的必要说明。
(1)随机全排列生成程序
方法1:读取一个随机文件中n+1字节数据存入d,对0~n的自然排列,从i=n开始,计算j=(d[i-1]+d[i])%i ,并交换P[i]与P[j]的值。
程序设计:
unsigned char *full_array1(int n)
{
int i,j;
char filename[20];
FILE *fp;
static unsigned char d[256],P[256],temp;
start:
printf("\n 请输入随机数据采样文件名:\n");
scanf("%s",filename);
if( (fp=fopen(filename,"rb"))==NULL )
{
printf("没有找到文件:%s\n",filename);
goto start;
}
fread(d,n+1,1,fp);
fclose(fp);
for(i=0;i<=n;i++)
{
P[i] = i ; //形成自然排列
}
for(i=n;i>0;i--)
{
j=(d[i-1]+d[i])%i ;
temp = P[i] ;
P[i] = P[j];
P[j] = temp; //交换
}
return(P);
}
方法2:随机函数生成一串,依次mod n,并入排列,并将重复的删去,直到生成0~n的随机全排列。
程序设计:
unsigned char *full_array2(int n)
{
int m, i, j, k, l=0, flag;
static unsigned char P[256];
start:
printf("\n请输入不小于%d的所用随机数个数:\n", n+1);
scanf("%d",&m);
if(m<=n)
{
printf("\n输入数%d比%d小,须重新输入!\n", m, n+1);
goto start;
}
srand((unsigned)time(NULL));
for(i=0;i<m;i++)
{
P[l++]=(unsigned char)(rand()%(n+1));
for(j=0;j<l-1;j++)
{
if(P[j]==P[l-1])
{
l--;
break;
}
}
}
k=l;
for(i=0;i<=n;i++)
{
flag=0;
for(j=0;j<k;j++)
{
if(P[j]==i)
{
flag=l;
break;
}
}
if(!flag)
{
P[l]=i;
l++;
}
}
return (P);
}
(2)程序逻辑图
主函数(题目一):
(题目二相似)
KeyGen_s函数(生成密码本):
2.详细设计
(1)密钥字法
原理:首先选择一个便于记忆的字母串作为密钥字,然后删去密钥字中重复字母,接下来用余下字母顺序添加至密钥之后,形成全新26字母排序。
举例:以密钥为hello为例,下图为该密钥生成密码本。
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |
h | e | l | o | a | b | c | d | f | g | i | j | k | m | n | p | q | r | s | t | u | v | w | x | y | z |
以密钥为abcabc为例,下图为该密钥生成密码本。
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |
程序设计:
printf("\n请输入密钥字:\n");
gets(KeyWords);
strcat(KeyWords,"abcdefghijklmnopqrstuvwxyz");
k=0;
l=strlen(KeyWords);
ChoiceWords[k]=tolower(KeyWords[0]);
for(i=1;i<l;i++)
{
if(letter_to_digit(KeyWords[i])==-1)
{
continue;
}
ChoiceWords[++k]=tolower(KeyWords[i]);
for(j=0;j<k;j++)
{
if(ChoiceWords[j]==ChoiceWords[k])
{
k--;
break;
}
}
}
for(i=0;i<26;i++)
{
KeyTab[i]=(unsigned char)letter_to_digit(ChoiceWords[i]);
}
(2)洗牌法
原理:就是将写有0~(n-1)的n张纸牌打乱次序后重新排列。打乱次序的方式利用(1)随机全排列生成程序。
程序设计:
p=full_array1(25); //生成随机全排列
for(i=0;i<26;i++)KeyTab[26+i]=p[i]; //密码本27~52(抽牌法)
(3)公式法
原理:利用公式生成密码表。公式为(11*i+3)mod 26
程序设计:
for(i=0;i<26;i++)
KeyTab[52+i]=(unsigned char)((11*i+3)%26);//密码本53~78
(4)自设计随机16全排列
原理:
unsigned char *full_array16_pickout(){
//自己写的创建随机16全排列,抽牌法
int i,j,l;
static unsigned char p[256];
while(l<16){
p[l++]=(unsigned char)(rand()%16);
for(j=0;j<l-1;j++){
if(p[j]==p[l-1]){
l--;
break;
}
}
}
static unsigned char pout[256];
l=15,i=0;
while(l>=0){
j=rand()%16;
pout[i++]=p[j];
p[j]=p[l--];
}
return pout;
}
(5)生成密码本(unsigned char *KeyGen)
原理:该函数生成密码本本质上是一个78长的数组。前26位存储密钥字法生成的字母序列;中间26位存储抽牌法生成的字母序列;最后26位储存公式法生成序列。
以下为周期3替代密码表。
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |
密钥字 | |||||||||||||||||||||||||
抽牌 | |||||||||||||||||||||||||
公式 |
生成五组随机数,构成5-16置换密码表。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
(6)源代码
在编写代码过程中,部分函数参考实验指导书,主函数
题目一:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <unistd.h>
unsigned char *KeyGen_s();
unsigned char *full_array1(int n);
int letter_to_digit(char c);
char digit_to_letter(int n);
void Encrypt_s(unsigned char *key);
int main()
{
unsigned char *key;
key=KeyGen_s();
printf("\n以下为生成密码本:\n");
for(int i=0;i<3;i++){
for(int j=0;j<26;j++){
putchar(digit_to_letter(key[i*26+j]));
printf(" ");
}
printf("\n");
}
Encrypt_s(key);
return 0;
}
int letter_to_digit(char c) //字母——数字
{
int i;
char alphabet[26];
for(int i=0;i<26;i++)alphabet[i]='a'+i;
for(i=0;i<26;i++)
{
if(tolower(c)==alphabet[i])
{
return (i);
}
}
return(-1);
}
char digit_to_letter(int n)//数字-——字母
{
char alphabet[26];
for(int i=0;i<26;i++)alphabet[i]='a'+i;
if(n<0 || n>25)
{
return (0);
}
return (alphabet[n]);
}
unsigned char *KeyGen_s() //用于生成密码本
{
char KeyWords[106];
char ChoiceWords[26];
unsigned char *p;
static unsigned char KeyTab[26*3];//创建密码本
int i, j, k, l;
printf("\n请输入密钥字:\n");
gets(KeyWords);
strcat(KeyWords,"abcdefghijklmnopqrstuvwxyz");
k=0;
l=strlen(KeyWords);
ChoiceWords[k]=tolower(KeyWords[0]);
for(i=1;i<l;i++)
//密钥字法后续删除重复字母
{
if(letter_to_digit(KeyWords[i])==-1)
{
continue;
}
ChoiceWords[++k]=tolower(KeyWords[i]);
for(j=0;j<k;j++)
{
if(ChoiceWords[j]==ChoiceWords[k])
{
k--;
break;
}
}
}
for(i=0;i<26;i++)
{
KeyTab[i]=(unsigned char)letter_to_digit(ChoiceWords[i]);
//密码本前26 (密钥字法)
}
p=full_array1(25); //生成随机全排列
for(i=0;i<26;i++)
{
KeyTab[26+i]=p[i]; //密码本27~52(抽牌法)
}
for(i=0;i<26;i++)
{
KeyTab[52+i]=(unsigned char)((11*i+3)%26);
//密码本53~78 公式为11*i+3(mod 26)
}
return(KeyTab);
}
void Encrypt_s(unsigned char *key)
// 读取文件,输出密文
{
int i;
FILE *fp;
char filename[20], c;
start:
printf("\n请输入待加密文本文件名:\n");
scanf("%s", filename);
if((fp=fopen(filename,"rt"))==NULL)
{
printf("没有找到文件:%s\n",filename);
goto start;
}
printf("\n密文如下:\n");
i=0;
while((c=fgetc(fp))!=EOF)
{
if(letter_to_digit(c)==-1)
{
putchar(c);
continue;
}
isupper(c)?putchar(toupper(digit_to_letter(key[(int)((i%3)*26+letter_to_digit(c))]))):\
putchar(digit_to_letter(key[(int)(i%3)*26+letter_to_digit(c)]));
i++;
if(i>=26*3)i=0;
}
fclose(fp);
}
unsigned char *full_array1(int n)
{
int i,j;
char filename[20];
FILE *fp;
static unsigned char d[256],P[256],temp;
start:
printf("\n 请输入随机数据采样文件名:\n");
scanf("%s",filename);
if( (fp=fopen(filename,"rb"))==NULL )
{
printf("没有找到文件:%s\n",filename);
goto start;
}
fread(d,n+1,1,fp);
fclose(fp);
for(i=0;i<=n;i++)
{
P[i] = i ; //形成自然排列
}
for(i=n;i>0;i--)
{
j=(d[i-1]+d[i])%i ;
temp = P[i] ;
P[i] = P[j];
P[j] = temp; //交换
}
return(P);
}
题目二:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
unsigned char *full_array2(int n);
unsigned char *KeyGen_p();
void Encrypt_p(unsigned char *key);
int main()
{
unsigned char *key;
key=KeyGen_p();
for(int i=0;i<5;i++){
for(int j=0;j<16;j++){
printf(" %d",key[i*16+j]))
}
printf("\n");
}
Encrypt_p(key);
return 0;
}
unsigned char *KeyGen_p()
{
unsigned char *p;
static unsigned char KeyTab[16*5];
int i, j;
for(i=0;i<5;i++)
{
p=full_array2(15);
for(j=0;j<16;j++)
{
KeyTab[16*i+j]=p[j];
}
}
return(KeyTab);
}
void Encrypt_p(unsigned char *key)
{
int i, j;
FILE *fp;
char filename[26], c;
char d[16];
start:
printf("\n请输入待加密文本文件名:\n");
scanf("%s", filename);
if((fp=fopen(filename,"rt"))==NULL)
{
printf("没有找到文件:%s\n",filename);
goto start;
}
printf("\n密文如下:\n");
i=0;
while((c=fgetc(fp))!=EOF)
{
d[i%16]=c;
i++;
if(i%16!=0)
{
continue;
}
for(j=0;j<16;j++)
{
putchar(d[key[(int)(((i-1)/16)*16+j)]]);
}
if(i>=16*5)
{
i=0;
}
}
if(i%16!=0)
{
for(j=i%16;j<16;j++)
{
d[j]='*';
}
for(j=0;j<16;j++)
{
putchar(d[key[(int)((i/16)*16+j)]]);
}
}
putchar('\n');
}
unsigned char *full_array2(int n)
{
int m, i, j, k, l=0, flag;
static unsigned char P[256];
start:
printf("\n请输入不小于%d的所用随机数个数:\n", n+1);
scanf("%d",&m);
if(m<=n)
{
printf("\n输入数%d比%d小,须重新输入!\n", m, n+1);
goto start;
}
srand((unsigned)time(NULL));
for(i=0;i<m;i++)
{
P[l++]=(unsigned char)(rand()%(n+1));
for(j=0;j<l-1;j++)
{
if(P[j]==P[l-1])
{
l--;
break;
}
}
}
k=l;
for(i=0;i<=n;i++)
{
flag=0;
for(j=0;j<k;j++)
{
if(P[j]==i)
{
flag=l;
break;
}
}
if(!flag)
{
P[l]=i;
l++;
}
}
return (P);
}
3.测试结果
密文:
(1)替换密码
运行结果:
检验:密码本如下
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |
j | y | k | a | b | c | d | e | f | g | h | i | l | m | n | o | p | q | r | s | t | u | v | w | x | z |
c | w | n | v | g | t | x | a | e | f | s | y | b | o | p | j | d | l | i | u | k | z | h | q | m | r |
d | o | z | k | v | g | r | c | n | y | j | u | f | q | b | m | x | i | t | e | p | a | l | w | h | s |
检验前六位:
明文 | Q | i | s | a | s | y |
密文 | P | e | t | j | i | h |
检验结果正确。
(2)移位密码
运行结果:
检验:密码本如下
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
0 | 6 | 7 | 8 | 3 | 10 | 1 | 11 | 4 | 9 | 13 | 12 | 14 | 2 | 5 | 15 |
3 | 2 | 0 | 8 | 14 | 9 | 11 | 5 | 10 | 1 | 6 | 15 | 7 | 12 | 4 | 13 |
6 | 14 | 8 | 7 | 2 | 12 | 0 | 1 | 5 | 13 | 3 | 4 | 15 | 9 | 10 | 11 |
0 | 4 | 5 | 8 | 11 | 14 | 15 | 6 | 9 | 3 | 1 | 10 | 2 | 12 | 7 | 13 |
7 | 12 | 1 | 4 | 13 | 9 | 0 | 14 | 3 | 8 | 6 | 10 | 2 | 5 | 11 | 15 |
检验前16位:
明 | Q | i | s | a |
| s | y | m | m | e | t | r | i | c | ||
密 | Q |
| s | y | s | m |
| e | m | r | t | i | i | a | c |
检验结果正确。
4.遇到问题
在实验过程中部分代码参考实验指导书,因此在实验过程中,发现实验指导书上存在的一些问题
(1)程序报错
程序在运行此行代码时报错,原因与长度有关。
解决方法:改进代码,如下。
char alphabet[i];
for(int i=0;i<26;i++)alphabet[i]='a'+i;
(2)实验指导书关于题目一代码错误
按照次代码,会将明文前26个用密钥字法加密;第二个26用抽牌法加密……。与要求周期3不符。
解决方法:修改代码,将i除26修改为i模3;
正在上传…重新上传取消
四、实验体会
本次是密码学的第一次实验,主要是两种基本古典密码设计与实现,在操作之中逐渐熟悉了他们的使用方法并了解了两种密码的差异。
本次实验的函数对我相对困难,因此部分函数借鉴了实验指导书,在对实验指导书的学习过程中,我也发现了实验指导书上面存在一些问题,并通过自己的理解修改了函数,并得到了老师的肯定。
能发现问题,源自于我对题目的深刻理解,这次实验之前的预习必不可少,投入的时间终究会有回报。
五、思考题
思考题:“代替表”与“置换”的不动点、逆等是否一致?
不一致。
代替密码的实质是从明文空间通过一定的映射关系到密文空间,已经不再是原来的集合了。移位密码的实质是在明文空间内部,通过移动明文位置,使明文乱序,从而达到加密的目的。两者的不动点和逆只有在统一集合变换时才可能一样。