算法原理概述
MD5信息摘要算法,( Message-Digest Algorithm 5),是一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。

MD5算法使用little-endian(小端模式,即低位字节存在内存低地址),输入任意不定长度信息,算法都首先将其以512-bit进行分组(不足补位),每个512-bit的分组都和四个32-bit的数据一起,进行4次大循环(共64次迭代),并更新这四个32-bit数据。当所有分组都经过上述操作后,最后得到的四个32-bit联合输出固定的128位信息摘要。

算法基本流程
算法的基本过程为:填充、分块、缓冲区初始化、循环压缩、得出结果。

总体结构
MD5算法的总体结构如上图所示,结构应该还算是比较清晰的。与上次的DES加密算法不同,MD5的主体只有一个压缩函数,我们只需要写好这个函数中的内容,其他都比较简单。

我认为最主要的关键点有两个:

如何对于数据的填充和分块,特别是当输入是以一个文件而不是一个短字符串的形式时;
如何进行算法中的64次迭代。
下面对于算法的流程进行一些简单的说明:

数据填充和分块
在长度为 K-bit 的原始消息数据尾部填充长度为 P-bit 的标识 100…0(一位1后面接若干位0),1 < P < 512(即至少要填充1个bit),使得填充后的消息位数为:K + P \equiv 448 (mod 512). 其中,如果当 K \equiv448 (mod 512) 时,需要P = 512。

再向上述填充好的消息尾部附加 K 值的低64位(即K mod 2^{64}), 最后得到一个总长度位数为K + P+ 64 \equiv 0 (mod 512) 的消息。

得到填充完毕后的消息以后,我们就可以将其恰好分为 L 个 512-bit 的分组。(同时每个分组也可以再细分成16个 32-bit 的小分组,在程序中我们可以使用一个数组保存起来,这在后续的压缩循环中会使用到)

初始化
初始化一个128-bit 的 MD 缓冲区,初始记为CV0,可以表示成4个32-bit 寄存器(A, B, C, D),后续的迭代始终在 MD 缓冲区进行,最后一步的128-bit 输出即为MD5算法的结果。

CV0的初始值为IV。寄存器(A, B, C, D) 置16进制初值作为初始向量IV,并采用小端存储(little-endian) 的存储结构(Intel x86系列CPU原本就采用Little Endian 方式存储):

A= 0x67452301
B= 0xEFCDAB89
C= 0x98BADCFE
D= 0x10325476
压缩循环
经过上述初始化步骤后,就可以开始执行算法的总体部分了,也就是压缩函数。

压缩函数每次都从CV(即上文提到的 128-bit 缓冲区)输入128位,从之前分好的消息分组中按顺序输入512位,完成4轮循环后,得到该轮压缩的128位结果,加到原来的缓冲区中,然后用下一分组继续上述步骤。(说具体一点,就是函数每次都从缓冲区(A, B, C, D)拿到四个数a, b, c, d,然后对于a, b, c, d进行压缩循环操作,把最后得到的结果a, b, c, d加到原来的(A, B, C, D)中,下一次函数执行再从(A, B, C, D)中拿数据)

上文提到函数共4轮循环,其中每轮循环分别固定不同的生成函数F, G, H, I,结合指定的T表元素T[]和消息分组的不同部分X[]做16次迭代运算, 生成下一轮循环的输入。即4轮循环共有64次迭代运算。每轮的逻辑如下:

生成函数g

X[k]
定义为当前处理消息分组的第k个(k= 0…15) 32位字(上文曾提到要将512-bit的分组再分为16个 32-bit 的小分组)。

k值的选取,设 j = i - 1:

第1轮循环:k = j,即顺序使用X[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
第2轮循环:k = (1 + 5j) mod 16,即顺序使用X[1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12]
第3轮循环:k = (5 + 3j) mod 16,即顺序使用X[5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2]
第4轮循环:k= 7j mod 16,即顺序使用X[0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9]
T[i]
T 表的第i个元素,每个元素为32位字;T表总共有64个元素,也称为加法常数。具体数值由于篇幅限制不再列出,具体可见程序。

CLS(s)
循环左移s位。64次迭代中,每一次迭代左移的位数s如下:

s[ 1…16] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 }
s[17…32] = { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 }
s[33…48] = { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 }
s[49…64] = { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }
加法
循环中的加号表示的是模2^{32} 加法。

模块分解
按照算法的流程以及实际程序编写的需要,总体上我们可以分解得到以下模块:

读取文件和补位模块
为了更贴合实际情况,本程序实现对于文本文件中内容的散列值计算,即可以支持任意位数字符。

我们无需按照死板的思维,一开始就马上统计文件所包含的比特数,然后马上计算出该如何如何进行补位。实际上,正是由于文件指针的存在,使得我们可以对于整个文件进行实时处理,也就是说我们只需要维护一个count变量,其初始值等于0。而我们每次读取文件64个字节(8*64=512),然后对于读到的这个消息分组即时进行压缩,同时累计位数到count上,直到我们读到文件的末尾,意味着此时不足512字节了。注意,由于我们一直在统计读到的位数,那么此时我们就得到了文件总大小,然后就可以对于最后的不足512bit的部分进行相应的补位操作了。

本文对应的程序就使用了上述方法。

主循环模块
主循环就是上文提到的压缩循环部分,输入为文件读取模块得到的512bit文本数据和上一轮循环得到的CV值(A, B, C, D),经过64次迭代后更新CV值,用于下一次新的循环。最终,随着文本读取的结束,也就得到了最终结果。

输入输出模块
输入输出模块就比较简单了,由用户输入要进行处理的文件,最后输出得到的结果(32个16进制数的形式)。

数据结构
本程序中没有使用到特殊的数据结构。只需要对于算法中会使用到的一些表和运算进行预先记录,可方便后续的操作,例如前文提到的生成函数、T表、循环左移的s值等等。

C语言源代码
注释已经比较详细,这里不再进行说明。

md5.h

