SHA-2

SHA-2算法概述

SHA-2家族成员(SHA-224、SHA-256、SHA-384、SHA-512 分别返回不同比特的结果)

SHA-2算法核心步骤

  1. 消息预处理(填充与长度附加)
  2. 初始化
  3. 消息分块处理
  4. 格式化输出

步骤详细

消息预处理(填充与长度附加)

  1. 在原始消息末尾添加一个'1'位(十六进制0x80)

  2. 填充0x00位直到 填充后的消息长度 ≡ 448 mod 512 单位是bit (512 bit = 64 字节)

  3. 最后添加64 bit (8 字节)的原始消息长度(大端序)

不足64字节 可填充部分 > 9字节 (64 > 41 + 9 )

41Byte = 41 * 8 = 328bit = 0x0148 bit

不足64字节, 可填充部分 = 9字节 (64 = 55 + 9)

55Byte = 55 * 8 = 440bit = 0x01B8 bit

不足64字节, 可填充部分 < 9字节 (64 < 56 + 9)

56Byte = 56 * 8 = 448bit = 0x01C0 bit

初始化(K数组和H数组)

K数组: 取自自然数中前64个素数的立方根的小数部分 的前32bit (8字节)

H数组: 取自自然数中前8个素数的平方根的小数部分的前32位(8字节)

c 复制代码
// 初始化常量K(前64个素数的立方根的小数部分的前32位)
var K = [64]uint32{
	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个素数的平方根的小数部分的前32位)
var H = [8]uint32{
	0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
	0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
}

消息分块处理

  1. 消息调度:
    1. 将512位块分为16个32位字
    2. 使用σ0和σ1函数扩展生成64个字
  2. 压缩函数:
    1. 初始化8个工作变量(a-h)
    2. 执行64轮迭代,每轮使用:
    3. 选择函数(Ch)
    4. 多数函数(Maj)
    5. Σ0和Σ1函数
    6. 常量K[i]
    7. 调度字w[i]
  3. 更新hash
    1. 将工作变量的结果累加到当前哈希值
    2. 使用模2³²加法
c 复制代码
    blocks := len(data) / 64
	hash := H // 使用初始哈希值
	for i := 0; i < blocks; i++ {
		// 从当前块创建消息调度数组
		var w [64]uint32
		block := data[i*64 : (i+1)*64]
		
		// 将前16个元素设置为消息块的内容
		for j := 0; j < 16; j++ {
			w[j] = binary.BigEndian.Uint32(block[j*4 : (j+1)*4])
		}
		
		// 扩展消息调度数组(16-63)
		for j := 16; j < 64; j++ {
			w[j] = sigma1(w[j-2]) + w[j-7] + sigma0(w[j-15]) + w[j-16]
		}
		
		// 初始化工作变量
		a, b, c, d, e, f, g, h := hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7]
		
		// 3. 主循环(64轮)
		for j := 0; j < 64; j++ {
			// 计算临时变量
			t1 := h + sum1(e) + ch(e, f, g) + K[j] + w[j]
			t2 := sum0(a) + maj(a, b, c)
			
			// 更新工作变量
			h = g
			g = f
			f = e
			e = d + t1
			d = c
			c = b
			b = a
			a = t1 + t2
		}
		
		// 4. 更新哈希值
		hash[0] += a
		hash[1] += b
		hash[2] += c
		hash[3] += d
		hash[4] += e
		hash[5] += f
		hash[6] += g
		hash[7] += h
	}

格式化输出

  1. 将8个32位哈希值转换为大端序字节数组

  2. 拼接成32字节的最终结果

XOR

定义

  1. XOR(通常表示为 ⊕)

作用

  1. 交换变量值‌:XOR运算可以在不使用临时变量的情况下交换两个变量的值。具体操作如下:假设有两个变量A和B,初始值分别为X和Y。可以将A与B进行XOR运算,结果存储在临时变量T中。然后,将T与A进行XOR运算,恢复A的原始值。最后,将B与T进行XOR运算,实现B的值变为Y,同时A的值也变为X。这种方法不仅适用于简单的数值交换,还可以扩展到更大的数据结构和复杂的逻辑操作中‌

条件判断‌:在布尔运算中,XOR运算的特点是当两个操作数一真一假时,结果为真;否则为假。

‌数据安全‌:XOR运算常用于数据加密和校验。通过将明文与密钥进行XOR运算,可以得到密文,从而实现数据的加密。同样,通过将密文与密钥进行XOR运算,可以恢复出原始数据。

字节序

0x01020304, 0x05060708

小端序 :

大端序 :

完整函数

c 复制代码
package main

import (
	"encoding/binary"
	"fmt"
)

// 初始化常量K(前64个素数的立方根的小数部分的前32位)
var K = [64]uint32{
	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个素数的平方根的小数部分的前32位)
var H = [8]uint32{
	0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
	0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
}

// 辅助函数:循环右移
作用:执行32位整数的循环右移操作

原理:
将x右移n位,获取右侧n位
将x左移(32-n)位,获取左侧32-n位
通过位或(|)操作合并两部分

示例:rotr(0x12345678, 8) → 0x78123456

func rotr(x uint32, n uint) uint32 {
	return (x >> n) | (x << (32 - n))
}

// 辅助函数:按位选择函数(Ch)
作用:根据e的位值选择f或g的对应位

真值表:

e	f	g	结果
0	0	0	0
0	0	1	1
0	1	0	0
0	1	1	1
1	0	0	0
1	0	1	0
1	1	0	1
1	1	1	1

原理:当e=1时,选择f的对应位 当e=0时,选择g的对应位

func ch(e, f, g uint32) uint32 {
	return (e & f) ^ (^e & g)
}

// 辅助函数:多数函数(Maj)
作用:返回a,b,c三个输入中占多数的位值

真值表:

a	b	c	结果
0	0	0	0
0	0	1	0
0	1	0	0
0	1	1	1
1	0	0	0
1	0	1	1
1	1	0	1
1	1	1	1
原理:当至少两个输入为1时输出1

func maj(a, b, c uint32) uint32 {
	return (a & b) ^ (a & c) ^ (b & c)
}

// 辅助函数:Σ0函数
作用:对输入进行非线性扩散

操作:
循环右移2位
循环右移13位
循环右移22位
三个结果进行异或

目的:破坏输入中的位相关性,增加混

func sum0(x uint32) uint32 {
	return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22)
}

// 辅助函数:Σ1函数
作用:对输入进行非线性扩散

操作:
循环右移6位
循环右移11位
循环右移25位
三个结果进行异或

目的:破坏输入中的位相关性,增加混淆

func sum1(x uint32) uint32 {
	return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25)
}

// 辅助函数:σ0函数
作用:消息扩展中的非线性变换

操作:
循环右移7位
循环右移18位
逻辑右移3位

三个结果进行异或
目的:扩展消息块时引入非线性特性

func sigma0(x uint32) uint32 {
	return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3)
}

// 辅助函数:σ1函数
操作:
循环右移17位
循环右移19位
逻辑右移10位
三个结果进行异或

目的:扩展消息块时引入非线性特性

func sigma1(x uint32) uint32 {
	return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10)
}

// SHA-256主函数
func sha256(data []byte) [32]byte {
	// 1. 消息预处理(填充)
	ml := uint64(len(data) * 8) // 原始消息长度(位)
	data = append(data, 0x80)  // 添加1个1位和7个0位(0x80 = 10000000)
	
	// 计算需要填充的0字节数
	padding := (56 - (len(data) % 64)) % 64
	if padding < 0 {
		padding += 64
	}
	
	// 添加填充字节(0x00)
	data = append(data, make([]byte, padding)...)
	
	// 添加64位原始消息长度(大端序)
	mlBytes := make([]byte, 8)
	binary.BigEndian.PutUint64(mlBytes, ml)
	data = append(data, mlBytes...)

	// 2. 处理每个512位块
	blocks := len(data) / 64
	hash := H // 使用初始哈希值
	
	for i := 0; i < blocks; i++ {
		// 从当前块创建消息调度数组
		var w [64]uint32
		block := data[i*64 : (i+1)*64]
		
		// 将前16个元素设置为消息块的内容
		for j := 0; j < 16; j++ {
			w[j] = binary.BigEndian.Uint32(block[j*4 : (j+1)*4])
		}
		
		// 扩展消息调度数组(16-63)
		for j := 16; j < 64; j++ {
			w[j] = sigma1(w[j-2]) + w[j-7] + sigma0(w[j-15]) + w[j-16]
		}
		
		// 初始化工作变量
		a, b, c, d, e, f, g, h := hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7]
		
		// 3. 主循环(64轮)
		for j := 0; j < 64; j++ {
			// 计算临时变量
			t1 := h + sum1(e) + ch(e, f, g) + K[j] + w[j]
			t2 := sum0(a) + maj(a, b, c)
			
			// 更新工作变量
			h = g
			g = f
			f = e
			e = d + t1
			d = c
			c = b
			b = a
			a = t1 + t2
		}
		
		// 4. 更新哈希值
		hash[0] += a
		hash[1] += b
		hash[2] += c
		hash[3] += d
		hash[4] += e
		hash[5] += f
		hash[6] += g
		hash[7] += h
	}
	
	// 5. 格式化输出为32字节数组
	var result [32]byte
	for i, s := range hash {
		binary.BigEndian.PutUint32(result[i*4:(i+1)*4], s)
	}
	return result
}

// 辅助函数:将字节数组转换为十六进制字符串
func toHex(b [32]byte) string {
	const hextable = "0123456789abcdef"
	var out [64]byte
	for i, v := range b {
		out[i*2] = hextable[v>>4]
		out[i*2+1] = hextable[v&0x0f]
	}
	return string(out[:])
}

func main() {
	// 测试用例
	tests := []struct {
		input  string
		expect string
	}{
		{
			"",
			"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
		},
		{
			"hello world",
			"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
		},
		{
			"The quick brown fox jumps over the lazy dog",
			"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
		},
	}
	
	// 运行测试
	for _, test := range tests {
		hash := sha256([]byte(test.input))
		hex := toHex(hash)
		fmt.Printf("输入: %q\n", test.input)
		fmt.Printf("计算: %s\n", hex)
		fmt.Printf("预期: %s\n", test.expect)
		fmt.Printf("结果: %t\n\n", hex == test.expect)
	}
}