MD5模拟

MD5签名,是一种对信息摘要的数字签名算法,或者说是一种hash算法。

数字签名

数字签名,就是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。 是一种类似写在纸上的普通的物理签名 ,比如合同签名。

数字签名有两种功效:一是能确定消息确实是由发送方签名并发出来的,因为别人假冒不了发送方的签名。二是数字
签名能确定消息的完整性。因为数字签名的特点是它代表了文件的特征,文件如果发生改变,数字摘要的值也将发生
变化。不同的文件将得到不同的数字摘要。

常用的数字签名方法主要有:HASH算法 ,此算法主要包括MD(Message-Digest,信息摘要)和SHA(安全散列算
法,Secure Hash Algorithm)两类 。

MD系列也有很多,有兴趣的可以去了解相关知识,下面主要看看MD5:

MD5

1.什么是MD5?

MD5是由Ron Rivest在1991设计的一种信息摘要(message-digest )算法,当给定任意长度的信息,MD5会产生一个
固定的128位“指纹”或者叫信息摘要。从理论的角度,所有的信息产生的MD5值都不同,也无法通过给定的MD5值产
生任何信息,即不可逆。

2.MD5的功能特点:

1.输入任意长度的信息,经过处理,输出为128位的信息(数字指纹)。

2.不同的输入得到的不同的结果(唯一性)。要使两个不同的信息产生相同的摘要,操作数量级在2^64次方。

3.根据128位的输出结果不可能反推出输入的信息。根据给定的摘要反推原始信息,它的操作数量级在2^128次。

3.MD5的用途:

1.防止信息被篡改

信息发送前先得到该信息的MD5,信息接收到后也得到一个MD5,比较两个MD5是否一致,一致则说明信息未被篡改。

2.密码保护

一些网站上用户输入密码,以MD5的形式存储在数据库中,这样就无法看到用户密码明文,实现对密码的保护。

3.防止抵赖

A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的了。这也就是所谓的数字签名。

4.MD5算法:

MD5的算法输入为以bit为单位的信息(1 byte = 8 * bit),经过处理,得到一个128bit的摘要信息。这128位的摘要信
息在计算过程中分成4个32bit的子信息,存储在4个buffer(A,B,C,D)中,它们初始化为固定常量。MD5算法然后
使用每一个512bit的数据块去改变A,B,C,D中值,所有的数据处理完之后,把最终的A,B,C,D值拼接在一
起,组成128bit的输出。最后再将输出转化成16进制的大端序输出,也就得到了MD5。

可以大致分为以下几个步骤:

1.添加填充位

以512bit为一块,信息的最尾部(不是每一块的尾部)要进行填充,使其最终的长度length(以bit为单位)满足length % 512 = 448,也就是剩下64bit块就满了。这一步始终要执行,即使信息的原始长度恰好符合上述要求,这时候填充512bit,不够填充了再加上一块。

填充规则:第一个bit填充位填 '1' ,后续bit填充位都填 '0' ,最终使消息的总体长度满足上述要求。总之,至少要填充 1 bit,至多填充 512 bit。

按字节填充,所以这里先填充0x80。现在看最后一块剩余的大小,如果小于64bit,也就是8字节,就把剩余位都添充0,再重新加一块数据,全部填充0;如果大于64bit,先把剩下的填为0;

2.添加bit长度

最后64bit存放原始文档的bit长度。

3.初始化MD buffer(A,B,C,D)

初始化a、b、c、d

word A: 01 23 45 67

word B: 89 ab cd ef

 word C: fe dc ba 98

 word D: 76 54 32 10

4.按512bit(64字节)逐块处理数据

处理方式:

MD5模拟_第1张图片

 s[ 0..15] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 }

 s[16..31] = { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 }

 s[32..47] = { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 }

 s[48..63] = { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }

 

k[i] = 2^32 * abs(sin(i))

 

chunk[g]

if (0 <= i < 16)  g = i;

if (16 <= i < 32) g = (5 * i + 1) % 16;

if (32 <= i < 48) g = (3 * i + 5) % 16;

 if (48 <= i < 63) g = (7 * i) % 16;

 

Func = func(b, c, d)
           这里的func有4种,分别为F,G,H,I

 d = c

 c =b

 b = b + shiftLeftRotate((a + Func + k[i] + chunk[g]), _shift[i])

 a = d

 

这里循环左移shiftLeftRotate实现思路:

结果:

MD5模拟_第2张图片

实现思路:

MD5模拟_第3张图片