#define _CRT_SECURE_NO_WARNINGS#ifndef MD5_H
#define MD5_H#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>// MD5压缩函数4轮循环中使用的生成函数,每轮不同
#define F(b, c, d) (((b) & (c)) | ((~b) & (d)))
#define G(b, c, d) (((b) & (d)) | ((c) & (~d)))
#define H(b, c, d) ((b) ^ (c) ^ (d))
#define I(b, c, d) ((c) ^ ((b) | (~d)))// 循环左移
#define LEFTROTATE(num, n) (((num) << n) | ((num >> (32 - n))))// T表,32位字,一共有64个元素,对应64次迭代,也成为加法常数
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, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };// 64次迭代运算采用的左循环移位的s值
const uint32_t S[64] = { 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 };// 两个工具函数
void int2byte(uint32_t val, uint8_t *bytes)
{bytes[0] = (uint8_t)val;bytes[1] = (uint8_t)(val >> 8);bytes[2] = (uint8_t)(val >> 16);bytes[3] = (uint8_t)(val >> 24);
}uint32_t byte2int(const uint8_t *bytes)
{return (uint32_t)bytes[0]| ((uint32_t)bytes[1] << 8)| ((uint32_t)bytes[2] << 16)| ((uint32_t)bytes[3] << 24);
}// MD5主函数
int MD5(const uint8_t* filepath, uint8_t *result) {FILE *fp = NULL;uint8_t buffer[64];uint8_t* temp = NULL;size_t count = 0, offset, i; // count用于记录总长度,补位的时候需要用到uint32_t X[16];int flag = 0;if ((fp = fopen(filepath, "rb+")) == NULL) {printf("[ERROR] File in %s not found.", filepath);return 0;}// MD缓冲区CV,迭代在缓冲区进行uint32_t A, B, C, D;// 初始向量IV,采用小端存储(Intel x86系列原本就采用了Little Endian方式存储)A = 0x67452301;B = 0xEFCDAB89;C = 0x98BADCFE;D = 0X10325476;while (!feof(fp)) {memset(buffer, 0, sizeof(buffer));// fread函数返回读取的次数,设定每次读取一个字符,就可以知道字符长度了int len = fread(buffer, 1, 64, fp);// 更新文件总长度count += len;// 当读取文件到末尾时,意味着需要进行补位操作了,此时读到的len可能不足512bit,也可能刚好等于512bitif (feof(fp)) {flag = 1;// 因为恰好等于448bit不行,所以new_len直接等于len+1int new_len;for (new_len = len + 1; new_len % 64 != 56; new_len++);// 还要增加64bittemp = (uint8_t*)malloc(new_len + 8);memcpy(temp, buffer, len);// 填充1000...0temp[len] = 0x80;for (offset = len + 1; offset < new_len; offset++)temp[offset] = 0;// 在末尾再附加总长度count的低64位,由于这里的count单位是byte,所以要乘以8int2byte(count * 8, temp + new_len);int2byte(count >> 29, temp + new_len + 4); //参考了其他代码,count>>29相当于count*8>>32,但可以避免值溢出len = new_len;}// 虽然每次只读取512bit,但是还是采用这样的方式,可以防止最后的一次由于补位导致可能出现的 len > 512bit 的情况(此时就要分两次了)for (offset = 0; offset < len; offset += 64) {// 读到结尾时,我们把补位后的数据存在了temp中,为了处理的统一,将temp中的数据保存到buffer上if (flag == 1) {memcpy(buffer, temp + offset, 64);}// 保存512位的每32位分组,在X[k]时会用到for (int i = 0; i < 16; i++) {X[i] = byte2int(buffer + i * 4);}uint32_t a, b, c, d, temp, g, k;a = A;b = B;c = C;d = D;// 主循环,共四轮,每轮16次迭代,共64次迭代for (i = 0; i < 64; i++) {if (i < 16) {g = F(b, c, d);k = i;}else if (i < 32) {g = G(b, c, d);k = (1 + 5 * i) % 16;}else if (i < 48) {g = H(b, c, d);k = (5 + 3 * i) % 16;}else {g = I(b, c, d);k = (7 * i) % 16;}temp = d;d = c;c = b;b = b + LEFTROTATE((a + g + X[k] + T[i]), S[i]);a = temp;}A += a;B += b;C += c;D += d;}}// 文件读取结束,释放内存free(temp);// 把128位的最终结果转化为字节形式int2byte(A, result);int2byte(B, result + 4);int2byte(C, result + 8);int2byte(D, result + 12);return 1;
}#endif // !MD5_H

main.c

#include "md5.h"int main() {printf("\n================== MD5 Message-Digest Algorithm ==================\n\n");printf("[INPUT] Enter the filepath: ");// 输入文件路径char filepath[256];scanf("%s", filepath);// 128位结果uint8_t result[16];int i;// 返回值不等于1表示有错误if (MD5(filepath, result) == 1) {printf("\n[INFO] The result is: ");//将字节通过无符号十六进制输出for (i = 0; i < 16; i++) {printf("%2.2x", result[i]);}printf("\n");}system("pause");return 0;
}

MD5算法的C语言实现相关推荐

  1. aes算法实现c语言_消息摘要算法MD5图解及C语言实现

    前言 最近看了很多关于消息摘要算法这方面的资料,既有CSDN上面各路大神写的文章,也有这些算法的标准文档.有的讲的比较啰嗦,有的给出来的代码是直接调库的.我想写一篇文章,帮助自己理清思路,利用图解简明 ...

  2. MD5密码哈希算法(c语言实现)

    MD5密码哈希算法(c语言实现) 本人为大学生在校生,所写源码有诸多不足,希望各位多多指正.编译器为Dev C++ #include<bits/stdc++.h> using namesp ...

  3. c语言压缩并加密算法,C语言压缩文件和用MD5算法校验文件完整性的实例教程

    使用lzma SDK对7z文件简单解压缩有时候我们只需要单纯对lzma算法压缩的7z文件进行解压,有时需要在嵌入式设备上解压,使用p7zip虽然支持多种格式,但是不容易裁剪,使用lzma SDK是首选 ...

  4. SHA1/MD5散列算法实现(C语言)

    一.实验目的   通过实际编程了解MD5算法的加密和解密过程,加深对Hash算法的认识.   二.实验原理  Hash函数是将任意长的数字串转换成一个较短的定长输出数字串的函数,输出的结果称为Hash ...

  5. MD5算法详解及实现(C语言)

    其他现代密码学算法详解及实现见专栏合集~ MD5算法 算法过程 (i)消息填充 首先填充消息,使它的长度比512的整数倍少64位(这64位用来记录原数据长度).填充的内容由一个1和后续的0组成.必须进 ...

  6. MD5算法之C#程序

    MD5算法比较特别,最适合用汇编语言来写,好多高级语言对之无能无力或效率极低. 比如我最开始尝试用Python和Euphoria编写,发现不太容易.相比而言,C#作为C家簇 中新兴的一门.net语言, ...

  7. 【建议收藏】MD5 算法的Java Bean

    MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输 ...

  8. MD5 算法描述及实现

    MD5 算法的原理及实现 章节目录 简介 算法描述 实现 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 简介## Wiki对其 ...

  9. MD5算法之C#程序 MD5算法描述

    MD5算法之C#程序 MD5算法描述 MD5算法描述 当我要写一个MD5算法的程序时,发现中英文的语言描述都有一些不确切的地方,某些个细节 讲得不清楚,或者说很费解.最后不得不拿出C语言的源程序来调试 ...

最新文章

  1. Unshielded Twisted Pair - CAT5, 5e 6
  2. Python进阶1——一摞纸牌
  3. Java继承_Hachi君浅聊Java三大特性之 封装 继承 多态
  4. Git 简易食用指南 v2.0
  5. java学习笔记--java中的基本数组[5]
  6. 15年软件开发经验总结
  7. Feature Extractor[DenseNet]
  8. 如何提高QnA maker机器人训练中文语义理解的能力
  9. wcg总决赛_关于总决赛
  10. Java 7:WatchService
  11. Apache-DBUtils实现CRUD操作,已封装的API实现jdbc对数据库进行操作
  12. SOFAStack的前世今生
  13. 【计算机网络】电路交换网络中,每条电路独占其经过的物理链路?
  14. 关于Markdown编辑器添加使用锚点的问题
  15. 伊万卡·特朗普的迈阿密豪华公寓楼接受加密付款
  16. 【Markdown简单语法练习】
  17. 中兴a2018拆机图片_中兴a2s拆机视频
  18. SPA是什么及原生js实现简易SPA单页面
  19. 数据挖掘场景-发票虚开
  20. c语言求两个字符串的交集,用c语言求两个集合的交集,并集,差集

热门文章

  1. 【微信篇】微信手机号存储问题
  2. 51单片机中断与定时器计数器,基于普中科技HC6800-ESV2.0
  3. gow在windows上使用的linux shell命令,Windows模拟linux终端工具Cmder+Gow
  4. 深入Go语言网络库的基础实现
  5. 网络字节序与地址转换函数
  6. 集成电路总线(Inter-Integrated Circuit, I2C)
  7. 关于单相全桥不控整流电路的一点思考(一):阻性负载下的二极管导通问题
  8. shell(18) : 替换文件内容
  9. sql镶嵌查询_超实用的SQL语句之嵌套查询
  10. Gartner权威认可 | 悬镜安全获评SCA和BAS技术代表厂商