C语言课程设计实习报告封面.docx
《C语言课程设计实习报告封面.docx》由会员分享,可在线阅读,更多相关《C语言课程设计实习报告封面.docx(35页珍藏版)》请在冰豆网上搜索。
C语言课程设计实习报告封面
信息安全基础课程设计
姓名:
范相地学号:
20111004304
专业:
192112班学号:
192112—28
院(系):
信息安全指导教师:
宋军、杨帆、余林琛
2014年3月
第一部分数学基础与密码学部分实验内容
第一章信息安全数学基础实验部分
1、题目:
使用VC++编程语言编写一个可测定不超过1,000,000的素数判定程序。
2、定理:
设n是一个正整数,如果对所有的素数p≤,都有płn,则n一定是素数。
注:
古希腊数学家埃拉托斯散(Eratosthenes,公元前275—公元前194)发明了求比某给定数小的素数的筛法技巧。
方法如下:
对于任意给定的正整数N,要求出所有不超过sqrt(N)的素数。
我们列出N个整数,从中删除小于等于的所有素数p1,…,pk的倍数。
然后依次删除,
p1的倍数:
2p1,…,p1
……
pk的倍数:
2pk,…,pk
余下的整数(不包括1)就是所要求的不超过N的素数。
3、代码设计:
首先要求出sqrt(n)以内的素数,创建一个函数进行调用来判断是否为素数。
intis_Prime(intq)
{
intsq;
intflag=1;
inti;
sq=sqrt(q);
for(i=2;i<=sq;i++)
{
if(q%i==0)
{
flag=0;
break;
}
}
returnflag;
}
当求出sqrt(n)以内的素数,并存储于数组a[i]中,则对于要判定的素数n,只需判断
是否n%a[i]==0。
for(i=0;i{
if(n%a[i]==0)
{
cout<break;
}
}
if(i==j)
cout<4、源码:
#include
#include
usingnamespacestd;
intmain()
{
intn,k;
inti;
intj=0;
intp=0;
inta[200];
cout<<"输入数n=";
cin>>n;
intis_Prime(int);
k=sqrt(n);
///////////////////////////////////////////////////////
for(i=2;i<=k;i++)
{
if(is_Prime(i)==1)
{cout<
a[j]=i;
j++;}}
cout<////////////////////////////////////////////////////////////
for(i=0;i{
if(n%a[i]==0)
{
cout<break;
}
}
if(i==j)
cout<cout<return0;
}
////////////////////////////////////////////
intis_Prime(intq)
{
intsq;
intflag=1;
inti;
sq=sqrt(q);
for(i=2;i<=sq;i++)
{
if(q%i==0)
{
flag=0;
break;
}
}
returnflag;
}
第二章密码学基础实验部分
题目:
1999年3月22日第二次AES会议上,将候选算法名单减少为5个,这5个
算法是RC6,Rijndael,SERPENT,Twofish和MARS。
使用VC++编程语言编写
一个程序:
(五选一)以Rijndael为例
关于Rijndael算法:
Rijndael算法首先是一个密钥分组加密的算法,通过置换(permutations)和替换(substitutions)迭代加密,进过多轮操作形成密文。
AES算是Rijndael算法的一种特殊实现,选的分组为128bit(16字节),密钥可以使用128、192和256bit三种。
分组
上面已经说了AES分组为16个字节,下面说说他的排列,其实就是一个4x4的矩阵,不过要注意是竖着排的。
AES原文:
a1a2a3a4a5a6a7a8a9a10a11a12a13a14a15a16...
a1 a5 a9 a13
a2 a6 a10 a14
a3 a7 a11 a15
a4 a8 a12 a16
密钥
AES的密钥虽然有三种,但是并不意味着这三种密钥的AES差异很大,相反加密过程其实完全一样,只是种子密钥是128,192,256bit三种而已。
密钥在AES与分组数据并没有做非常复杂的变化,其实只是简单的&(与)操作而已。
不过又因为AES算法有很多轮,所有单单的种子密钥是不够的,所有AES有自己的扩展密钥的方法。
还要提一下,密钥扩展后也是竖着排成方阵的。
key:
k1k2k3k4k5k6k7k8k9k10k11k12k13k14k15k16...
k1 k5 k9 k13
k2 k6 k10 k14
k3 k7 k11 k15
k4 k8 k12 k16
轮数
轮数主要跟种子密钥长度有关。
一般习惯用Nk表示密钥所含的数据字数,一字表示32bit,也就是4字节,也就是一竖排。
128,192,256bit对应Nk=4,6,8
一般习惯用Nr表示轮数
Nr=Nk+6也就是10,12,14
密钥扩展(KeyExpansion)
对与密钥扩展其实只要关心两点:
一是扩展成多长,这个其实跟加密过程有关,暂时只要知道密钥总长度为(分组大小=16个字节)*(Nr+1),也就是4*(Nr+1)字,下面讲过程的时候就会明白为什么要这么长。
二是密钥扩展的算法,密钥的算法其实比较复杂,但是却不难理解。
注:
Nk密钥字数,字为4字节也就是一竖排。
算法步骤如下:
Nk≤6的密钥扩展
1)最前面的Nk个字是由种子密钥填充的。
2)之后的每一个字W[j]等于前面的字W[j-1]的与Nk个位置之前的字W[j-Nk]的异或。
3)而且对于Nk的整数倍的位置处的字,在异或之前,对W[j-1]的进行如下变换:
.字节的循环移位RotByte->即当输入字为(a,b,c,d)时,输出字为(b,c,d,a)
.用S盒进行变换次位元组
.异或轮常数Rcon[i/Nk]
伪代码
KeyExpansion(byteKey[4*Nk],W[Nb*(Nr+1)])
{
for(i=0;i W[i]=(Key[4*i],Key[4*i+1],Key[4*i+2],Key[4*i+3]);
//扩展密钥的前面4个字由种子密钥组成
for(i=Nk;i {
temp=W[i-1];
if(i%Nk==0)
temp=SubByte(RotByte(temp))^Rcon[i/Nk];
//i是NK的整数倍是要特殊处理
W[i]=W[i-Nk]^temp;
}
}
Nk>6的密钥扩展
KeyExpansion(byte Key[4*Nk],W[Nb*(Nr+1)])
{for(i=0;i W[i]=(Key[4*i],Key[4*i+1],Key[4*i+2],Key[4*i+3]);
//扩展密钥的前面4个字由种子密钥组成
for(i=Nk;i {temp=W[i-1];
if(i%Nk==0)
temp=SubByte(RotByte(temp))^Rcon[i/Nk];
//i是NK的整数倍是要特殊处理
elseif(i%Nk==4)
temp=SubByte(temp);
//i是4的整数倍是要特殊处理
W[i]=W[i-Nk]^temp;}
异或轮常数Rcon[i/Nk]:
Rcon[i/Nk]=(RC[i/Nk]],’00’,’00’,’00’)(i一定会大于Nk) RC[1]=‘01’RC[x]=2⊙RC[x-1]
RC[1]=(01,00,00,00),RC[2]=(02,00,00,00),
RC[3]=(04,00,00,00),RC[4]=(08,00,00,00),
RC[5]=(10,00,00,00),RC[6]=(20,00,00,00),
RC[7]=(40,00,00,00),RC[8]=(80,00,00,00),
RC[9]=(1b,00,00,00),RC[10]=(36,00,00,00))
每一轮分组(源数据)都要经过4种变换,分别是ByteSubShiftRowMixColumnAddRoundKey
ByteSub(字节代换)
这个比较简单,就是查表替代。
对于分组的16字节都进行字节代换就行,还有字节一般写成16进制的也就是如FF这种。
sbox如下图:
ShiftRow(行移位)
对于分组4x4有4行,进行如下规则位移
第0行不移位,
第1行左移1字节,
第2行左移2字节,
第3行左移3字节。
MixColumn(列混合)
列混合其实比较难理解。
原理是数学矩阵相乘,如下图s为原数据,s‘为加密后数据。
不过这里的乘法和加法都是数学域操作,说白了就是一种新的运算。
加法
a+b=a^b
加法就是做两者的异或操作
乘法
0x01*b=b
0x02*b只要b小于0x80,b<<1。
如果b大于或等于0x80,(b<<1)^0x1b
对于大于0x02可以分解成0x02和0x01的算法,这牵扯到数学域GF(2^8),可以分解成2的幂次方,在这不多做介绍,其实是本文作者能力有限,讲不清楚。
b*0x03=b*(0x02+0x01)
=(b*0x02)+(b*0x01)
b*0x0d=b*(0x08+0x04+0x01)
=(b*0x08)+(b*0x04)+(b*0x01)
=(b*0x02*0x02*0x02)+(b*0x02*0x02)+(b*0x01)
b*0x09=b*(0x08+0x01)
=(b*0x02*0x02*0x02)+(b*0x01)
b*0x0b=b*(0x08+0x02+0x01)
=(b*0x02*0x02*0x02)+(b*0x02)+(b*0x01)
b*0x0e=b*(0x08+0x04+0x02)
=(b*0x02*0x02*0x02)+(b*0x02*0x02)+(b*0x02)
AddRoundKey
就是4x4的原数据与4x4的扩展密钥做异或运算
下面是加密流程的伪代码,到此aes算法就介绍完了
Encryption(State,CipherKey)
{ KeyExpansion(CipherKey,RoundKey)
AddRoundKey(State,RoundKey)//第0轮只做AddRoundKey
For(i=1;i Round(State,RoundKey)
{ByteSub(State);
ShiftRow(State);
MixColumn(State);
AddRoundKey(State,RoundKey)}
FinalRound(State,RoundKey)//最后一轮不做mixcolumn
{
ByteSub(State);
ShiftRow(State);
AddRoundKey(State,RoundKey);
}
}
第二部分信息论基础实验部分
题目一算术编码
1﹑题目要求
算术编码是把一个信源表示为实轴上0和1之间的一个区间,信源集合中的每一个元素都用来缩短这个区间。
算法流程
(1)输入信源符号个数,信源概率分布,还有需要编码的符号序列,
(2)根据概率可以算出初始编码间隔,
High——当前编码的上限,
Low——当前编码的下限,
high——中间变量,用来计算下一个编码符号的当前间隔的上限,
low——中间变量,用来计算下一个编码符号的当前间隔的下限,
d——当前间隔之间的距离。
(3)扫描需编码的符号序列,确定编码空间
第1个编码符号的当前间隔为其初始的编码间隔,
第i个编码符号的当前间隔为第i-1个编码后的[Low,High),
第i+1个编码符号的当前间隔算法如下:
high=Low+d*第i+1个初始编码符号对应的上限,low=Low+d*第i+1个编码符号对应的下限,然后High=high,Low=low,d=d*第i个编码符号的概率。
2.设计
其算法的主要算法基本思想:
首先就是构造一个结构体用于存储信源的相关信息(包括信源符号,信源概率,编码的上下限);接着就是初始化信源的相关信息,如初始化编码间隔;利用算术编码的原理构造编码方法,最后实现编码
3.调试分析:
此算法主要就是算术编码方法的构造,初始化以后,根据初始化间隔完成编码序列的编码。
在调试的过程中经常遇到一些小问题,通过一步步的调试以及修改,问题一个的解决了。
同时也遇到比较大的问题,就是编码方法过程的实现,最大的体会就是必须深入理解次编码方法的原理。
4.流程图
4.测试结果:
7.源程序清单:
#include
#include
#include
usingnamespacestd;
typedefstructNode
{
charname;
doublepi;
doublefi;
};
intmain()
{
inti,j;
doubleps=1;
doublefs;
intlength;//码字s1的长度
Nodenode[20];
intn;//信源符号个数n元
cout<<"输入信源个数n=";
cin>>n;
//////////////////////////////////////////
cout<<"输入信源符号及其概率"<for(i=0;i{
cin>>node[i].name;
cin>>node[i].pi;
}
node[0].fi=0;
for(i=1;inode[i].fi=node[i-1].fi+node[i-1].pi;
////////////////////////////////////////////////////
chars[20];//所要编码符号序列
chars1[20];//码字结果
cout<<"输入需要编码序列s:
";
cin>>s;
cout<
intls=strlen(s);//编码序列s的长度
///////////////////////////////////
doubleHigh=1;
doubleLow=0;
doubled=1;
//////////////////////////////////////////
//求出码字长度
for(i=0;i{
for(j=0;j{
if(node[j].name==s[i])
{
Low=Low+d*node[j].fi;
d=d*node[j].pi;
High=Low+d;
break;
}
}
ps*=node[j].pi;
}
length=log(1/ps)/log
(2)+1;
fs=Low;
cout<<"码字长度为"<cout<<"累计概率fs="</////////////////////////////////////////
intk[20];
for(i=0;i{
fs=fs*2.0;
if(fs<1.0)
k[i]=0;
else
{
k[i]=1;
fs=fs-1.0;
}
}
////////////////////////////////////
if(fs!
=0)
for(i=length-1;i>=0;i--)
{
if(k[i]==0)
{
k[i]=1;
break;
}
else
k[i]=0;
}
///////////////////////////////////////////
cout<<"码字为:
";
for(i=0;icout<cout<}
题目二Huffman编码对文本的压缩和解压缩
1﹑题目要求
根据信源压缩编码——Huffman编码的原理,制作对英文文本进行压缩和解压缩的软件。
要求软件有简单的用户界面,软件能够对运行的状态生成报告,分别是:
字符频率统计报告、编码报告、压缩程度信息报告、码表存储空间报告。
2.设计
设计思想:
首先构造一个结构体用于统计字符频率,然后把统计后的结果当作信源;接着用Haffman编码的方法对其编码,编码后对输入的英语文本进行编码的转换,即用Haffman编码的每一个字符的编码代替输入的英语文本每一字符,输入的英语文本就变成了01代码流,最后利用这01代码流每八位压缩成相对应的字符。
压缩流程:
读取扫描文本文件——〉统计字符频率——〉生成码字——〉保存压缩文件
解压缩流程:
读取扫描压缩文件——〉提取字符频率——〉生成码树——〉保存文本文件
3.调试分析:
本算法可以分成四个部分即统计字符概率,Huffman编码,压缩文件和解压文件四部分,也就是次算法中函数stati(),函数HCode()和函数compress()的构建,用于压缩后的字符出现了乱码,所以对解压文件这部分难以实现,这也是此算法失败的地方。
经过不断的调试和修改还是只完成统计字符概率,Huffman编码和压缩文件这三部分。
4.流程图
5测试结果:
6.源程序清单:
#include
#include
#defineMaxValue1000//设定权值最大值
#defineMaxBit10//设定的最大编码位数
#defineMaxN1000//设定的最大结点个数
#include"Huffman.h"
floatComNum=0;//用于计算压缩后的字符个数
structstatistics//统计字符频率
{
chara[100];//出现的字符
doublep[100];//字符出现的概率
inttag[100];//每一个字符出现次数
intnum;//总计出现的字符种类个数
floatn;//总计字符出现的次数
}TJ;
charcc[100];
voidraplace(myHaffCode);
stati()//统计字符
{
FILE*fp;
FILE*fp1;
charch,filename[10];
inti=0,k;
printf("请输入用于保存字符文本的文件名,如file.txt\n");
scanf("%s",filename);
getchar();
printf("请输入英语文本:
");
gets(cc);
fp=fopen(filename,"w+");
fprintf(fp,"%s",cc);
fclose(fp);
TJ.num=1;
TJ.n=0;
if((fp1=fopen(filename,"r"))==NULL)
{
printf("文件无法打开!
");
exit(0);
}
ch=fgetc(fp1);
TJ.a[i]=ch;
while(ch!
=EOF)//统计字符出现的次数,并计算起概率。
{
intj;
for(j=i;j>=0;j--)
{
if(TJ.a[j]==ch)
{