相关文章:
最近陆续造了一批哈希算法的轮子,包括MD家族(包括MD2/MD4/MD5), SHA1, SHA2家族(SHA256/SHA384/SHA512),SHA3家族以及国密SM3算法。
原来打算将每一个算法都详细分析并实现,现在看来,这个工作短时间可能无法完成,所以先将源码发上来。
这部分实现的源码完全参考官方文档的算法描述,连变量名也尽可能和官方文档中的变量保持一致,方便学习。
另外, 代码封装的MD4哈希调用接口参考了openssl官方的接口,完全兼容,无缝对接。会使用这里的接口,就会使用openssl的库函数接口,甚至连代码都不需要修改。
除了实现的源码外,还另外附带了一个测试例子,这个测试例子不仅仅是用于测试哈希算法的实现是否正确,还可以提供了"-f"/"-s"等选项用于对任意文件和字符串进行哈希,因此作为一个工具使用,类似系统内置的md5sum/sha1sum。
MD4实现源码
1. 头文件md4.c
/*
* @ file: md4.h
* @ description: header file for md4.c
* @ author: Gu Yongqiang
* @ blog: https://blog.csdn.net/guyongqiangx
*/
#ifndef __ROCKY_MD4__H
#define __ROCKY_MD4__H
#define ERR_OK 0
#define ERR_ERR -1 /* generic error */
#define ERR_INV_PARAM -2 /* invalid parameter */
#define ERR_TOO_LONG -3 /* too long */
#define ERR_STATE_ERR -4 /* state error */
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef struct md4_context {
/* message total length in bytes */
uint64_t total;
/* intermedia hash value for each block */
struct {
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
uint32_t e;
}hash;
/* last block */
struct {
uint32_t used; /* used bytes */
uint8_t buf[64]; /* block data buffer */
}last;
}MD4_CTX;
/* https://www.openssl.org/docs/man1.1.0/man3/MD5_Init.html */
int MD4_Init(MD4_CTX *c);
int MD4_Update(MD4_CTX *c, const void *data, unsigned long len);
int MD4_Final(unsigned char *md, MD4_CTX *c);
unsigned char *MD4(const unsigned char *d, unsigned long n, unsigned char *md);
#endif
2. 代码文件md4.c
/*
* @ file: md4.c
* @ description: implementation for the MD4 Message-Digest Algorithm
* @ author: Gu Yongqiang
* @ blog: https://blog.csdn.net/guyongqiangx
*/
#include <stdio.h>
#include <string.h>
#include "utils.h"
#include "md4.h"
//#define DEBUG
#ifdef DEBUG
#define DBG(...) printf(__VA_ARGS__)
#define DUMP_BLOCK_DATA 1
#define DUMP_BLOCK_HASH 1
#define DUMP_ROUND_DATA 1
#else
#define DBG(...)
#define DUMP_BLOCK_DATA 0
#define DUMP_BLOCK_HASH 0
#define DUMP_ROUND_DATA 0
#endif
#define MD4_BLOCK_SIZE 64 /* 512 bits = 64 bytes */
#define MD4_LEN_SIZE 8 /* 64 bits = 8 bytes */
#define MD4_LEN_OFFSET (MD4_BLOCK_SIZE - MD4_LEN_SIZE)
#define MD4_DIGEST_SIZE 16 /* 128 bits = 16 bytes */
#define MD4_PADDING_PATTERN 0x80
#define MD4_ROUND_NUM 64
#define HASH_BLOCK_SIZE MD4_BLOCK_SIZE
#define HASH_LEN_SIZE MD4_LEN_SIZE
#define HASH_LEN_OFFSET MD4_LEN_OFFSET
#define HASH_DIGEST_SIZE MD4_DIGEST_SIZE
#define HASH_PADDING_PATTERN MD4_PADDING_PATTERN
#define HASH_ROUND_NUM MD4_ROUND_NUM
typedef uint32_t (*md4_func)(uint32_t x, uint32_t y, uint32_t z);
/* MD4 Round Constants, refer rfc1320, section 3.4 */
static uint32_t T[3] =
{
0x00000000, /* Round 1( 0 ~ 15), placeholder of T[idx/16] in MD4_OP */
0x5A827999, /* Round 2(16 ~ 31), square root of 2 */
0x6ED9EBA1, /* Round 3(32 ~ 47), square root of 3 */
};
/* ROTate Left (circular left shift) */
static uint32_t ROTL(uint32_t x, uint8_t shift)
{
return (x << shift) | (x >> (32 - shift));
}
/*
* F/G/H definition, refer rfc1320, section 3.4
*/
/*
* Condition
* In each bit position, F acts as a conditional:
* if X then Y else Z.
*/
static uint32_t F(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) | ((~x) & z);
}
/*
* Majority
* In each bit position, G acts as a majority function:
* if at least two of X, Y, Z are on, then G has a "1" bit in that bit position, else G has a "0" bit.
*/
static uint32_t G(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) | (x & z) | (y & z);
}
/*
* Parity
* H is the bit-wise XOR or "parity" function
*/
static uint32_t H(uint32_t x, uint32_t y, uint32_t z)
{
return x ^ y ^ z;
}
/* MD4 Functions */
static md4_func g[3] =
{
F, /* 0 ~ 15 operations */
G, /* 16 ~ 31 operations */
H /* 32 ~ 47 operations */
};
int MD4_Init(MD4_CTX *c)
{
if (NULL == c)
{
return ERR_INV_PARAM;
}
memset(c, 0, sizeof(MD4_CTX));
/* MD4 Initial Value, refer rfc1320, section 3.3 */
c->hash.a = 0x67452301; /* little endian */
c->hash.b = 0xEFCDAB89;
c->hash.c = 0x98BADCFE;
c->hash.d = 0x10325476;
c->total = 0;
c->last.used = 0;
return ERR_OK;
}
static int MD4_PrepareScheduleWord(const unsigned char *block, uint32_t *X)
{
uint32_t i;
uint32_t *temp;
if ((NULL == block) || (NULL == X))
{
return ERR_INV_PARAM;
}
temp = (uint32_t *)block;
for (i=0; i<HASH_BLOCK_SIZE/4; i++)
{
X[i] = le32toh(temp[i]);
}
return ERR_OK;
}
#if (DUMP_ROUND_DATA == 1)
#define MD4_OP(a,b,c,d,k,s) \
a = ROTL(a + (g[idx/16])(b, c, d) + X[k] + T[idx/16], s); \
DBG(" %02d: a=0x%08x, b=0x%08x, c=0x%08x, d=0x%08x, X=0x%08x, T=0x%08x\n", idx, a, b, c, d, X[k], T[idx/16]); \
idx ++;
#else
#define MD4_OP(a,b,c,d,k,s) \
a = ROTL(a + (g[idx/16])(b, c, d) + X[k] + T[idx/16], s); \
idx ++;
#endif
/* Process Message in 16-Word Blocks, refer rfc1320, section 3.4 */
static int MD4_ProcessBlock(MD4_CTX *ctx, const void *block)
{
uint32_t X[HASH_BLOCK_SIZE/4];
uint32_t A, B, C, D;
uint32_t idx;
if ((NULL == ctx) || (NULL == block))
{
return ERR_INV_PARAM;
}
#if (DUMP_BLOCK_DATA == 1)
DBG("---------------------------------------------------------\n");
DBG(" BLOCK: %llu\n", ctx->total/HASH_BLOCK_SIZE);
DBG(" DATA:\n");
print_buffer(block, HASH_BLOCK_SIZE, " ");
#endif
#if (DUMP_BLOCK_HASH == 1)
DBG(" (LE)IV: %08x %08x %08x %08x\n",
ctx->hash.a, ctx->hash.b, ctx->hash.c, ctx->hash.d);
#endif
/* Copy block into X */
MD4_PrepareScheduleWord(block, X);
A = ctx->hash.a;
B = ctx->hash.b;
C = ctx->hash.c;
D = ctx->hash.d;
idx = 0;
/* Round 1 */
MD4_OP(A, B, C, D, 0, 3); MD4_OP(D, A, B, C, 1, 7); MD4_OP(C, D, A, B, 2, 11); MD4_OP(B, C, D, A, 3, 19);
MD4_OP(A, B, C, D, 4, 3); MD4_OP(D, A, B, C, 5, 7); MD4_OP(C, D, A, B, 6, 11); MD4_OP(B, C, D, A, 7, 19);
MD4_OP(A, B, C, D, 8, 3); MD4_OP(D, A, B, C, 9, 7); MD4_OP(C, D, A, B, 10, 11); MD4_OP(B, C, D, A, 11, 19);
MD4_OP(A, B, C, D, 12, 3); MD4_OP(D, A, B, C, 13, 7); MD4_OP(C, D, A, B, 14, 11); MD4_OP(B, C, D, A, 15, 19);
/* Round 2 */
MD4_OP(A, B, C, D, 0, 3); MD4_OP(D, A, B, C, 4, 5); MD4_OP(C, D, A, B, 8, 9); MD4_OP(B, C, D, A, 12, 13);
MD4_OP(A, B, C, D, 1, 3); MD4_OP(D, A, B, C, 5, 5); MD4_OP(C, D, A, B, 9, 9); MD4_OP(B, C, D, A, 13, 13);
MD4_OP(A, B, C, D, 2, 3); MD4_OP(D, A, B, C, 6, 5); MD4_OP(C, D, A, B, 10, 9); MD4_OP(B, C, D, A, 14, 13);
MD4_OP(A, B, C, D, 3, 3); MD4_OP(D, A, B, C, 7, 5); MD4_OP(C, D, A, B, 11, 9); MD4_OP(B, C, D, A, 15, 13);
/* Round 3 */
MD4_OP(A, B, C, D, 0, 3); MD4_OP(D, A, B, C, 8, 9); MD4_OP(C, D, A, B, 4, 11); MD4_OP(B, C, D, A, 12, 15);
MD4_OP(A, B, C, D, 2, 3); MD4_OP(D, A, B, C, 10, 9); MD4_OP(C, D, A, B, 6, 11); MD4_OP(B, C, D, A, 14, 15);
MD4_OP(A, B, C, D, 1, 3); MD4_OP(D, A, B, C, 9, 9); MD4_OP(C, D, A, B, 5, 11); MD4_OP(B, C, D, A, 13, 15);
MD4_OP(A, B, C, D, 3, 3); MD4_OP(D, A, B, C, 11, 9); MD4_OP(C, D, A, B, 7, 11); MD4_OP(B, C, D, A, 15, 15);
ctx->hash.a += A;
ctx->hash.b += B;
ctx->hash.c += C;
ctx->hash.d += D;
#if (DUMP_BLOCK_HASH == 1)
DBG(" (LE)OUT: %08x %08x %08x %08x\n",
ctx->hash.a, ctx->hash.b, ctx->hash.c, ctx->hash.d);
#endif
return ERR_OK;
}
int MD4_Update(MD4_CTX *c, const void *data, unsigned long len)
{
uint32_t copy_len = 0;
if ((NULL == c) || (NULL == data))
{
return ERR_INV_PARAM;
}
/* has used data */
if (c->last.used != 0)
{
/* less than 1 block in total, combine data */
if (c->last.used + len < HASH_BLOCK_SIZE)
{
memcpy(&c->last.buf[c->last.used], data, len);
c->last.used += len;
return ERR_OK;
}
else /* more than 1 block */
{
/* process the block in context buffer */
copy_len = HASH_BLOCK_SIZE - c->last.used;
memcpy(&c->last.buf[c->last.used], data, copy_len);
MD4_ProcessBlock(c, &c->last.buf);
c->total += HASH_BLOCK_SIZE;
data = (uint8_t *)data + copy_len;
len -= copy_len;
/* reset context buffer */
memset(&c->last.buf[0], 0, HASH_BLOCK_SIZE);
c->last.used = 0;
}
}
/* less than 1 block, copy to context buffer */
if (len < HASH_BLOCK_SIZE)
{
memcpy(&c->last.buf[c->last.used], data, len);
c->last.used += len;
return ERR_OK;
}
else
{
/* process data blocks */
while (len >= HASH_BLOCK_SIZE)
{
MD4_ProcessBlock(c, data);
c->total += HASH_BLOCK_SIZE;
data = (uint8_t *)data + HASH_BLOCK_SIZE;
len -= HASH_BLOCK_SIZE;
}
/* copy rest data to context buffer */
memcpy(&c->last.buf[0], data, len);
c->last.used = len;
}
return ERR_OK;
}
int MD4_Final(unsigned char *md, MD4_CTX *c)
{
uint32_t *temp;
if ((NULL == c) || (NULL == md))
{
return ERR_INV_PARAM;
}
/* Last block should be less than HASH_BLOCK_SIZE - HASH_LEN_SIZE */
if (c->last.used >= (HASH_BLOCK_SIZE - HASH_LEN_SIZE))
{
c->total += c->last.used;
/* one more block */
c->last.buf[c->last.used] = HASH_PADDING_PATTERN;
c->last.used++;
memset(&c->last.buf[c->last.used], 0, HASH_BLOCK_SIZE - c->last.used);
MD4_ProcessBlock(c, &c->last.buf);
memset(&c->last.buf[0], 0, HASH_BLOCK_SIZE - HASH_LEN_SIZE);
c->last.used = 0;
/* save length */
temp = (uint32_t *)&(c->last.buf[HASH_LEN_OFFSET]);
temp[0] = htole32((c->total << 3) & 0xFFFFFFFF);
temp[1] = htole32(((c->total << 3) >> 32) & 0xFFFFFFFF);
MD4_ProcessBlock(c, &c->last.buf);
}
else /* 0 <= last.used < HASH_BLOCK_SIZE - HASH_LEN_SIZE */
{
c->total += c->last.used;
/* one more block */
c->last.buf[c->last.used] = HASH_PADDING_PATTERN;
c->last.used++;
/* padding 0s */
memset(&c->last.buf[c->last.used], 0, HASH_BLOCK_SIZE - HASH_LEN_SIZE - c->last.used);
/* save length */
temp = (uint32_t *)&(c->last.buf[HASH_LEN_OFFSET]);
temp[0] = htole32((c->total << 3) & 0xFFFFFFFF);
temp[1] = htole32(((c->total << 3) >> 32) & 0xFFFFFFFF);
MD4_ProcessBlock(c, &c->last.buf);
}
/* LE for MD4/MD5, different from SHA family(Big Endian) */
temp = (uint32_t *)md;
temp[0] = htole32(c->hash.a);
temp[1] = htole32(c->hash.b);
temp[2] = htole32(c->hash.c);
temp[3] = htole32(c->hash.d);
return ERR_OK;
}
unsigned char *MD4(const unsigned char *d, unsigned long n, unsigned char *md)
{
MD4_CTX c;
if ((NULL == d) || (NULL == md))
{
return NULL;
}
MD4_Init(&c);
MD4_Update(&c, d, n);
MD4_Final(md, &c);
return md;
}
MD4源码的编译和测试
我直接在Makefile中内置了一个test伪目标,编译时除了编译生成名为md4的哈希工具外,还会直接调用内置的哈希测试。
编译和运行如下:
$ make
gcc -Wall -g -O2 -c utils.c -o utils.o
gcc -Wall -g -O2 -c md4.c -o md4.o
gcc -Wall -g -O2 -c md4test.c -o md4test.o
gcc -Wall -g -O2 utils.o md4.o md4test.o -o md4
Run Test...
./md4 -x
Internal hash tests for ./md4:
./md4("")
Expect: 31d6cfe0d16ae931b73c59d7e0c089c0
Result: 31d6cfe0d16ae931b73c59d7e0c089c0
./md4("a")
Expect: bde52cb31de33e46245e05fbdbd6fb24
Result: bde52cb31de33e46245e05fbdbd6fb24
./md4("abc")
Expect: a448017aaf21d8525fc10ae87aa6729d
Result: a448017aaf21d8525fc10ae87aa6729d
./md4("message digest")
Expect: d9130a8164549fe818874806e1c7014b
Result: d9130a8164549fe818874806e1c7014b
./md4("abcdefghijklmnopqrstuvwxyz")
Expect: d79e1c308aa5bbcdeea8ed63df412da9
Result: d79e1c308aa5bbcdeea8ed63df412da9
./md4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
Expect: 043f8582f241db351ce627e153e7f0e4
Result: 043f8582f241db351ce627e153e7f0e4
./md4("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
Expect: e33b4ddc9c38f2199c3e7b164fcc0536
Result: e33b4ddc9c38f2199c3e7b164fcc0536
目前版本的openssl工具还支持md4哈希算法,因此可以将md4工具和openssl执行dgst计算的结果进行比较:
# 使用"-f"和"-s"选项分别对文件和字符串计算md4哈希值
$ md4 -f md4.o
md4(md4.o) = 801a3587c9d30d276bd07a7a4a1fa616
$ md4 -s "I Love China!"
md4("I Love China!") = 24cb6e3ed5ba92ab304dee9982d7f317
# 使用开源的openssl工具计算相应的哈希进行对比
$ openssl dgst -md4 md4.o
MD4(md4.o)= 801a3587c9d30d276bd07a7a4a1fa616
$ echo -n "I Love China!" | openssl dgst -md4
(stdin)= 24cb6e3ed5ba92ab304dee9982d7f317
完整代码
完整的代码包括Makefile和相应的测试文件md4test.c,需要代码请访问:
- https://github.com/guyongqiangx/cryptography/
其它
洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。
所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:
- 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
- 一个Android OTA的讨论组,请说明加Android OTA群。
- 一个git和repo的讨论组,请说明加git和repo群。
在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:
洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号: