时间:2022年2月11日14:04:54
来源参考:SHA256算法原理详解_随煜而安的专栏-CSDN博客_sha256https://blog.csdn.net/u011583927/article/details/80905740
代码参考:
SHA256算法原理和代码实现(java) - harara-小念 - 博客园 (cnblogs.com)https://www.cnblogs.com/kiko2014551511/p/15609112.htmlSHA256转换工具:Generate a SHA-256 encrypted hash (online-convert.com)https://hash.online-convert.com/sha256-generator
下面是文档的伪代码说明,中文为我个人的理解,具体说明可以参考第一篇文章,那边说得比较详细,这里只是严格按照伪代码进行JAVA语言的翻译和实现。
// Note: All variables are unsigned 32 bits and wrap modulo 232 when calculating
// 注:计算时,所有变量均为无符号32位,并以232模换行
//
//
// Initialize variables
// (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
// 前8个质数的平方根的小数部分,32位
// h0 := 0x6a09e667
// h1 := 0xbb67ae85
// h2 := 0x3c6ef372
// h3 := 0xa54ff53a
// h4 := 0x510e527f
// h5 := 0x9b05688c
// h6 := 0x1f83d9ab
// h7 := 0x5be0cd19
//
//
// Initialize table of round constants
// (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
// 前64个质数的立方根的小数部分,32位
// k[0..63] :=
// 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
//
//
// Pre-processing: 预处理
// append the bit '1' to the message 先添加 “1”
// append k bits '0', where k is the minimum number >= 0 such that the resulting message
// length (in bits) is congruent to 448(mod 512) 补0 到模512取余后长度为448
// append length of message (before pre-processing), in bits, as 64-bit big-endian integer 添加64位源数据的长度
//
//
// Process the message in successive 512-bit chunks: 将消息分为512位的块(chunk)
// break message into 512-bit chunks
// for each chunk
// 对于每一个块
// break chunk into sixteen 32-bit big-endian words w[0..15]
// 将每个块分为16个,每个32位的字(word)
//
// Extend the sixteen 32-bit words into sixty-four 32-bit words: 将16个32位字扩展为64个32位字,前16个不变
// for i from 16 to 63
// 从第16到63个转换方式
// s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor(w[i-15] rightshift 3)
// s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor(w[i-2] rightshift 10)
// w[i] := w[i-16] + s0 + w[i-7] + s1
//
// Initialize hash value for this chunk:
// a := h0
// b := h1
// c := h2
// d := h3
// e := h4
// f := h5
// g := h6
// h := h7
//
// Main loop:
// for i from 0 to 63
// s0 := (a rightrotate 2) xor (a rightrotate 13) xor(a rightrotate 22)
// maj := (a and b) xor (a and c) xor(b and c)
// t2 := s0 + maj
// s1 := (e rightrotate 6) xor (e rightrotate 11) xor(e rightrotate 25)
// ch := (e and f) xor ((not e) and g)
// t1 := h + s1 + ch + k[i] + w[i]
// h := g
// g := f
// f := e
// e := d + t1
// d := c
// c := b
// b := a
// a := t1 + t2
//
// Add this chunk's hash to result so far:
// h0 := h0 + a
// h1 := h1 + b
// h2 := h2 + c
// h3 := h3 + d
// h4 := h4 + e
// h5 := h5 + f
// h6 := h6 + g
// h7 := h7 + h
//
// Produce the final hash value (big-endian):
// digest = hash = h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7
简单总结一下流程:
1.将输入的文本转化为二进制(这里用了String,而且受到String长度的限制(2^16),所以不能代表所有的场景)
对应方法:toBinary(String in)
2. 规范化源数据。对应方法:addZeroTo512(StringBuilder binaryIn)
2.1 在二进制后面补一个“1”
2.2 在后面补0,直到整个长度按512取模余数为448,为什么是448呢?因为下面还有讲
2.3 再加上64位的源数据长度
总之,最后得到的长度肯定是512的倍数
3. 计算。cycleCalculation(String binaryIn)
3.1 将整段数据按照512长度(二进制)分段(chunk),分成n段
3.2 将每一段(512位)分成16个“字(word)”,每个字为32位
3.3 将16个字扩充为64个字:
3.3.1 前16个字就是原有的
3.3.2 后面的字按照如下算法来计算:
// s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor(w[i-15] rightshift 3)
// s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor(w[i-2] rightshift 10)
// w[i] := w[i-16] + s0 + w[i-7] + s1
说明1:rightrotate:表示循环右移,在无符号32位数字的情况下,将二进制位右移,右边溢出的数字补到开头。简单的例子(4位):1001-》1100-》0110
说明2:rightshift:表示右移,左边补0,右边溢出的数字则舍去。例子(4位):1001-》0100-》0010
3.4 以前面准备的数据,循环64次计算
3.5 将最终的h0到h7组合,为最终的值
代码中使用long模拟32为无符号整数,也可以用int来直接模拟,但是需要忽略计算中的负数(显示为负数,但是计算主要涉及到的是二进制的计算,不影响使用,在转为二进制时,需要手动添加前导0而达到32位长度。)。
具体实现如下:
package sha;
public class Sha256 {
// 用long模拟32位无符号数据
// 前32位0,后32位1的数字,控制数字在32位,4294967295
final static long OVER = 0xFFFFFFFFL;
// 前8个质数的平方根的小数
// long[] h = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
static long h0 = 0x6a09e667L;
static long h1 = 0xbb67ae85L;
static long h2 = 0x3c6ef372L;
static long h3 = 0xa54ff53aL;
static long h4 = 0x510e527fL;
static long h5 = 0x9b05688cL;
static long h6 = 0x1f83d9abL;
static long h7 = 0x5be0cd19L;
// 前64个质数的立方根的小数
static long[] k = {
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 };
public static String getSha256(String in) {
StringBuilder stringBuilder = toBinary(in);
String string = addZeroTo512(stringBuilder);
return cycleCalculation(string);
}
// 1. 将输入的字符串转换为二进制in
// string的长度限制在2^32
private static StringBuilder toBinary(String in) {
StringBuilder stringBuilder = new StringBuilder();
char[] inArray = in.toCharArray();
for (int i = 0; i < inArray.length; i++) {
// 还有前导的0
String binary = Integer.toBinaryString(inArray[i]);
int count0 = 8 - binary.length();
for (int j = 0; j < count0; j++) {
stringBuilder.append("0");
}
stringBuilder.append(binary);
}
return stringBuilder;
}
// 2. 将输入的二进制的长度补齐到512的倍数,原长度l + 1(默认1位1) + k(补齐的0) + 64(64为二进制表示的输入字符串的长度)
private static String addZeroTo512(StringBuilder binaryIn) {
int l = binaryIn.length();
int k = 959 - (l % 512);
if (k > 512) {
k = k - 512;
}
// 默认先添加1
binaryIn.append("1");
// 添加k个0
for (int i = 0; i < k; i++) {
binaryIn.append("0");
}
// 添加64位的源数据长度
String lengthBinary = Integer.toBinaryString(l);
int k2 = 64 - lengthBinary.length();
for (int i = 0; i < k2; i++) {
binaryIn.append("0");
}
binaryIn.append(lengthBinary);
return binaryIn.toString();
}
// 核心:循环计算
/**
*
* @param binaryIn 通过补位后的源数据
* @return
*/
private static String cycleCalculation(String binaryIn) {
// 1. 按照512的长度分成n个消息块
int n = binaryIn.length() / 512;
for (int i = 0; i < n; i++) {
// 对于每一个块
// 2. 每个消息块分成16个32位的“字”
String[] wString = new String[16];
for (int j = 0; j < 16; j++) {
wString[j] = binaryIn.substring(32 * j, 32 + 32 * j);
}
// 3. 将16个字扩充为64个字,转换方法
long[] w = new long[64];
for (int j = 0; j < wString.length; j++) {
// 将二进制的string转为10进制的long
w[j] = Long.parseLong(wString[j], 2);
}
// s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor(w[i-15] rightshift 3)
// s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor(w[i-2] rightshift 10)
// w[i] := w[i-16] + s0 + w[i-7] + s1
for (int j = 16; j < 64; j++) {
long s0 = ((rightRotate(w[j - 15], 7)) ^ rightRotate(w[j - 15], 18) ^ (rightShift(w[j - 15], 3))) & OVER;
long s1 = ((rightRotate(w[j - 2], 17)) ^ (rightRotate(w[j - 2], 19)) ^ (rightShift(w[j - 2], 10))) & OVER;
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) & OVER;
}
// 4. hash初始化
long a = h0;
long b = h1;
long c = h2;
long d = h3;
long e = h4;
long f = h5;
long g = h6;
long h = h7;
// 5. 64次循环
for (int j = 0; j < 64; j++) {
// s0 := (a rightrotate 2) xor (a rightrotate 13) xor(a rightrotate 22)
// maj := (a and b) xor (a and c) xor(b and c)
// t2 := s0 + maj
// s1 := (e rightrotate 6) xor (e rightrotate 11) xor(e rightrotate 25)
// ch := (e and f) xor ((not e) and g)
// t1 := h + s1 + ch + k[i] + w[i]
// h := g
// g := f
// f := e
// e := d + t1
// d := c
// c := b
// b := a
// a := t1 + t2
long s0 = (rightRotate(a, 2) ^ (rightRotate(a, 13)) ^ (rightRotate(a, 22))) & OVER;
long maj = ((a & b) ^ (a & c) ^ (b & c)) & OVER;
long t2 = (s0 + maj) & OVER;
long s1 = ((rightRotate(e, 6)) ^ (rightRotate(e, 11)) ^ (rightRotate(e, 25))) & OVER;
long ch = ((e & f) ^ ((~e) & g)) & OVER;
long t1 = (h + s1 + ch + k[j] + w[j]) & OVER;
h = g;
g = f;
f = e;
e = (d + t1) & OVER;
d = c;
c = b;
b = a;
a = (t1 + t2) & OVER;
}
h0 = (h0 + a) & OVER;
h1 = (h1 + b) & OVER;
h2 = (h2 + c) & OVER;
h3 = (h3 + d) & OVER;
h4 = (h4 + e) & OVER;
h5 = (h5 + f) & OVER;
h6 = (h6 + g) & OVER;
h7 = (h7 + h) & OVER;
}
// 把h0到h7拼起来就是所需要的值了。
return getHash(h0) + getHash(h1) + getHash(h2) + getHash(h3) + getHash(h4) + getHash(h5) + getHash(h6) + getHash(h7);
}
// 把long型的数字转换为字符串,只取后32位的数字,不足则前补0,前面多余的则舍去
private static String getHash(long h) {
String hash = Long.toHexString(h);
StringBuilder result = new StringBuilder();
if (hash.length() > 8) {
result.append(hash.substring(hash.length() - 8, hash.length()));
} else {
int count0 = 8 - hash.length();
for (int i = 0; i < count0; i++) {
result.append("0");
}
result.append(hash);
}
return result.toString();
}
/**
* 右旋n位,循环右移n位,对于32位无符号数字而言,末尾的数字被移到开头
* 用long模拟32位无符号数字,末尾被移出的数字替换到开头,相当于左移了
* @param x
* @param n
* @return
*/
private static long rightRotate(long x, int n) {
long wei = (0 << n) - 1; // 这个操作有点疑问
x = ((wei & (x & OVER)) << (32 - n)) | (x & OVER) >> n;
return x;
}
/**
* 按位右移n位,末尾的数字舍去,前面补0,相当于>>>,无符号右移
* @param x
* @param n
* @return
*/
private static long rightShift(long x, int n) {
// return (x&0xFFFFFFFFL)>>n;
return (x & OVER) >>> n;
}
}
测试:
public static void main(String[] args) {
System.out.println(Sha256.getSha256("a"));
}
输出:
ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb