对文件采用不同数字签名算法生成签名
一、项目需求
功能框架图如图所示:
(1)初始化获得公钥和私钥
(2)利用公钥进行签名
(3)利用私钥进行验证
(4)MD5算法
二、数据结构及算法设计
1.主要数据结构
int pow_mod(int a, int b, int p) { int ans = 1; int tmp = a % p; while (b) { if (b & 1) ans = ans * tmp % p; b >>= 1; tmp = tmp * tmp % p; } return ans % p; }
2.主要算法流程
(1)文件签名
1、获取文件路径
2、打开文件,获取文件内容,进行MD5加密
3、获取公钥和模数
4、对MD5加密后的哈希值进行基于RSA算法的签名
5、输出签名
(2)签名认证
1、选择文件
2、获取文件路径
3、对文件内容进行MD5加密
4、获取私钥、模数
5、获取签名
6、用私钥和哈希值进行签名认证
7、输出认证结果
三、原码
//文件 main.cpp #include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <math.h> #include <windows.h> #include <ShlObj.h> #include <stdio.h> #include <stdlib.h> #include <atlstr.h> #include <exception> #include "MD5.h" using namespace std; //n方求余 int pow_mod(int a, int b, int p) { int ans = 1; int tmp = a % p; while (b) { if (b & 1) ans = ans * tmp % p; b >>= 1; tmp = tmp * tmp % p; } return ans % p; } /*个人认为是全部代码中最核心的功能部分,这个函数用于计算数字的乘方模余,并且避免了数字过大的问题。首先,会对乘方根进行求余计算一次,接着便会带入WHILE循环进行循环求余,并每次把乘方数右移两位运算符,通过对未完全乘方的结果进行求余,使得结果不会过超出计算机的运算范围,并能更加快地得出结果。*/ //互质验证 int mgcd(int a, int b) { int t; if (a < b) { t = a; a = b; b = t; } while (a % b) { t = b; b = a % b; a = t; } return b; } /*由于要求公钥e和私钥d的最大公因数只能为一,因此设计了这个函数。利用的原理是辗转相除法,最后会把最大的公因数结果带回函数。*/ //字母转换 int tran(char* a) { int i, num = 0; for (i = 0; i < 2; i++) { if (a[i]<'G' && a[i]>'A' || a[i] == 'A') { num = num * 100 + a[i] - 55; } else { num = num * 10 + a[i] - 48; } } return num; } /*由于MD5值是16进制,且输入的是字符串类型,在计算上难以使用,因此需要将A到F转换成相应的数字进行计算。我根据ASCII码值减去48和55来得到相应的数字。*/ int tran1(char* a,int max) { int i, num = 0; for (i = 0; i <max; i++) { if (a[i]<'G' && a[i]>'A' || a[i] == 'A') { num = num * 100 + a[i] - 55; } else { num = num * 10 + a[i] - 48; } } return num; } //由于签名的分组未知,需要知道间隔,因此单独书写了一个函数 //签名分组 void ded(char* MD5_NAME, int* num) { int i, k; char cha[16][10]; for (i = 0, k = 0; i < 32; i = i + 2, k++) { strncpy_s(cha[k], MD5_NAME + i, 2); } for (i = 0; i < 16; i++) { num[i] = tran(cha[i]); } } /*考虑到MD5值转换成数字后过大无法用于计算,因此我将他们分成了一个随机等分大小的数组元素,再分别实行签名。*/ void ded1(char* MD5_NAME, int* num,int max) { int i, k; char cha[16][10]; //printf("INPUT:%s\n", MD5_NAME); for (i = 0, k = 0; i < 16*max; i = i + max, k++) { strncpy_s(cha[k], MD5_NAME + i, max); //printf("cha:%s \n", cha[k]); } for (i = 0; i < 16; i++) { num[i] = tran1(cha[i],max); //printf("num:%d\n", num[i]); } } //同理,因为签名无法识别分组,因此需要单另提交间隔 //最大 int length(int en) { int i, max=0; while (en != 0) { printf("en:%d\n", en); en = en / 10; max++; } return max; } //这个函数是用于计算数组元素单个结果的最长位数,用于之后的分组 //签名 void sign(char* MD5_NAME, int* en, int* num, int e, int n) { int i,max,t; for (i = 0; i < 16; i++) { en[i] = pow_mod(num[i], e, n); } max = length(en[0]); for (i = 1; i < 16; i++) { t = length(en[i]); if (max < t) max = t; } printf("数字签名:\n"); for (i = 0; i < 16; i++) { printf("%0*d",max, en[i]); } printf("\n"); } /*这个函数是签名算法的实现,具体流程就是将输入的转化后的MD5值数组进行乘方求模运算,然后根据求出的最大位数输出以最大位数为打印单位,不足填充零的签名结果*/ //MD5加密 void MD5_CREATE(char* MD5_NAME) { char tmpstr[256], buf0[10], buf1[10], buf2[10], buf3[10], tmp[33],*data; FILE* fp; int length; printf("请输入加密文件路径:\n"); cin >> tmpstr; fopen_s(&fp, tmpstr, "r"); while (fp==NULL){ printf("输入错误,请重新输入\n"); cin >> tmpstr; fopen_s(&fp, tmpstr, "r"); } fseek(fp, 0, SEEK_END); length = ftell(fp); data = (char*)malloc((length + 1) * sizeof(char)); rewind(fp); length = fread(data, 1, length, fp); data[length] = '\0'; fclose(fp); //MD5加密 unsigned int* tmpGroup = MD5(data); sprintf_s(buf0, "%08X", tmpGroup[0]); sprintf_s(buf1, "%08X", tmpGroup[3]); sprintf_s(buf2, "%08X", tmpGroup[2]); sprintf_s(buf3, "%08X", tmpGroup[1]); sprintf_s(tmp, "%s%s%s%s", buf0, buf1, buf2, buf3); strcpy_s(MD5_NAME, 33, tmp); } MD5加密函数是用于把文件内容提取后放入DATA字符串,接着再放入MD5函数中进行加密。由于文件内容长度不一,因此需要使用DATA动态数组进行装载 //认证 void veri(int e,int n) { char buf0[10], buf1[10], buf2[10], buf3[10], tmp[33], path[256], input[256],*data; int en[16],de[16],t[16],i,flag=0,length,max; FILE* fp; OPENFILENAME open;// 公共对话框结构。 char file[MAX_PATH];// 用来保存获取文件名称的缓冲区。 printf("开始验证,请选择文件\n"); ZeroMemory(&open, sizeof(OPENFILENAME)); // 初始化选择文件对话框 open.lStructSize = sizeof(OPENFILENAME);//指定这个结构的大小,以字节为单位。 open.lpstrFile = file;//打开的文件的全路径 open.lpstrFile[0] = '\0'; //第一个字符串是过滤器描述的显示字符串 open.nMaxFile = MAX_PATH; //指定lpstrFile缓冲的大小,以TCHARs为单位 open.lpstrFilter = "文本文件(*.txt)\0*.txt\0所有文件(*.*)\0*.*\0\0"; //打开文件类型 open.nFilterIndex = 1; //指定在文件类型控件中当前选择的过滤器的索引 open.lpstrFileTitle = NULL; // 指向接收选择的文件的文件名和扩展名的缓冲(不带路径信息)。这个成员可以是NULL。 open.nMaxFileTitle = 0; //指定lpstrFileTitle缓冲的大小,以TCHARs为单位 open.lpstrInitialDir = NULL; //指向以空字符结束的字符串,可以在这个字符串中指定初始目录。 open.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;//位标记的设置,你可以使用来初始化对话框 //GetOpenFileName (&open) ;//打开文件对话框 //GetSaveFileName(&open);//保存文件对话框 if (GetOpenFileName(&open)){ // 显示打开选择文件对话框。 strcpy_s(path, 256, file); printf("文件路径:%s", path); } //获取内容 fopen_s(&fp, path, "r"); if (fp == NULL) { printf("输入错误,请重新选择"); ZeroMemory(&open, sizeof(OPENFILENAME)); // 初始化选择文件对话框 open.lStructSize = sizeof(OPENFILENAME);//指定这个结构的大小,以字节为单位。 open.lpstrFile = file;//打开的文件的全路径 open.lpstrFile[0] = '\0'; //第一个字符串是过滤器描述的显示字符串 open.nMaxFile = MAX_PATH; //指定lpstrFile缓冲的大小,以TCHARs为单位 open.lpstrFilter = "文本文件(*.txt)\0*.txt\0所有文件(*.*)\0*.*\0\0"; //打开文件类型 open.nFilterIndex = 1; //指定在文件类型控件中当前选择的过滤器的索引 open.lpstrFileTitle = NULL; // 指向接收选择的文件的文件名和扩展名的缓冲(不带路径信息)。这个成员可以是NULL。 open.nMaxFileTitle = 0; //指定lpstrFileTitle缓冲的大小,以TCHARs为单位 open.lpstrInitialDir = NULL; //指向以空字符结束的字符串,可以在这个字符串中指定初始目录。 open.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;//位标记的设置,你可以使用来初始化对话框 //GetOpenFileName (&open) ;//打开文件对话框 //GetSaveFileName(&open);//保存文件对话框 if (GetOpenFileName(&open)) { // 显示打开选择文件对话框。 strcpy_s(path, 256, file); printf("文件路径:%s", path); } } fseek(fp, 0, SEEK_END); length = ftell(fp); data = (char*)malloc((length + 1) * sizeof(char)); rewind(fp); length = fread(data, 1, length, fp); data[length] = '\0'; fclose(fp); printf("\n请输入数字签名:\n"); cin >> input; //MD5加密 unsigned int* tmpGroup = MD5(data); sprintf_s(buf0, "%08X", tmpGroup[0]); sprintf_s(buf1, "%08X", tmpGroup[3]); sprintf_s(buf2, "%08X", tmpGroup[2]); sprintf_s(buf3, "%08X", tmpGroup[1]); sprintf_s(tmp, "%s%s%s%s", buf0, buf1, buf2, buf3); for (max = 1; max < strlen(input)+1; max++) { //printf("%d\n",strlen(input)); //printf("max:%d\n", max); flag = 1; //分组 ded(tmp, de); ded1(input, en, max); //判断 for (i = 0; i < 16; i++) { t[i] = de[i] % n; //printf("de[i]:%d en[i]:%d\n", de[i], en[i]); if (t[i] != pow_mod(en[i], e, n)) flag = 0; break;//跳出验证 } if (flag != 1) { continue;//max++ } else { printf("认证合法\n"); break;//跳出MAX } } if (flag == 0) { printf("认证不合法\n"); } } /* 认证函数首先会调取WINDOWS窗口让操作者选择需要验证的文件,接着函数将调用MD5加密函数进行加密并将结果分组保存。接着函数会要求操作者输入私钥、模数以及签名,再对签名进行整型转化,然后分组。最后,会用H(m) mod n== c^d mod n来验证用户输入签名和文件签名是否相同 主函数的功能大多数都被分封到了其他函数中,只剩下图形化美化以及部分初始化数据的输入是在主函数中进行*/ int main() { int e,p,q,n,o,d=1,select; char MD5_NAME[33]; int en[16], de[16]; while (1) { printf("########################################################################################################################\n"); printf("请选择功能:\n0:密钥初始化 1:文件签名 2:签名认证 3:退出\n"); scanf_s("%d", &select); if (select == 0) { //大素数p,q printf("请输入素数p,注意保密销毁:\n"); scanf_s("%d", &p); printf("请输入素数q,注意保密销毁:\n"); scanf_s("%d", &q); //模余数 n = p * q; o = (p - 1) * (q - 1); //私钥并验证 printf("请输入公钥e:\n"); scanf_s("%d", &e); while (mgcd(e, o) != 1) { printf("私钥不符合要求,请重新输入与(p-1)(q-1)互质的数\n"); scanf_s("%d", &e); } //求D while ((d * e) % o != 1) { d++; } printf("接收方的公钥为:(n:%d,e:%d)\n您的私钥为d:%d\n", n, e, d); printf("########################################################################################################################\n"); printf("1:返回 其他:退出\n"); scanf_s("%d", &select); if (select == 1) { system("cls"); } else { exit(1); } } else if (select == 1) { MD5_CREATE(MD5_NAME); printf("MD5:\n%s\n", MD5_NAME); printf("########################################################################################################################\n"); printf("请输入公钥e:\n"); scanf_s("%d",&e); printf("请输入模数n:\n"); scanf_s("%d", &n); //预准备 ded(MD5_NAME, de); //签名 sign(MD5_NAME, en, de, e, n); printf("########################################################################################################################\n"); printf("1:返回 其他:退出\n"); scanf_s("%d", &select); if (select == 1) { system("cls"); } else { exit(1); } } else if (select == 2) { //私钥并验证 printf("请输入私钥d:\n"); scanf_s("%d", &d); printf("请输入模数n:\n"); scanf_s("%d", &n); veri(d, n); printf("########################################################################################################################\n"); printf("1:返回 其他:退出\n"); scanf_s("%d", &select); if (select == 1) { system("cls"); } else{ exit(1); } } else if (select == 3) { exit(1); } else { system("cls"); } } getchar(); return 0; }
四、结果
1、初始化
(1)首先输入大素数P、Q进行公钥和私钥的初始化,使用后需要销毁PQ
(2)接着需要计算N=P*Q,O=(P-1)(Q-1)用于接下来的运算
(3)要求输入公钥E,注意,需要和O的最大公因数为1
(4)计算出私钥D,它和公钥相乘后的模余为1
(5)输出公钥和私钥结果
2、文件签名
(1)输入文件路径,程序会根据路径找到文件并获取内容进行MD5加密,输出结果
(2)在输入了公钥和模数后,程序会根据公式m^d mod n来得出签名结果并输出,输出的结果是长度不定的。
3、签名认证
(1)程序会弹出窗口,让操作者选择需要验证的文件
(2)在选择好后,程序会读取文件内容进行MD5加密
(3)程序会要求操作者输入私钥和模数用于验证签名
(4)在输入了操作者提供的私钥后,程序会根据H(m) mod n == s^d mod n来进行比对,如果相等就会认证成功,否则认证失败
3.1失败
当结果不相同的时候,认证是不合法的
3.2成功
输入签名若在某一分组时所用分组的运算结果都相等则验证结果成功