现在的电子设备中,通讯报文的收发都需要进行加密和解密操作,不能明文发送,因为明文容易被黑客破解出协议报文,进而改动协议数据,以伪指令报文的方式进行发送,这样会造成非常严重的安全隐患,尤其是在生命科学医用电子仪器设备,自能驾驶汽车应用上,黑客入侵会导致威胁生命安全的危害事件发生。
加密和解密变得越来越重要,随着物联网和车联网的迅速崛起,信息安全变成一种新型的科研方向。本文就是以SHA256哈希加密和解密算法来进行解释和说明数据加密和解密操作的。
SHA-256是一种哈希函数,属于SHA-2(Secure Hash Algorithm 2)家族的一部分。它是由美国国家安全局(NSA)设计,并由美国国家标准与技术研究院(NIST)发布为联邦信息处理标准(FIPS)。SHA-256的输出是一个256位(32字节)的哈希值,通常表示为64个十六进制字符。
SHA-256的工作原理基于Merkle-Damgard结构,主要包括以下几个步骤:
补位
假设消息M的二进制编码长度为l位,首先在消息末尾补上一位“1”,然后再补上k个“0”,其中k为满足(l+1+k)≡448(mod512)的最小非负整数。
在上述字符串后面再补上l的二进制表示形式,长度为64位,使得最终消息长度是512的倍数。
初始化哈希值:
使用8个硬编码的32位哈希值作为初始哈希值,这些值代表前8个素数的平方根的小数部分的前32位。
处理消息块
将补位后的消息以512位为单位分块,并对每个消息块进行处理。
使用一系列复杂的运算(包括循环右移、相加后对2^32求余等)和64个常量(这些常量是对自然数中前64个质数的立方根的小数部分取前32比特而来)来更新哈希值。
输出哈希值
经过多轮处理后,得到最终的256位哈希值。
SHA256是SHA-2下细分出的一种算法
SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发,属于SHA算法之一,是SHA-1的后继者。
SHA-2下又可再分为六个不同的算法标准
包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
这些变体除了生成摘要的长度 、循环运行的次数等一些微小差异外,算法的基本结构是一致的。
回到SHA256上,说白了,它就是一个哈希函数。
为了更好的理解SHA256的原理,这里首先将算法中可以单独抽出的模块,包括常量的初始化、信息预处理、使用到的逻辑运算分别进行介绍,甩开这些理解上的障碍后,一起来探索SHA256算法的主体部分,即消息摘要是如何计算的。
SHA256算法中用到了8个哈希初值以及64个哈希常量
其中,SHA256算法的8个哈希初值如下:
// expected
expected[0] = 0x68325720;
expected[1] = 0xaabd7c82;
expected[2] = 0xf30f554b;
expected[3] = 0x313d0570;
expected[4] = 0xc95accbb;
expected[5] = 0x7dc4b5aa;
expected[6] = 0xe11204c0;
expected[7] = 0x8ffe732b;
这些初值是对自然数中前8个质数(2,3,5,7,11,13,17,19)的平方根的小数部分取前32bit而来
在SHA256算法中,用到的64个常量如下:
static const uint32_t K256[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 };
和8个哈希初值类似,这些常量是对自然数中前64个质数(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97…)的立方根的小数部分取前32bit而来。
SHA256算法核心
预处理:
待哈希的消息在继续哈希计算之前首先要进行以下两个步骤:
对消息进行补位处理,是的最终的长度是512位的倍数,然后
以512位为单位对消息进行分块
SHA256对进入的信息要进行初始化,即使消息满足指定结构。信息处理主要是消息填充和附加长度。(1.通过在消息后增加0,使消息达到指定长度。2.并在最后增加消息长度信息)
(1)在报文末尾进行填充,使报文长度在对512取模以后的余数是448。
填充是这样进行的:先补第一个比特为1,然后都补0,直到长度满足对512取模后余数是448。信息必须进行填充,也就是说,即使长度已经满足对512取模后余数是448,补位也必须要进行,这时要填充512个比特。(即最少补一位,最多补512位)。
(2)这里余数是448的原因是填充后,会再附加上一个64bit的数据,用来表示原始报文的长度信息。而448+64=512,正好拼成了一个完整的结构。附加的64bit的数据,即用一个64位的数据表示原始消息的长度(故消息长度必须小于2^64)。长度信息的编码方式为64-bit big-endian integer(可以简单地理解成,从最高位开始数数字长度)。
得到分组后,进行第一步,也就是扩散
扩散:
对于消息分解成的每个512bit的块,需要构成64个字(每个字节是8位二进制,每个字有4个字节,故每个字有32bit)。前16个字直接由原消息组成,记为W[0] 、…W[15]
混淆:
初始哈希值:
取自自然数中前面8个素数(2,3,5,7,11,13,17,19)的平方根的小数部分, 并且取前面的32位。
混淆常量:
取自自然数中前面64个素数的立方根的小数部分的前32位。
我们把最后一轮A-H的值和初始哈希值相加得到第一轮的哈希值,经过64次迭代,可以得到第一个512块函数的摘要。
整体下来则是这样的
最后一次计算的哈希值即为我们最终需要要的哈希值,所以在最后一次合并H0-H7的值即可。
至此,结束我们主要算法部分。
SHA256伪代码
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ***********************/
/ Hash constant words K for SHA-256: */
static const uint32_t K256[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 };
/* Initial hash value H for SHA-256: */
static const uint32_t Initial_Hash[8] = { 0x6a09e667, 0xbb67ae85,
0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
//Initial Hash values for SHA-224
static const uint32_t Initial_Hash_224[8] = { 0xc1059ed8, 0x367cd507,
0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 };
void shaHelper( uint32_t * message, uint32_t * Hash){
uint32_t W[16]={0};
unsigned int i= 0;
unsigned int t =0;
unsigned int counter=0;
uint32_t temp1=0;
uint32_t temp2=0;
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
uint32_t e;
uint32_t f;
uint32_t g;
uint32_t h;
/* Main algorithm /
/ Chunk control. Process 512 bits at a time*/
/Place i-1 Hash into letters. Initialize with initial hash values./
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 (t=0; t < 64; t++){ // need to change to do/while loop.
counter++;
if (t < 16 ) {
W[t] = message[ 16*i + t ];
}
else {
W[t%16] = sigma1(W[(t-2)%16]) + W[(t-7)%16] + sigmaZ(W[(t-15)%16]) + W[(t-16)%16];
}
// Algorithm Proper
temp1 = h + SIG1(e) + Ch(e, f, g) + K256[t] + W[t%16];
temp2 = Maj(a, b, c) +SIGZ(a) ;
h=g;
g=f;
f=e;
e=d + temp1;
d=c;
c=b;
b=a;
a= temp1 + temp2;
}
Hash[0] = Hash[0] + a;
Hash[1] = Hash[1] + b;
Hash[2] = Hash[2] + c;
Hash[3] = Hash[3] + d;
Hash[4] = Hash[4] + e;
Hash[5] = Hash[5] + f;
Hash[6] = Hash[6] + g;
Hash[7] = Hash[7] + h;
}
void SHA_2 ( uint32_t *Message, uint32_t MessageLengthBytes, uint32_t *Hash, short mode){
uint64_t Mbit_Length = MessageLengthBytes*8;
/Variable Declarations go here/
unsigned int leftoverlong = 0;
unsigned int leftoverbits = 0;
uint64_t Nblocks = 0;
unsigned int i= 0;
unsigned int p =0;
unsigned int v =0;
uint64_t M_Length;
uint32_t onemask = 0;
uint32_t zeromask=0;
/* Pre-processing:
-
- Initialize Hash Values 2. Parse the Message block 3. Padd the message block*****/
if( mode==0){
for (i=0;i<=7; i++){
Hash[i] = Initial_Hash_224[i];
} // Initialize Hash for SHA-224
}
else{
for (i=0;i<=7; i++){
Hash[i] = Initial_Hash[i];
} // Initialize Hash for SHA-256
}
i=0; //clear i
/* Message Parsing */
M_Length = Mbit_Length >> 5; // Converting Bit length of message to How many longs in a message
Nblocks = M_Length >> 4; // Number of whole buckets (512 bits or 16 32-bit buckets)
leftoverlong = M_Length % 16; // leftover longs not in a full bucket
leftoverbits = Mbit_Length % 32; // leftover bits in last long
/* Message Padding: The next set of statements finds the end of a message, appends a 1, then adds 0's
- to pad the message to a 512bit chunk. The length of the original message is parsed into the last 2 bytes**/
onemask = 0x80000000>>leftoverbits;
zeromask = ~(0x7FFFFFFF>>leftoverbits);
Message[M_Length]=(Message[M_Length]|onemask);
Message[M_Length]=(Message[M_Length]&zeromask);
if ((Mbit_Length % 512) < 448){ //Check to see if a new block (chunk) is needed
// no new chunk needed
for(v=1; v < (14-leftoverlong); v++){
Message[lastchunk + leftoverlong + v] &= 0x00000000; // zero pad
}
Message[lastchunk + 14]= Mbit_Length >> 32; //append bit length to end of chunk
Message[lastchunk + 15] = Mbit_Length & 0x00000000ffffFFFF;
} else {
//new chunk needed
for (p=1; p < (16-leftoverlong); p++){
Message[lastchunk +leftoverlong +p] = 0x00000000; // zero out remaining bits in chunk
}
for (p=0; p <14; p++){
Message[lastchunk + 16 + p] = 0x00000000; //zero out next chunk
}
Message[lastchunk + 30]= Mbit_Length >> 32; //append bit length to end of chunk
Message[lastchunk + 31] = Mbit_Length & 0x0000FFFF;
}
i=0;
while(i<(((Mbit_Length+64)/512) + ((Mbit_Length + 64) && 0x1FF)) ){
// run hash core function
shaHelper( Message+(16*i), Hash);
i++;
}
}
void sha256_example1(uint32_t * hash){
// Input: 0xbd (1 byte)
// Expected Result: 68325720 aabd7c82 f30f554b 313d0570 c95accbb 7dc4b5aa e11204c0 8ffe732b
int i;
// Space must be reserved for 64 bytes
uint32_t message[16];
uint32_t expected[8];
uint32_t bytes_to_be_hashed;
short hash_mode;
// expected
expected[0] = 0x68325720;
expected[1] = 0xaabd7c82;
expected[2] = 0xf30f554b;
expected[3] = 0x313d0570;
expected[4] = 0xc95accbb;
expected[5] = 0x7dc4b5aa;
expected[6] = 0xe11204c0;
expected[7] = 0x8ffe732b;
// MSB contains message
message[0]=0xbd000000;
bytes_to_be_hashed = 1;
hash_mode = SHA_256;
SHA_2( &message[0], bytes_to_be_hashed, hash, hash_mode);
for (i=0;i<8;i++)
{
if (hash[i] != expected[i])
return(-1);
}
return(0);
}
上一篇帖子中我讲了AES128算法
,现在我就在AES128的KEIL工程上添加SHA256算法
1.先打开KEIL
添加路劲和文件
添加头文件
#include "sha2.h"
int sha256_example1(uint32_t * hash){
// Input: 0xbd (1 byte)
// Expected Result: 68325720 aabd7c82 f30f554b 313d0570 c95accbb 7dc4b5aa e11204c0 8ffe732b
int i;
// Space must be reserved for 64 bytes
uint32_t message[16];
uint32_t expected[8];
uint32_t bytes_to_be_hashed;
short hash_mode;
// expected
expected[0] = 0x68325720;
expected[1] = 0xaabd7c82;
expected[2] = 0xf30f554b;
expected[3] = 0x313d0570;
expected[4] = 0xc95accbb;
expected[5] = 0x7dc4b5aa;
expected[6] = 0xe11204c0;
expected[7] = 0x8ffe732b;
// MSB contains message
message[0]=0xbd000000;
for(i=0;i<8;i++)printf("原始数据expected[%d] = 0x%x\r\n",i,expected[i]);
bytes_to_be_hashed = 1;
hash_mode = SHA_256;
SHA_2( &message[0], bytes_to_be_hashed, hash, hash_mode);
for (i=0;i<8;i++)
{
if (hash[i] != expected[i])
{
return(-1);
}
printf("解密后的hash[%d] = 0x%x\r\n",i,hash[i]);
}
return(0);
}
烧录程序,通过串口助手查看数据
测试SHA256算法正确