最近在项目中遇到sha256算法加密的需求,于是看了一些相关的资料,最后也整理出一些东西,并且参考文档自己实现了一遍:
#define S(a, b) (((a) >> (b)) | ((a) << (32 - b))) /* a循环右移b位 */
/* sha256逻辑函数 */
#define CH(x, y, z) ((x & y) ^ ((~x) & z))
#define MA(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
#define Σ0(x) (S(x, 2)^S(x, 13)^S(x, 22))
#define Σ1(x) (S(x, 6)^S(x, 11)^S(x, 25))
#define σ0(x) (S(x, 7)^S(x, 18)^(x >> 3))
#define σ1(x) (S(x, 17)^S(x, 19)^(x >> 10))
/* 功能:大小端判断
返回: 1小端 0大端 */
int Endian()
{
int a = 1;
int *p = &a;
return (*(char *)p == 1);
}
/* 功能:sha256编码
入参:
char *Src 源数据
int SrcLen 源数据长度
返回值: sha256数据 */
char* MyShaA256Encode(const char *Src, long long SrcLen)
{
unsigned long i = 0, T1 = 0, T2 = 0, A = 0, B = 0, C = 0, D = 0, E = 0, F = 0, G = 0, H = 0;
unsigned long *p = NULL;
unsigned long W[64] = {0}; /* 64个字 */
static char shadata[256] = {0}; /* sha256数据缓存 */
unsigned long Key[64] = /* 秘钥表 */
{
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
};
unsigned long Hash[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; /* 初始哈希值 */
long long reallen = (SrcLen * 8) % 512 >= 448 ? ((SrcLen * 8) / 512 + 2) * 64 : ((SrcLen * 8) / 512 + 1) * 64; /* 补位后长度 */
char * processsrc = (char *)calloc(reallen, sizeof(char)); /* 源数据补位后缓存 */
if (NULL == processsrc) {
return NULL;
}
/* 预处理 */
if (Endian()) {
for (i = 0; i < SrcLen; processsrc[i + 3 - 2 * (i % 4)] = Src[i], i++); /* 小端存法 */
processsrc[i + 3 - 2 * (i % 4)] = 1 << 7; /* 补一个1 */
} else {
memcpy(processsrc, Src, SrcLen); /* 大端存法 */
*(processsrc + SrcLen) = 1 << 7; /* 补一个1 */
}
*((long*)(processsrc + reallen - 4)) = SrcLen << 3; /* 最后64位代表源数据长度 */
*((long*)(processsrc + reallen - 8)) = SrcLen >> 29;
for (p = (unsigned long *)processsrc; p < (unsigned long *)(processsrc + reallen); p += 16) {
/* 初始化64个字(W) */
for (i = 0; i < 16; ++i) {
W[i] = *(p + i);
}
for (i = 16; i < 64; ++i) {
W[i] = σ1(W[i - 2]) + W[i - 7] + σ0(W[i - 15]) + W[i - 16];
}
/* sha256摘要迭代 */
A = Hash[0]; B = Hash[1]; C = Hash[2]; D = Hash[3]; E = Hash[4]; F = Hash[5]; G = Hash[6]; H = Hash[7];
for (i = 0; i < 64; ++i) {
T1 = H + Σ1(E) + CH(E, F, G) + Key[i] + W[i];
T2 = Σ0(A) + MA(A, B, C);
H = G, G = F, F = E, E = D + T1, D = C, C = B, B = A, A = T1 + T2;
}
Hash[0] += A, Hash[1] += B, Hash[2] += C, Hash[3] += D, Hash[4] += E, Hash[5] += F, Hash[6] += G, Hash[7] += H;
}
sprintf(shadata, "%08X%08X%08X%08X%08X%08X%08X%08X", Hash[0], Hash[1], Hash[2], Hash[3], Hash[4], Hash[5], Hash[6], Hash[7]);
free(processsrc);
printf("sha256: %s\r\n", shadata);
return shadata;
}
源码详解:
1、sha256常数
sha256最终输出的是64位字符串,故需要一串64字符的初始哈希值,即源码中的Hash[8],刚好是8*8个字符,其次还需要64个4byte的秘钥,用于摘要迭代,即源码中的Key[64]:
unsigned long Key[64] = /* 秘钥表 */
{
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
};
unsigned long Hash[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; /* 初始哈希值 */
2、补位后长度计算
补位后长度计算遵循这样的原则:在源数据后先添加一位1,再在1后面添加若干个0,知道数据总位数对512求余为512-64=448,最后64位需要填写源数据长度,用于入参SrcLen表示的是原字符串长度(单位是byte),1byte=8bit,所以在计算时需要有*8的操作,具体如下:
long long reallen = (SrcLen * 8) % 512 >= 448 ? ((SrcLen * 8) / 512 + 2) * 64 : ((SrcLen * 8) / 512 + 1) * 64; /* 补位后长度 */
3、预处理(补位)
将原始数据Src扩展成512位对齐的processsrc数据,此处需要关注的是Src数据默认是低地址->高地址排列传入的,假设需要加密的数据是字符串"12345",传入的是字符串首地址,长度为5byte,这个方法内部看到的是0x31,0x32,0x33,0x34,0x35这些数据,接着以4字节为单位进行加工,首先是0x31323334,将这个数据看成是一个4byte的整体,从低地址到高地址依次存入processsrc,小端机器按0x34,0x33,0x32,0x31存入,大端机器按0x31,0x32,0x33,0x34存入(此步其他数文章里没有提出,本人也不确定是不是这样??),小端最后就是这行代码:
for (i = 0; i < SrcLen; processsrc[i + 3 - 2 * (i % 4)] = Src[i], i++);
大端是这行代码:
memcpy(processsrc, Src, SrcLen);
将源数据存入后,再进行1+n个0的补齐,calloc初始值就是0,所以只需要补齐一个1:,小端补1:
processsrc[i + 3 - 2 * (i % 4)] = 1 << 7;
大端补1:
*(processsrc + SrcLen) = 1 << 7;
最后64位代表源数据长度,继续补齐:
*((long*)(processsrc + reallen - 4)) = SrcLen << 3;
*((long*)(processsrc + reallen - 8)) = SrcLen >> 29;
4、初始化64个字
将每个512位的数据块扩充成64byte,前16个byte将512位数据完全拷贝即可,后面48byte参考文档里面这个公式扩充:
相应的代码是这段:
for (i = 0; i < 16; ++i) {
W[i] = *(p + i);
}
for (i = 16; i < 64; ++i) {
W[i] = σ1(W[i - 2]) + W[i - 7] + σ0(W[i - 15]) + W[i - 16];
}
5、摘要迭代
根据官方手册上提供的逻辑图进行操作即可:
代码是这一段:
A = Hash[0]; B = Hash[1]; C = Hash[2]; D = Hash[3]; E = Hash[4]; F = Hash[5]; G = Hash[6]; H = Hash[7];
for (i = 0; i < 64; ++i) {
T1 = H + Σ1(E) + CH(E, F, G) + Key[i] + W[i];
T2 = Σ0(A) + MA(A, B, C);
H = G, G = F, F = E, E = D + T1, D = C, C = B, B = A, A = T1 + T2;
}
Hash[0] += A, Hash[1] += B, Hash[2] += C, Hash[3] += D, Hash[4] += E, Hash[5] += F, Hash[6] += G, Hash[7] += H;
接下来就是重复4、5步操作,直到将processsrc 的数据完全处理完,得到一串Hash即sha256的加密数据。