MD5 加密算法 - C++ 实现

MD5 加密算法 - C++ 实现

写在前头: 还在学习中! 整个文档写的很匆忙, 肯定还有很多不周到的地方. 欢迎在评论中提出你的宝贵意见!!

算法背景 Background

MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16个字符(BYTES))的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 中被加以规范。

将数据(如一段文字)运算变为另一固定长度值,是散列算法的基础原理。

1996年后被证实存在弱点,可以被加以破解,对于需要高度安全性的资料,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞攻击,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

— 摘自 维基百科 MD5 - 维基百科,自由的百科全书 (wikipedia.org)

说明 在 MD5 算法中, 数据的存储均使用 小端序. 即低位存放在内存地址小的位置. (英:little-endian)

Little-Endian.svg

基本结构 Structure

在这里插入图片描述

主要有一下几个构件组成.

MD5 算法

输入: 任意长度的明文

输出: 128-bit 的摘要 (digest)

H M D 5 H_{MD5} HMD5 (有点像DES)

输入: 512-bit 的明文分组, CV 链接向量(类似Key)

输出: 128-bit 结果

L o g i c _ F u n c t i o n i ( ) Logic\_Function_i() Logic_Functioni() 逻辑函数

输入: 三个 32-bit 字

输出: 一个 32-bit 字

以 类似CBC 的模式链接 (即每一组的加密会受到前一组的影响),

算法流程 Process

MD5 算法可以大致分为三个步骤: 消息预处理, 初始化缓冲区, 循环哈希.

  1. 消息预处理

    该步骤可以分为两个小点: ① 填充消息; ② 附加信息.

    • 填充消息

      填充输入的原消息, 使消息的长度 $Length(M’) \equiv 448 \pmod{512} $

      必须对输入消息进行填充, 即使消息原本就已经满足如上要求, 也要进行填充. 这是为了第二步操作预留 64-bit 空间.

      填充内容: 第一位为1, 其余位为零.

    • 附加信息

      在第一步预留出的空间里附加上明文消息的长度.

      原因: 增加攻击者伪造明文的难度. 伪造信息的长度必需要与原文长度相等(其实是同余)

      将消息长度 L e n g t h ( M ) ( m o d 2 64 ) Length(M) \pmod{2^{64}} Length(M)(mod264)小端序的形式附加到第一步预留的 64-bit 空间中.

    按以上步骤处理完消息之后, 每一组消息可以按 32-bit 一组, 被分为 16组字(Word) (512 = 32 * 16)

    在本文中被记作 M i [ j ] M_i[j] Mi[j] j表示字的组数.

  2. 初始化缓冲区

    算法使用 128-bit 的缓冲区存放中间结果和最终哈希值, 128-bit 可以看做 4 组 32-bit 字所占的比特位(128 = 4 * 32)

    被记作 B u f f e r A , B u f f e r B , B u f f e r C , B u f f e r D Buffer_A, Buffer_B,Buffer_C, Buffer_D BufferA,BufferB,BufferC,BufferD. 每个缓冲区都以小端的方式存储数据. 将 4 块 Buffer 组合起来记为链接向量 C V i CV_i CVi

    C V i = C V i − 1 CV_i = CV_{i - 1} CVi=CVi1

    C V 0 CV_0 CV0 被规定为常量, 如下表格所示.

    字节序存储内容
    小端序Buffer[4] = {0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210};
    大端序Buffer[4] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476};
  3. 循环哈希

    调用 H M D 5 ( M i , C V i ) H_{MD5}(M_i, CV_i) HMD5(Mi,CVi) 函数对每组消息分组进行哈希计算.

    在这里插入图片描述

    每一次 H M D 5 ( ) H_{MD5}() HMD5() 中有 4 轮结构相同的逻辑函数, 不同轮次间的唯一区别是参与计算的逻辑函数不同, 分别表示为 FGHI.

    此外, H M D 5 ( ) H_{MD5}() HMD5() 还有一个常数表 T, 常数表 T 为定义为 KaTeX parse error: Undefined control sequence: \abs at position 22: … 2^{32} \times \̲a̲b̲s̲{\sin{i}} (也就是 KaTeX parse error: Undefined control sequence: \abs at position 1: \̲a̲b̲s̲{\sin i} 的前 32-bit 小数.) (i 是弧度)

    在本文中把 H M D 5 H_{MD5} HMD5 的轮函数记为 R ( M i , B u f f e r , T [ ] , f u n c t i o n i ( ) ) R(M_i, Buffer, T[], function_i()) R(Mi,Buffer,T[],functioni()). 其中, 轮函数每次调用是都会读取缓存区中的数据. 而且不同轮次之间所调用的逻辑函数也是不一样的. 此外, 每一次调用轮函数会用到不同 16 组 T 元素(对应轮函数内部的中 16 次迭代).

    H M D 5 ( ) H_{MD5}() HMD5() 完成第四轮轮函数处理之后, 将得到的结果和输入的 $CV_i $按字(每 32-bit) 分组按位加. 得到最终输出结果 C V i + 1 CV_{i+1} CVi+1.

函数定义 Function define

轮函数(压缩函数)的具体实现

轮函数每次调用内部有 16 次迭代计算, 将轮函数当前迭代的次数记作 i

在这里插入图片描述

读取缓冲区中的数据, 将缓冲区按字 32-bit 分为 4 组, 记作 ABCD

  • 第一步

    BCD 暂时不动, A 有以下 x 层计算:

    • A + L o g i c _ F u n c t i o n 轮数 ( B , C , D ) A + Logic\_Function_{轮数}(B, C, D) A+Logic_Function轮数(B,C,D)

      F ( b , c , d ) = ( b ∧ c ) ∨ ( b ˉ ∧ d ) G ( b , c , d ) = ( b ∧ d ) ∨ ( c ∧ d ˉ ) H ( b , c , d ) = b ⊕ c ⊕ d I ( b , c , d ) = c ⊕ ( b ∨ d ˉ ) \begin{array}{l} F(b, c, d) &=& (b \wedge c) \vee(\bar{b} \wedge d) \\ G(b, c, d) &=& (b \wedge d) \vee(c \wedge \bar{d}) \\ H(b, c, d) &=& b \oplus c \oplus d \\ I(b, c, d) &=& c \oplus(b \vee \bar{d}) \end{array} F(b,c,d)G(b,c,d)H(b,c,d)I(b,c,d)====(bc)(bˉd)(bd)(cdˉ)bcdc(bdˉ)

    • A + M i [ k ] A + M_i[k] A+Mi[k] (k 受到当前轮数以及迭代次数 i 的影响)

    • A + T [ i ] A + T[i] A+T[i] (受到输入影响, 与当前轮数有关)

    • A 循环左移 s 位 (s 由一个常量表给出)

      ρ 1 ( i ) = i ρ 2 ( i ) = ( 1 + 5 i )   m o d   16 ρ 3 ( i ) = ( 5 + 3 i )   m o d   16 ρ 4 ( i ) = 7 i   m o d   16 \begin{array}{l} \rho_{1}(i) = i\\ \rho_{2}(i)=(1+5 i) \bmod 16 \\ \rho_{3}(i)=(5+3 i) \bmod 16 \\ \rho_{4}(i)=7 i \bmod 16 \\ \end{array} ρ1(i)=iρ2(i)=(1+5i)mod16ρ3(i)=(5+3i)mod16ρ4(i)=7imod16

    • A + B

  • 第二步

    对缓冲区中的四个字按字右循环位移 1 个字 即新的缓冲区 B u f f e r ′ = D ′ ∣ ∣ A ′ ∣ ∣ B ′ ∣ ∣ C ′ Buffer' = D' || A' || B' || C' Buffer=D∣∣A∣∣B∣∣C

也就是说, 一组消息的压缩要经过这样的过程:

轮次 1

/* [abcd k s i] a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
[ABCD  0 7  1][DABC  1 12  2][CDAB  2 17  3][BCDA  3 22  4]
[ABCD  4 7  5][DABC  5 12  6][CDAB  6 17  7][BCDA  7 22  8]
[ABCD  8 7  9][DABC  9 12 10][CDAB 10 17 11][BCDA 11 22 12]
[ABCD 12 7 13][DABC 13 12 14][CDAB 14 17 15][BCDA 15 22 16]

轮次 2

/* [abcd k s i] a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
[ABCD  1 5 17][DABC  6 9 18][CDAB 11 14 19][BCDA  0 20 20]
[ABCD  5 5 21][DABC 10 9 22][CDAB 15 14 23][BCDA  4 20 24]
[ABCD  9 5 25][DABC 14 9 26][CDAB  3 14 27][BCDA  8 20 28]
[ABCD 13 5 29][DABC  2 9 30][CDAB  7 14 31][BCDA 12 20 32]

轮次 3

/* [abcd k s i] a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
[ABCD  5 4 33][DABC  8 11 34][CDAB 11 16 35][BCDA 14 23 36]
[ABCD  1 4 37][DABC  4 11 38][CDAB  7 16 39][BCDA 10 23 40]
[ABCD 13 4 41][DABC  0 11 42][CDAB  3 16 43][BCDA  6 23 44]
[ABCD  9 4 45][DABC 12 11 46][CDAB 15 16 47][BCDA  2 23 48]

轮次 4

/* [abcd k s i] a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
[ABCD  0 6 49][DABC  7 10 50][CDAB 14 15 51][BCDA  5 21 52]
[ABCD 12 6 53][DABC  3 10 54][CDAB 10 15 55][BCDA  1 21 56]
[ABCD  8 6 57][DABC 15 10 58][CDAB  6 15 59][BCDA 13 21 60]
[ABCD  4 6 61][DABC 11 10 62][CDAB  2 15 63][BCDA  9 21 64]

代码实现 Implement

#include <iostream>
#include <string>
#include <stdint.h> // for uint* type
#include <limits.h> // for CHAR_BIT
using namespace std;

const uint32_t T[64] = {
	0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
	0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
	0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8,
	0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
	0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
	0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
	0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
	0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};

const unsigned int SHIFT[4][4]{
	{7, 12, 17, 22},
	{5, 9, 14, 20},
	{4, 11, 16, 23},
	{6, 10, 15, 21}};

const uint8_t PADDING[] = {
	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

inline uint32_t Left_Rotate32(uint32_t x, unsigned int num)
{
	num &= 31;
	return (x << num) | (x >> (-num & 31));
}

inline uint32_t Logic_Function(int Round_i, uint32_t b, uint32_t c, uint32_t d)
{
	switch (Round_i)
	{
	case 3:
		return c ^ (b | ~d);
	case 2:
		return b ^ c ^ d;
	case 1:
		return (b & d) | (c & ~d);
	case 0:
		return (b & c) | (~b & d);
	}
	return 0;
}

inline unsigned int Substituion(int Round_i, int i)
{
	switch (Round_i)
	{
	case 0:
		return i;
	case 1:
		return (1 + 5 * i) % 16;
	case 2:
		return (5 + 3 * i) % 16;
	case 3:
		return (7 * i) % 16;
	}
	return 0;
}
void Round_Function(int Round_i, uint32_t buffer[4], const uint32_t message_block[16])
{
	// Input:
	// Logic_Function				- provided by Round_i
	// T Table[Round_i*16 +(0-16)]	- provided by Round_i
	// Message Block
	// Buffer

	// Output:
	// void, but UPDATE buffer

	// Elements:
	// i = Round_i * 16 + i;
	// k = substituion(Round_i, i)
	for (int i = 0; i < 16; i++)
	{
		// 1. Calculation
		buffer[0] += Logic_Function(Round_i, buffer[1], buffer[2], buffer[3]);
		buffer[0] += message_block[Substituion(Round_i, i)];
		buffer[0] += T[Round_i * 16 + i];
		buffer[0] = Left_Rotate32(buffer[0], SHIFT[Round_i][i % 4]);
		buffer[0] += buffer[1];

		// 2. Rotate Buffer
		// Buffer right rotate by 1WORD(32-bit)
		uint32_t bufferCache = buffer[3];
		buffer[3] = buffer[2];
		buffer[2] = buffer[1];
		buffer[1] = buffer[0];
		buffer[0] = bufferCache;
	}
	return;
}

void Hash_MD5(uint32_t chain_vector[4], const uint32_t message_block[16])
{
	// traverse message_block, MD5 iteration
	uint32_t buffer[4];
	memcpy(buffer, chain_vector, 128 / CHAR_BIT);

	// For loop, i control Round function
	for (int i = 0; i < 4; i++)
		Round_Function(i, buffer, message_block);

	// Update chain_vector
	for (int i = 0; i < 4; i++)
		chain_vector[i] += buffer[i];
}

__uint128_t MD5(string _message)
{
	// 1. Pre-process message
	// padding & append length info
	// padding cached array
	// append messageBITcount, naturally store by litter-endian in C++
	uint64_t messageLength = _message.length();
	uint64_t messageBitCount = messageLength * CHAR_BIT;
	int blockCount = (messageBitCount + 64 - 1) / 512 + 1;
	uint8_t message[64 * blockCount];
	memcpy(message, _message.c_str(), messageLength);
	for (int i = messageLength, j = 0; i < (64 * blockCount - 8); i++)
		message[i] = PADDING[j++];

	memcpy(message + (64 * blockCount - 8), &messageBitCount, 64 / CHAR_BIT);

	uint32_t *messageBuffer = new uint32_t[16];

	// 2. Init Chain_vector_0 as a const
	// PAY ATTENTION TO THE IV ORDER!!
	// WRONG!! uint32_t res[4] = {0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210};
	uint32_t res[4] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476};
	for (int i = 0; i < blockCount; i++)
	{
		// Update Message_Block
		memcpy(messageBuffer, message + 64 * i, 64);
		Hash_MD5(res, messageBuffer);
	}

	// Output
	__uint128_t md5 = 0;
	for (int i = 0; i < 4; i++)
		md5 += (__uint128_t)res[i] << (i * 32);

	delete[] messageBuffer;
	return md5;
}

void MD5_Print(__uint128_t in)
{
	unsigned char *ptr = (unsigned char *)&in;
	for (int i = 0; i < 16; i++)
		printf("%02x", ptr[i]);
}

int main(void)
{
#ifdef LOCAL_COMPILE
	// freopen("in", "r", stdin);
	// freopen("out", "w", stdout);
#endif

	// user interface
	cout << "----------------- MD5 -----------------\n";
	cout << "INFO: Input a line of text, ended with an enter. (to end program input CTRL + C)\n";
	cout << "-----------------------\n";
	string str;
	while (1)
	{
		cout << "text: ";
		getline(cin, str);
		__uint128_t md5 = MD5(str);

		cout << "result: ";
		MD5_Print(md5);

		cout << "\n-----------------------\n";
	}

	return 0;
}

代码测试 Test

参考 Rivest 在 RFC 1321 规范中给出的结果, 对上述代码进行测试.

----------------- MD5 -----------------
INFO: Input a line of text, ended with an enter. (to end program input CTRL + C)
-----------------------
text:
result: d41d8cd98f00b204e9800998ecf8427e
-----------------------
text: a
result: 0cc175b9c0f1b6a831c399e269772661
-----------------------
text: abc
result: 900150983cd24fb0d6963f7d28e17f72
-----------------------
text: message digest
result: f96b697d7cb7938d525a2f31aaf161d0
-----------------------
text: abcdefghijklmnopqrstuvwxyz
result: c3fcd3d76192e4007dfb496cca67e13b
-----------------------
text: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
result: d174ab98d277d9f5a5611c2c9f419d9f
-----------------------
text: 12345678901234567890123456789012345678901234567890123456789012345678901234567890
result: 57edf4a22be3c955ac49da2e2107b67a
-----------------------

与规范中给出的结果一致, 测试通过!

MD5 test suite:
MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0
MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
MD5 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") =
d174ab98d277d9f5a5611c2c9f419d9f
MD5 ("123456789012345678901234567890123456789012345678901234567890123456
78901234567890") = 57edf4a22be3c955ac49da2e2107b67a

参考资料 Reference

字节顺序 - 维基百科,自由的百科全书 (wikipedia.org)

MD5 - 维基百科,自由的百科全书 (wikipedia.org)

RFC 1321: The MD5 Message-Digest Algorithm (rfc-editor.org)

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
md5c++实现md5算法. 开发平台 Ubuntu14.04 运行 sudo get-apt install g++ make ./md5_test md5简介 消息摘要算法第五版(英语:Message-Digest Algorithm 5,缩写为MD5),是当前计算机领域用于确保信息传输完整一致而广泛使用的散列算法之一(又译哈希算法、摘要算法等),主流编程语言普遍已有MD5实现。将数据 (如一段文字)运算变为另一固定长度值,是散列算法的基础原理,MD5的前身有MD2、MD3和MD4。MD5由MD4、MD3、MD2改进而来,主要增强算法复杂度和不可逆性。目前,MD5算法因其普遍、稳定、快速的特点,仍广泛应用于普通 数据的错误检查领域。例如在一些BitTorrent下载中,软件将通过计算MD5检验下载到的文件片段的完整性。MD5已经广泛使用在为文件传输提供一定的可靠性方面。例如,服务器预先提供一个MD5校验和,用户下载完文件以后, 用MD5算法计算下载文件的MD5校验和,然后通过检查这两个校验和是否一致,就能判断下载的文件是否出错。MD5是输入不定长度信息,输出固定长度128-bits的算法。经过程序流程,生成四个32位数据,最后联合起来成为一个 128-bits散列。基本方式为,求余、取余、调整长度、与链接变量进行循环运算。得出结果。 md5算法描述 假设输入信息(input message)的长度为b(bit),我们想要产生它的报文摘要,在此处b为任意的非负整数:b也可能为0,也不一定为8的整数倍,且可能是任意大的长度。设该信息的比特流表示如下: M[0] M[1] M[2] ... M[b-1] 计算此信息的报文摘要需要如下5步: 1.补位 信息计算前先要进行位补位,设补位后信息的长度为LEN(bit),则LEN%512 = 448(bit),即数据扩展至 K * 512 + 448(bit)。即K * 64+56(byte),K为整数。补位操作始终要执行,即使补位前信息的长度对512求余的结果是448。具体补位操作:补一个1,然后补0至满足上述要求。总共最少要补1bit,最多补512bit。 2.尾部加上信息长度 将输入信息的原始长度b(bit)表示成一个64-bit的数字,把它添加到上一步的结果后面(在32位的机器上,这64位将用2个字来表示并且低位在前)。当遇到b大于2^64这种极少的情况时,b的高位被截去,仅使用b的低64位。经过上面两步,数据就被填补成长度为512(bit)的倍数。也就是说,此时的数据长度是16个字(32byte)的整数倍。此时的数据表示为: M[0 ... N-1] 其中的N是16的倍数。 3.初始化缓存区 用一个四个字的缓冲器(A,B,C,D)来计算报文摘要,A,B,C,D分别是32位的寄存器,初始化使用的是十六进制表示的数字,注意低字节在前: 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.转换 首先定义4个辅助函数,每个函数的输入是三个32位的字,输出是一个32位的字: F(X,Y,Z) = XY v not(X) Z G(X,Y,Z) = XZ v Y not(Z) H(X,Y,Z) = X xor Y xor Z I(X,Y,Z) = Y xor (X v not(Z)) FF(a,b,c,d,Mj,s,ti)表示 a = b + ((a + F(b,c,d) + Mj + ti) << s) GG(a,b,c,d,Mj,s,ti)表示 a = b + ((a + G(b,c,d) + Mj + ti) << s) HH(a,b,c,d,Mj,s,ti)表示 a = b + ((a + H(b,c,d) + Mj + ti) << s) Ⅱ(a,b,c,d,Mj,s,ti)表示 a = b + ((a + I(b,c,d) + Mj + ti) << s) 这四轮(64步)是: 第一轮 FF(a,b,c,d,M0,7,0xd76aa478) FF(d,a,b,c,M1,12,0xe8c7b756) FF(c,d,a,b,M2,17,0x242070db) FF(b,c,d,a,M3,22,0xc1bdceee) FF(a,b,c,d,M4,7,0xf57c0faf) FF(d,a,b,c,M5,12,0x4787c62a) FF(c,d,a,b,M6,17,0xa8304613) FF(b,c,d,a,M7,22,0xfd469501) FF(a,b,c,d,M8,7,0x698098d8) FF(d,a,b,c,M9,12,0x8b44f7af) FF(c,d,a,b,M10,17,0xffff5bb1) FF(b,c,d,a,M11,22,0x895cd7be) FF(a,b,c,d,M12,7,0x6b901122) FF(d,a,b,c,M13,12,0xfd987193) FF(c,d,a,b,M14,17,0xa679438e) FF(b,c,d,a,M15,22,0x49b40821) 第二轮 GG(a,b,c,d,M1,5,0xf61e2562) GG(d,a,b,c,M6,9,0xc040b340) GG(c,d,a,b,M11,14,0x265e5a51) GG(b,c,d,a,M0,20,0xe9b6c7aa) GG(a,b,c,d,M5,5,0xd62f105d) GG(d,a,b,c,M10,9,0x02441453) GG(c,d,a,b,M15,14,0xd8a1e681) GG(b,c,d,a,M4,20,0xe7d3fbc8) GG(a,b,c,d,M9,5,0x21e1cde6) GG(d,a,b,c,M14,9,0xc33707d6) GG(c,d,a,b,M3,14,0xf4d50d87) GG(b,c,d,a,M8,20,0x455a14ed) GG(a,b,c,d,M13,5,0xa9e3e905) GG(d,a,b,c,M2,9,0xfcefa3f8) GG(c,d,a,b,M7,14,0x676f02d9) GG(b,c,d,a,M12,20,0x8d2a4c8a) 第三轮 HH(a,b,c,d,M5,4,0xfffa3942) HH(d,a,b,c,M8,11,0x8771f681) HH(c,d,a,b,M11,16,0x6d9d6122) HH(b,c,d,a,M14,23,0xfde5380c) HH(a,b,c,d,M1,4,0xa4beea44) HH(d,a,b,c,M4,11,0x4bdecfa9) HH(c,d,a,b,M7,16,0xf6bb4b60) HH(b,c,d,a,M10,23,0xbebfbc70) HH(a,b,c,d,M13,4,0x289b7ec6) HH(d,a,b,c,M0,11,0xeaa127fa) HH(c,d,a,b,M3,16,0xd4ef3085) HH(b,c,d,a,M6,23,0x04881d05) HH(a,b,c,d,M9,4,0xd9d4d039) HH(d,a,b,c,M12,11,0xe6db99e5) HH(c,d,a,b,M15,16,0x1fa27cf8) HH(b,c,d,a,M2,23,0xc4ac5665) 第四轮 Ⅱ(a,b,c,d,M0,6,0xf4292244) Ⅱ(d,a,b,c,M7,10,0x432aff97) Ⅱ(c,d,a,b,M14,15,0xab9423a7) Ⅱ(b,c,d,a,M5,21,0xfc93a039) Ⅱ(a,b,c,d,M12,6,0x655b59c3) Ⅱ(d,a,b,c,M3,10,0x8f0ccc92) Ⅱ(c,d,a,b,M10,15,0xffeff47d) Ⅱ(b,c,d,a,M1,21,0x85845dd1) Ⅱ(a,b,c,d,M8,6,0x6fa87e4f) Ⅱ(d,a,b,c,M15,10,0xfe2ce6e0) Ⅱ(c,d,a,b,M6,15,0xa3014314) Ⅱ(b,c,d,a,M13,21,0x4e0811a1) Ⅱ(a,b,c,d,M4,6,0xf7537e82) Ⅱ(d,a,b,c,M11,10,0xbd3af235) Ⅱ(c,d,a,b,M2,15,0x2ad7d2bb) Ⅱ(b,c,d,a,M9,21,0xeb86d391)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值