结论:循环左移n位(0

5.输出摘要(转化为16进制大端序输出)

例:0x12345678 ---->  78563412    

思想:每次取出两位数,也就是8位,然后再取4位,每次都是头插

就比如第一次取出78,从78中先去8头插到str中,str现在是8,再取7头插,str成了78

下一次继续去56,依次头插完了str成了5678,每次通过右移和与0xff来实现取最后两位数。

5.代码

代码分为三个文件MD5.h、MD5.cpp、test.cpp

1.MD5.h

#pragma once
#define N 64
#include

class MD5
{
public:
	MD5();
	std::string getStringMD5(const std::string& str); //计算字符串的MD5
	std::string getFileMD5(const char* filename); //计算文件的MD5
private:
	void init(); //初始化(_a, _b, _c, _d, k[i], s[i])
	void calculateMD5(size_t* chunk); //计算MD5
	void calculateMD5Final(); //计算最后的MD5
	std::string changeHex(size_t num); //转化成16进制的数(大端)


	/*
	F(x,y,z) = (x & y) | ((~x) & z)
	G(x,y,z) = (x & z) | ( y & (~z))
	H(x,y,z) = x ^ y ^ z
	I(x,y,z) = y ^ (x | (~z))
	*/
	size_t F(size_t x, size_t y, size_t z)
	{
		return (x & y) | ((~x) & z);
	}
	size_t G(size_t x, size_t y, size_t z)
	{
		return (x & z) | (y & (~z));
	}
	size_t H(size_t x, size_t y, size_t z)
	{
		return x ^ y ^ z;
	}
	size_t I(size_t x, size_t y, size_t z)
	{
		return y ^ (x | (~z));
	}

	//循环左移
	size_t shiftLeftRotate(size_t num, size_t n)
	{
		return (num << n) | (num >> (32 - n));
	}
private:
	size_t _a;
	size_t _b;
	size_t _c;
	size_t _d;

	size_t _k[N];
	size_t _shift[N];

	const size_t _chunkByte; // 每块大小(字节)
	unsigned char _chunk[N]; // 标记当前这块数据

	size_t _lastByte; // 最后一块的原有数据大小(字节)
	unsigned long long _totalByte; //数据总大小(字节)
};

2.MD5.cpp

#include
#include
#include
#include"MD5.h"

MD5::MD5()
	:_chunkByte(N)
{
	init();
	memset(_chunk, 0, _chunkByte);
	_totalByte = _lastByte = 0;
}

void MD5::init()
{
	/*
	word A: 01 23 45 67
	word B: 89 ab cd ef
	word C: fe dc ba 98
	word D: 76 54 32 10
	*/
	//初始化A,B,C,D(大端序)
	_a = 0x67452301;
	_b = 0xefcdab89;
	_c = 0x98badcfe;
	_d = 0x10325476;
	
	/*
	s[ 0..15] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 }
	s[16..31] = { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 }
	s[32..47] = { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 }
	s[48..63] = { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }
	*/
	size_t s[] =  { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
					5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20,
					4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
					6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };
	memcpy(_shift, s, sizeof(s));

	//k[i] = 2^32 * abs(sin(i))
	for (size_t i = 0; i < 64; ++i)
	{
		_k[i] = (size_t)(pow(2, 32) * (abs(sin(i + 1))));
	}
}


std::string MD5::getFileMD5(const char* filename)
{
	std::ifstream fin(filename, std::ifstream::binary);
	if (fin.is_open())
	{
		while (!fin.eof())
		{
			fin.read((char*)_chunk, _chunkByte);
			//读到的字节数不是一个整块的大小,就停止读取
			if (_chunkByte != fin.gcount())
				break;
			_totalByte += _chunkByte;
			//每取一整块,计算MD5签名
			calculateMD5((size_t*)_chunk);
		}
		//整块读取完毕,得到最后一块剩余字节数
		_lastByte = fin.gcount();
		_totalByte += _lastByte;
		//对最后一块数据处理
		calculateMD5Final();
	}
	return changeHex(_a) + changeHex(_b) + changeHex(_c) + changeHex(_d);
}

std::string MD5::getStringMD5(const std::string& str)
{
	if (str.empty())
		return "";
	else
	{
		unsigned char* pstr = (unsigned char*)str.c_str();
		size_t numChunk = str.size() / _chunkByte;
		for (int i = 0; i < numChunk; ++i)
		{
			_totalByte += _chunkByte;
			//每次最多处理64个字节(512bit)也就是一块
			//这里pstr + i * _chunkByte指向的是这一块要开始的位置
			calculateMD5((size_t*)(pstr + i * _chunkByte));
		}
		_lastByte = str.size() % _chunkByte;
		memcpy(_chunk, pstr + _totalByte, _lastByte);
		calculateMD5Final();
		return changeHex(_a) + changeHex(_b) + changeHex(_c) + changeHex(_d);
	}
}

void MD5::calculateMD5Final()
{
	//lastByte < 64byte, 最后一块数据的大小
	unsigned char* p = _chunk + _lastByte;
	//填充位的前八bit位: 1000 0000   0x80
	*p++ = 0x80;
	//remainFillByte:剩余填充位(字节)
	size_t remainFillByte = _chunkByte - _lastByte - 1;
	if (remainFillByte < 8)  // 剩余填充位小于8字节(64位)
	{
		memset(p, 0, remainFillByte);
		calculateMD5((size_t*)_chunk);
		memset(_chunk, 0, _chunkByte);
	}
	else
	{
		memset(p, 0, remainFillByte);
	}
	//最后的64bit存放原始文档的bit长度
	//char类型最大范围就是256,用long long型每次偏移8个字节(64位)
	//一块有8个64位,最后64位存在_chunk[7]中,_chunk[7]就是一块(512bit)数据的最后64bit
	((unsigned long long*)_chunk)[7] = _totalByte * 8;
	calculateMD5((size_t*)_chunk);
}


//转换成大端序(16进制数字--->字符串(大端序))
//0x12345678 ---> "78563412"
std::string MD5::changeHex(size_t num)
{
	static std::string strMap = "0123456789abcdef";
	std::string ret;
	std::string byteStr;
	for (int i = 0; i < 4; ++i)
	{
		byteStr = "";
		//每次取num最后8位
		size_t b = (num >> (i * 8)) & 0xff;
		for (int j = 0; j < 2; ++j)
		{
			//取了8位,也就是2个16进制数,78 ---循环两次头插就成了 87
			//从0开始插入1个字符strMap[b % 16],strMap[b % 16]对应到字符串中的字符
			byteStr.insert(0, 1, strMap[b % 16]);
			b /= 16;
		}
		ret += byteStr;
	}
	return ret;
}

void MD5::calculateMD5(size_t* chunk)
{
	size_t a = _a;
	size_t b = _b;
	size_t c = _c;
	size_t d = _d;
	// chunk[g]		f : 哈希函数的返回值
	size_t f, g;
	/*
	if (0 <= i < 16)  g = i;
	if (16 <= i < 32) g = (5 * i + 1) % 16;
	if (32 <= i < 48) g = (3 * i + 5) % 16;
	if (48 <= i < 63) g = (7 * i) % 16;
	*/

	//64次变换,4轮操作,每一轮操作:16次子操作
	for (int i = 0; i < 64; ++i)
	{
		if (0 <= i && i < 16)
		{
			f = F(b, c, d);
			g = i;
		}
		else if (16 <= i && i < 32)
		{
			f = G(b, c, d);
			g = (5 * i + 1) % 16;
		}
		else if (32 <= i && i < 48)
		{
			f = H(b, c, d);
			g = (3 * i + 5) % 16;
		}
		else
		{
			f = I(b, c, d);
			g = (7 * i) % 16;
		}

		/*
		Func = func(b, c, d)
		这里的func有4种,分别为F,G,H,I
		d = c
		c =b
		b = b + shiftLeftRotate((a + Func + k[i] + chunk[g]), _shift[i])
		a = d
		*/

		size_t dtemp = d;
		d = c;
		c = b;
		b = b + shiftLeftRotate(a + f + _k[i] + chunk[g], _shift[i]);
		a = dtemp;
	}

	_a += a;
	_b += b;
	_c += c;
	_d += d;
}

3.test.cpp

#include
#include"MD5.h"

int main(int argc, char* argv[])
{
	//输入三个参数  第一个是程序的名字   第二个是选项,字符串还是文件 第三个是要MD5签名文件的名字
	if (argc != 3)
	{
		std::cout << "Command error!Please output according to the following format:" << std::endl;
		std::cout << "MD5.exe option filename/string" << std::endl;
		std::cout << "Example 1:MD5.exe f filename" << std::endl;
		std::cout << "Example 2:MD5.exe s string";
		return 0;
	}

	MD5 md5;
	char option = *argv[1];
	switch (option)
	{
	case 'f':
		std::cout << md5.getFileMD5(argv[2]);
		break;
	case 's':
		std::cout << md5.getStringMD5(argv[2]);
		break;
	default:
		std::cout << "'" << argv[1]  << "'" << ":This option is not defined!";
	}
	return 0;
}

6.结果测试

首先运行代码,然后打开cmd命令框,切换到当前文件的Debug目录

MD5模拟_第4张图片

这里对字符串和文件都可以进行MD5签名,这里只以文件为例验证:

这里要计算一个文件的MD5签名必须按照之前在test.cpp中我设置的格式,这里先演示一下,只输入这个MD5.exe运行,会提示正确的输入格式,如下图:

MD5模拟_第5张图片

这里有一个命令MD5.exe和两个参数;第一个参数是选项,选择是计算字符串的还是文件的,字符串是选项s,文件是选项f(输入是如果选项错误也会有对应的提示信息,这里不过多去演示了);第二个参数就是你要获取MD5签名的文件名或者是字符串。

我们按照格式计算一下MD5.ilk这个文件的MD5值,如下:

这里得到了MD5值,我们还需要验证一下是否正确,我们可以利用Windows命令行自带Hash工具CertUtil计算MD5

命令CertUtil -hashfile "filename" MD5,我们再计算一下MD5.ilk的MD5值,如图:

MD5模拟_第6张图片

对比我们发现相同,所以MD5签名完成实现了。

 

你可能感兴趣的:(MD5模拟)