消息摘要算法
简介
消息摘要(Message Digest)是数字签名算法的核心,常用于验证数据完整性,包含MD,SHA,MAC三大种类
java中的
equals
方法在比较对象时,实际上比较的是两个对象的散列值hashCode()
是否相同任何消息经过散列函数处理后,都会得到唯一的散列值。这一过程称为“消息摘要”,其散列值称为“数字指纹”,自然其算法就是“消息摘要算法”
通过散列函数可获得对应的散列值,但不可通过该散列值反推其原始信息。这是消息摘要算法安全性的根本所在
MD5
MD5算法是典型的消息摘要算法,是计算机广泛使用的杂凑算法之一(又译摘要算法、散列算法)
补充:杂凑(Hash)算法的输出被称作杂凑值(hash value),输出长度与算法有关,与原文长度无关
MD5算法会输出128位的二进制摘要信息,转换为16进制得到32位字符串
实现流程
目标字符串以字符为单位转换成字符编码(ascii码/unicode码等),将字符编码转换为二进制码
明文末尾补1个1和n个0,使其对512取余为448
二进制码进行分组转换。即每以512位二进制码一组,最后一组数据位数448(保留64位)
保留64位处表明原文长度信息,记录原文的位长。以使最后一组也保持512位
将每个512分组再分出16个32位子分组
初始化幻数
A: 01 23 45 67 (16进制) B: 89 ab cd ef (16进制) C: fe dc ba 98 (16进制) D: 76 54 32 10 (16进制) A: 0x67452301 B: 0xefcdab89 C: 0x98badcfe D: 0x10325476 A: 1732584193 B: 4023233417 C: 2562383102 D: 271733878
32位分组与幻数组合进行4轮(FF,GG,HH,II),每轮16次,共64次的位运算
位运算涉及常量:位移S与正弦函数表(sine table)
// 向左位移数 const unsigned int 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}; // unsigned int(abs(sin(i+1))*(2pow32)) const unsigned int k[] = { 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}; /* [ 3614090360, 3905402710, 606105819, 3250441966, 4118548399,1200080426, 2821735955, 4249261313, 1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329, 4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448, 568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562, 4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656, 681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645, 4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497, 1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745 ] */
A + BCD进行位运算结果 + 明文块 + 正弦常量。得到的结果,循环左移指定常量位数。得到的结果 + B = 返回值。示例代码FF,参考文章https://www.cnblogs.com/xiaxveliang/p/15004954.html
// FF,GG,HH和II调用F,G,H,I函数进行进一步变换 private long FF(long a, long b, long c, long d, long x, long s, long ac) { a += F(b, c, d) + x + ac; // 循环左移s位 //这里long型数据右移时使用无符号右移运算符>>> a = ((int) a << s) | ((int) a >>> (32 - s)); a += b; return a; }
计算过程中幻数ABCD也会改变。运算后每个512分组得到4个数值A、B、C、D,与拿到的A、B、C、D求和,作为下一组的幻数
// 一组中的第一轮运算FF为例 // 参数[4]: 明文数据块 参数[5]: 位移 参数[6]: 正弦函数表 // 返回值 - 新的A、B、C、D a = FF(a, b, c, d, M0, 7, 0xd76aa478L); d = FF(d, a, b, c, M1, 12, 0xe8c7b756L); c = FF(c, d, a, b, M2, 17, 0x242070dbL); b = FF(b, c, d, a, M3, 22, 0xc1bdceeeL); a = FF(a, b, c, d, M4, 7, 0xf57c0fafL); d = FF(d, a, b, c, M5, 12, 0x4787c62aL); c = FF(c, d, a, b, M6, 17, 0xa8304613L); b = FF(b, c, d, a, M7, 22, 0xfd469501L); a = FF(a, b, c, d, M8, 7, 0x698098d8L); d = FF(d, a, b, c, M9, 12, 0x8b44f7afL); c = FF(c, d, a, b, M10, 17, 0xffff5bb1L); b = FF(b, c, d, a, M11, 22, 0x895cd7beL); a = FF(a, b, c, d, M12, 7, 0x6b901122L); d = FF(d, a, b, c, M13, 12, 0xfd987193L); c = FF(c, d, a, b, M14, 17, 0xa679438eL); b = FF(b, c, d, a, M15, 22, 0x49b40821L); // a、b、c、d 为每一512bit分组的运算结果; // A、B、C、D 是下一组计算的输入参数; // 若无下一个512bit分组 A、B、C、D 则为最终计算结果; A = a + A; B = b + B; C = c + C; D = d + D;
字符串拼接最后一个512分组计算出的ABCD得到MD5结果
例子:目标字符串 A
A
的ascii码为65
,ascii码的二进制码01000001
。因此只有一个512分组- 填充 0 位,直到总长度达到 448 位。
01000001 10000000 00000000 ... (共 439 个 0)
- 补充长度信息。原文共一个字符,8位,保留64位为
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001000
- 将其分为16个32位分组
01000001 10000000 00000000 00000000 ... 14个全0分组 ... 00000000 00000000 00000000 00001000
- 与幻数进行4*16次位运算得到计算后的ABCD
- 将计算后的A
7fc56270
Be7a70fa8
C1a5935b7
D2eacbe29
拼接得到结果7fc56270e7a70fa81a5935b72eacbe29
代码
Java API
import java.io.UnsupportedEncodingException; public class MD5 { public static String getMD5 (String data) { // 初始化 MessageDigest messageDigest = null; try { // 获取md5算法 messageDigest = MessageDigest.getInstance("MD5"); messageDigest.reset(); // 传入明文 messageDigest.update(data.getBytes("UTF-8")); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 执行摘要 byte[] byteArray = messageDigest.digest(); StringBuffer res = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { // toHexString: 无符号生疏参数所表示的值转16进制 // 取低8位后转16进制 String hex = Integer.toHexString(0xFF & byteArray[i]); if (hex.length == 1) res.append("0"); res.append(hex); } // 返回16位则切9-25 return res.substring(8, 24).toString().toUpperCase(); // 返回正常的32位 return res.toString().toUpperCase(); } }
Python实现。代码出自GPT:使用python编写md5算法的实现流程
import math import struct # 左旋转函数 def left_rotate(x, amount): x &= 0xFFFFFFFF # 确保 x 是 32 位 return ((x << amount) | (x >> (32 - amount))) & 0xFFFFFFFF # MD5 主算法类 class MD5: def __init__(self): # 初始化常量 self.s = [ 7, 12, 17, 22, # 轮 1 5, 9, 14, 20, # 轮 2 4, 11, 16, 23, # 轮 3 6, 10, 15, 21 # 轮 4 ] # 常数 T(i) - 正弦表 self.K = [int(abs(math.sin(i + 1)) * (2**32)) & 0xFFFFFFFF for i in range(64)] # 初始化缓冲区 self.A = 0x67452301 self.B = 0xEFCDAB89 self.C = 0x98BADCFE self.D = 0x10325476 def process_block(self, block): # 把 512 位块拆成 16 个 32 位的小块 M = struct.unpack('<16I', block) A, B, C, D = self.A, self.B, self.C, self.D # 主循环 for i in range(64): if 0 <= i <= 15: F = (B & C) | (~B & D) g = i elif 16 <= i <= 31: F = (D & B) | (~D & C) g = (5 * i + 1) % 16 elif 32 <= i <= 47: F = B ^ C ^ D g = (3 * i + 5) % 16 elif 48 <= i <= 63: F = C ^ (B | ~D) g = (7 * i) % 16 F = (F + A + self.K[i] + M[g]) & 0xFFFFFFFF A = D D = C C = B B = (B + left_rotate(F, self.s[i % 4 + (i // 16) * 4])) & 0xFFFFFFFF # 更新缓冲区 self.A = (self.A + A) & 0xFFFFFFFF self.B = (self.B + B) & 0xFFFFFFFF self.C = (self.C + C) & 0xFFFFFFFF self.D = (self.D + D) & 0xFFFFFFFF def update(self, message): # 预处理步骤:填充消息 original_length_in_bits = (len(message) * 8) & 0xFFFFFFFFFFFFFFFF message += b'\x80' while len(message) % 64 != 56: message += b'\x00' # 添加消息长度 - 小端长整数 - Q: unsigned long long message += struct.pack('<Q', original_length_in_bits) # 处理每个 512 位块 for i in range(0, len(message), 64): self.process_block(message[i:i+64]) def digest(self): # 输出最终的 128 位摘要 - 小端 unsigned int return struct.pack('<4I', self.A, self.B, self.C, self.D) def hexdigest(self): # 将摘要转换为十六进制字符串 # 02表示大端补0填充到2位,x表示16进制 - b二进制,o八进制,d十进制 return ''.join(f'{x:02x}' for x in self.digest()) # 测试 MD5 算法 def md5(message): md5_obj = MD5() md5_obj.update(message) return md5_obj.hexdigest() # 示例 message = "hello world" print(f"MD5(\"hello world\") = {md5(message.encode())}")
C++实现。代码参考 百度百科MD5
#include<iostream> #include<string> using namespace std; #define shift(x, n) (((x) << (n)) | ((x) >> (32-(n))))//右移的时候,高位一定要补零,而不是补充符号位 #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | (~z))) #define A 0x67452301 #define B 0xefcdab89 #define C 0x98badcfe #define D 0x10325476 //strBaye的长度 unsigned int strlength; //A,B,C,D的临时变量 unsigned int atemp; unsigned int btemp; unsigned int ctemp; unsigned int dtemp; //常量ti unsigned int(abs(sin(i+1))*(2pow32)) const unsigned int k[]={ 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}; //向左位移数 const unsigned int 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}; const char str16[]="0123456789abcdef"; void mainLoop(unsigned int M[]) { unsigned int f,g; unsigned int a=atemp; unsigned int b=btemp; unsigned int c=ctemp; unsigned int d=dtemp; for (unsigned int i = 0; i < 64; i++) { if(i<16){ // 第一轮 f=F(b,c,d); // BCD的位运算 g=i; // 明文数据块下标 } else if (i<32) { f=G(b,c,d); g=(5*i+1)%16; } else if(i<48) { f=H(b,c,d); g=(3*i+5)%16; } else { f=I(b,c,d); g=(7*i)%16; } unsigned int tmp=d; d=c; c=b; b=b+shift((a+f+k[i]+M[g]),s[i]); // 求和并位移,再与B求和 a=tmp; } atemp=a+atemp; btemp=b+btemp; ctemp=c+ctemp; dtemp=d+dtemp; } /* *填充函数 *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64) *填充方式为先加一个1,其它位补零 *最后加上64位的原来长度 */ unsigned int* add(string str) { unsigned int num=((str.length()+8)/64)+1; // 以512位,64个字节为一组 unsigned int *strByte=new unsigned int[num*16]; // 64/4=16,所以有16个整数 strlength=num*16; for (unsigned int i = 0; i < num*16; i++) strByte[i]=0; for (unsigned int i=0; i <str.length(); i++) { strByte[i>>2]|=(str[i])<<((i%4)*8); // 一个整数存储四个字节,i>>2表示i/4 一个unsigned int对应4个字节,保存4个字符信息 } strByte[str.length()>>2]|=0x80<<(((str.length()%4))*8); // 尾部添加1 一个unsigned int保存4个字符信息,所以用128左移 /* *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 */ strByte[num*16-2] = str.length()*8; return strByte; } string changeHex(int a) { int b; string str1; string str=""; for (int i = 0; i < 4; i++) { str1=""; b = ((a>>i*8)%(1<<8))&0xff; //逆序处理每个字节 for (int j = 0; j < 2; j++) { str1.insert(0,1,str16[b%16]); b = b / 16; } str += str1; } return str; } string getMD5(string source) { atemp=A; //初始化 btemp=B; ctemp=C; dtemp=D; unsigned int *strByte=add(source); for(unsigned int i = 0; i < strlength/16; i++) { unsigned int num[16]; for(unsigned int j=0;j<16;j++) num[j] = strByte[i*16+j]; mainLoop(num); } return changeHex(atemp).append(changeHex(btemp)).append(changeHex(ctemp)).append(changeHex(dtemp)); } unsigned int main() { string ss; // cin>>ss; string s=getMD5("abc"); cout<<s; return 0; }
加盐
原文保持不变的情况下,md5得到的值也会不变,这是撞库的原理。通过加盐方式防止撞库,比如原文后拼接特定字符串
SHA
SHA算法(Secure Hash Algorithm,安全散列算法)基于MD4算法基础。与MD算法不同之处主要在于摘要长度,SHA算法的摘要长度更长,安全性更高
SHA算法家族目前共有SHA-1、SHA-224、SHA-256、SHA-384和SHA-512五种算法。通常将后四种算法并称为SHA-2算法
实现流程
目标字符串以字符为单位转换成字符编码(ascii码/unicode码等),将字符编码转换为二进制码
明文末尾补1个1和n个0,使其对512取余为448
二进制码进行分组转换。即每以512位二进制码一组,最后一组数据位数448(保留64位)
保留64位处表明原文长度信息,记录原文的位长。以使最后一组也保持512位
将每个512分组再分出16个32位子分组
将16个32位子分组扩展到80个32位子分组
- 对于前 16 个子块(0 ≤ t ≤15),直接将 W[t] 赋值为 M[t]
- 从第 17 个子块开始(16 ≤ t ≤ 79),使用以下公式进行扩展: W[t]=(W[t−3]⊕W[t−8]⊕W[t−14]⊕W[t−16])≪1
初始化幻数
uint32_t H0 = 0x67452301; // 0x01, 0x23, 0x45, 0x67 uint32_t H1 = 0xEFCDAB89; // 0x89, 0xAB, 0xCD, 0xEF uint32_t H2 = 0x98BADCFE; // 0xFE, 0xDC, 0xBA, 0x98 uint32_t H3 = 0x10325476; // 0x76, 0x54, 0x32, 0x10 uint32_t H4 = 0xC3D2E1F0; // 0xF0, 0xE1, 0xD2, 0xC3 /* 前4个数字与MD5幻数一致 A: 1732584193 B: 4023233417 C: 2562383102 D: 271733878 E: 3285377520 */
进行4轮,每轮20次,共80次运算。每轮运算时使用不同的值求和
/* SHA1 Constants */ static uint32_t K[4] = { 0x5A827999, /* [0, 19] */ 0x6ED9EBA1, /* [20, 39] */ 0x8F1BBCDC, /* [40, 59] */ 0xCA62C1D6 /* [60, 79] */ }; /* 1518500249 1859775393 2400959708 3395469782 */
计算过程中幻数ABCDE也会改变。运算后每个512分组得到幻数,与该分组计算前传入的幻数求和,作为下一分组的传入幻数
字符串拼接最后一个512分组计算出的ABCDE得到SHA1结果
例子:目标字符串abc
- 字符串
abc
ascii码的二进制码01100001 01100010 01100011
- 补位以便进行分组
01100001 01100010 01100011 100...0
共计423个0(423个0 + 1个1 + 原文3*8 = 448),16进制如下所示 (512位二进制 -> 128位16进制)61626380 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
- 附加长度信息:原文长度:3*8 = 24位二进制,24的16进制为18
61626380 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000018
- 先分出32个子分组,再通过公示扩展成80个子分组
- 进行位运算得到最终幻数H0 = H0+A、H1=H1+B、H2=H2+C、H3=H3+D、H4=H4+E
- 拼接最后一个分组得到的ABCDE即为最终结果
代码
JAVA API
import java.security.MessageDigest; public class SHA { // SHA-1 : 160位二进制 - 40位16进制字符串(每4位二进制得到一个16进制字符) public static byte[] encodeSHA1(byte[] data) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA"); return md.digest(data); } // SHA-256 : 256位二进制 - 64位16进制字符串 public static byte[] encodeSHA256(byte[] data) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); return md.digest(data); } // SHA-384 : 384位二进制 - 96位16进制字符串 public static byte[] encodeSHA384(byte[] data) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-384"); return md.digest(data); } // SHA-512 : 512位二进制 - 128位16进制字符串 public static byte[] encodeSHA512(byte[] data) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-512"); return md.digest(data); } }
Python实现。代码出自GPT:使用python编写sha1算法的实现流程
import struct # 定义循环左移函数 def left_rotate(n, b): return ((n << b) | (n >> (32 - b))) & 0xFFFFFFFF # SHA-1 主算法类 class SHA1: def __init__(self): # 初始化五个缓冲区变量(五个32位寄存器) self.h0 = 0x67452301 self.h1 = 0xEFCDAB89 self.h2 = 0x98BADCFE self.h3 = 0x10325476 self.h4 = 0xC3D2E1F0 def update(self, message): # 预处理:填充消息 original_length = len(message) * 8 # 以位为单位 - 一字节8位 message += b'\x80' # 先添加 1 位,后跟随 0 位填充 # 填充0直到消息长度模512等于448位(即最后64位用来存储长度) # while (len(message) * 8 + 64) % 512 != 0: # 化简后为 56 = 448 / 8 while len(message) % 64 != 56: message += b'\x00' # 补充全0字节 # 以大端格式附加消息的原始长度 message += struct.pack('>Q', original_length) # 按 512 位(64 字节)块处理消息 for i in range(0, len(message), 64): self.process_block(message[i:i + 64]) def process_block(self, block): # 将 512 位块分成 16 个 32 位的字 w = list(struct.unpack('>16I', block)) # 长度16的数组 # 扩展到 80 个 32 位字 for i in range(16, 80): # 补充index 16 - 79 w.append(left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)) # 初始化五个变量 a, b, c, d, e = self.h0, self.h1, self.h2, self.h3, self.h4 # 主循环 for i in range(80): if 0 <= i <= 19: f = (b & c) | (~b & d) k = 0x5A827999 elif 20 <= i <= 39: f = b ^ c ^ d k = 0x6ED9EBA1 elif 40 <= i <= 59: f = (b & c) | (b & d) | (c & d) k = 0x8F1BBCDC else: f = b ^ c ^ d k = 0xCA62C1D6 temp = (left_rotate(a, 5) + f + e + k + w[i]) & 0xFFFFFFFF e = d d = c c = left_rotate(b, 30) b = a a = temp # 更新缓冲区 self.h0 = (self.h0 + a) & 0xFFFFFFFF self.h1 = (self.h1 + b) & 0xFFFFFFFF self.h2 = (self.h2 + c) & 0xFFFFFFFF self.h3 = (self.h3 + d) & 0xFFFFFFFF self.h4 = (self.h4 + e) & 0xFFFFFFFF def digest(self): # 拼接返回 160 位哈希值 - 指定'>5I'拼接 return struct.pack('>5I', self.h0, self.h1, self.h2, self.h3, self.h4) def hexdigest(self): # 将哈希值转换为十六进制字符串 # 指定'>5I'解包后 return ''.join(f'{x:08x}' for x in struct.unpack('>5I', self.digest())) # 等效为''.join(f'{x:08x}' for x in [h0, h1, h2, h3, h4]) # 测试 SHA-1 算法 def sha1(message): sha1_obj = SHA1() sha1_obj.update(message) return sha1_obj.hexdigest() # 示例 message = b"hello world" print(f"SHA-1(\"hello world\") = {sha1(message)}")
C实现 参考https://www.cnblogs.com/Kingfans/p/16561821.html
#include <string.h> #include <stdio.h> #define HASH_BLOCK_SIZE 64 /* 512 bits = 64 bytes */ #define HASH_LEN_SIZE 8 /* 64 bits = 8 bytes */ #define HASH_LEN_OFFSET 56 /* 64 bytes - 8 bytes */ #define HASH_DIGEST_SIZE 16 /* 128 bits = 16 bytes */ #define HASH_ROUND_NUM 80 typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t; /* Swap bytes in 32 bit value. 0x01234567 -> 0x67452301 */ // 大端小端互转 #define __bswap_32(x) \ ((((x) & 0xff000000) >> 24) \ | (((x) & 0x00ff0000) >> 8) \ | (((x) & 0x0000ff00) << 8) \ | (((x) & 0x000000ff) << 24)) /* SHA1 Constants */ static uint32_t K[4] = { 0x5A827999, /* [0, 19] */ 0x6ED9EBA1, /* [20, 39] */ 0x8F1BBCDC, /* [40, 59] */ 0xCA62C1D6 /* [60, 79] */ }; /* f(X, Y, Z) */ /* [0, 19] */ static uint32_t Ch(uint32_t X, uint32_t Y, uint32_t Z) { return (X & Y) ^ ((~X) & Z); } /* [20, 39] */ /* [60, 79] */ static uint32_t Parity(uint32_t X, uint32_t Y, uint32_t Z) { return X ^ Y ^ Z; } /* [40, 59] */ static uint32_t Maj(uint32_t X, uint32_t Y, uint32_t Z) { return (X & Y) ^ (X & Z) ^ (Y & Z); } /* 循环向左移动offset个比特位 */ static uint32_t MoveLeft(uint32_t X, uint8_t offset) { uint32_t res = (X << offset) | (X >> (32 - offset)); return res; } #define ASSERT_RETURN_INT(x, d) if(!(x)) { return d; } int sha1(unsigned char *out, const unsigned char* in, const int inlen) { ASSERT_RETURN_INT(out && in && (inlen >= 0), 1); int i = 0, j = 0, t = 0; // step 1: 字节填充(Append Padding Bytes) // 数据先补上1个1比特,再补上k个0比特,使得补位后的数据比特数(n+1+k)满足(n+1+k) mod 512 = 448,k取最小正整数 int iX = inlen / HASH_BLOCK_SIZE; int iY = inlen % HASH_BLOCK_SIZE; iX = (iY < HASH_LEN_OFFSET) ? iX : (iX + 1); int iLen = (iX + 1) * HASH_BLOCK_SIZE; unsigned char* X = malloc(iLen); memcpy(X, in, inlen); // 先补上1个1比特+7个0比特 X[inlen] = 0x80; // 再补上(k-7)个0比特 for (i = inlen + 1; i < (iX * HASH_BLOCK_SIZE + HASH_LEN_OFFSET); i++) { X[i] = 0; } // step 2: 追加长度信息(Append Length) uint8_t *pLen = (uint64_t*)(X + (iX * HASH_BLOCK_SIZE + HASH_LEN_OFFSET)); uint64_t iTempLen = inlen << 3; uint8_t *pTempLen = &iTempLen; pLen[0] = pTempLen[7]; pLen[1] = pTempLen[6]; pLen[2] = pTempLen[5]; pLen[3] = pTempLen[4]; pLen[4] = pTempLen[3]; pLen[5] = pTempLen[2]; pLen[6] = pTempLen[1]; pLen[7] = pTempLen[0]; // Step 3. 初始化MD Buffer(Initialize MD Buffer) uint32_t H0 = 0x67452301; // 0x01, 0x23, 0x45, 0x67 uint32_t H1 = 0xEFCDAB89; // 0x89, 0xAB, 0xCD, 0xEF uint32_t H2 = 0x98BADCFE; // 0xFE, 0xDC, 0xBA, 0x98 uint32_t H3 = 0x10325476; // 0x76, 0x54, 0x32, 0x10 uint32_t H4 = 0xC3D2E1F0; // 0xF0, 0xE1, 0xD2, 0xC3 uint32_t M[HASH_BLOCK_SIZE / 4] = { 0 }; uint32_t W[HASH_ROUND_NUM] = { 0 }; // step 4: 处理消息块(Process Message in 64-Byte Blocks) for (i = 0; i < iLen / HASH_BLOCK_SIZE; i++) { /* Copy block i into X. */ for (j = 0; j < HASH_BLOCK_SIZE; j = j + 4) { uint64_t k = i * HASH_BLOCK_SIZE + j; M[j / 4] = (X[k] << 24) | (X[k + 1] << 16) | (X[k + 2] << 8) | X[k + 3]; } /* a. Divide M(i) into 16 words W(0), W(1), ..., W(15), where W(0) is the left - most word. */ for (t = 0; t <= 15; t++) { W[t] = M[t]; } /* b. For t = 16 to 79 let W(t) = S^1(W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16)). */ for (t = 16; t <= 79; t++) { W[t] = MoveLeft(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1); } /* c. Let A = H0, B = H1, C = H2, D = H3, E = H4. */ uint32_t A = H0; uint32_t B = H1; uint32_t C = H2; uint32_t D = H3; _t E = H4; /* d. For t = 0 to 79 do TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t); E = D; D = C; C = S^30(B); B = A; A = TEMP; */ for (t = 0; t <= 19; t++) { uint32_t temp = MoveLeft(A, 5) + Ch(B, C, D) + E + W[t] + K[0]; E = D; D = C; C = MoveLeft(B, 30); B = A; A = temp; } for (t = 20; t <= 39; t++) { uint32_t temp = MoveLeft(A, 5) + Parity(B, C, D) + E + W[t] + K[1]; E = D; D = C; C = MoveLeft(B, 30); B = A; A = temp; } for (t = 40; t <= 59; t++) { uint32_t temp = MoveLeft(A, 5) + Maj(B, C, D) + E + W[t] + K[2]; E = D; D = C; C = MoveLeft(B, 30); B = A; A = temp; } for (t = 60; t <= 79; t++) { uint32_t temp = MoveLeft(A, 5) + Parity(B, C, D) + E + W[t] + K[3]; E = D; D = C; C = MoveLeft(B, 30); B = A; A = temp; } /* e. Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. */ H0 = H0 + A; H1 = H1 + B; H2 = H2 + C; H3 = H3 + D; H4 = H4 + E; } // step 5: 输出ABCD uint32_t* pOut = (uint8_t*)out; pOut[0] = __bswap_32(H0); pOut[1] = __bswap_32(H1); pOut[2] = __bswap_32(H2); pOut[3] = __bswap_32(H3); pOut[4] = __bswap_32(H4); free(X); return 0; } int main() { unsigned char digest[20] = { 0 }; sha1(digest, "Hello World!", strlen("Hello World!")); return 0; }