除空字符串外,自定义 SHA256 哈希计算失败

Custom SHA256 hash calculation fails on anything but an empty String

我正在尝试创建自己的散列 framework/library,但我偶然发现了一个问题。当我计算一个空字符串的 SHA256 哈希时,哈希计算成功,但是当我计算其他任何东西时,它失败了。有人可以帮我弄清楚为什么吗?

Wikipedia 所提供,在线执行并使用 python 时,此哈希匹配。

let h = SHA256(message: Data("".utf8))
let d = h.digest()
// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
print(d)

但是'Hello world'没有

let h = SHA256(message: Data("Hello world".utf8))
let d = h.digest()
// ce9f4c08f0688d09b8061ed6692c1d5af2516c8682fad2d9a5d72f96ba787a80
print(d)
// Expected:
// 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c

希望有人能帮助我。 SHA256 实现如下:

/*
 First 32 bits of the fractional parts of the
 square roots of the first 8 primes 2..19.
*/

fileprivate let kSHA256H0: UInt32 = 0x6a09e667
fileprivate let kSHA256H1: UInt32 = 0xbb67ae85
fileprivate let kSHA256H2: UInt32 = 0x3c6ef372
fileprivate let kSHA256H3: UInt32 = 0xa54ff53a
fileprivate let kSHA256H4: UInt32 = 0x510e527f
fileprivate let kSHA256H5: UInt32 = 0x9b05688c
fileprivate let kSHA256H6: UInt32 = 0x1f83d9ab
fileprivate let kSHA256H7: UInt32 = 0x5be0cd19

/*
 First 32 bits of the fractional parts of the
 cube roots of the first 64 primes 2..311.
 */

fileprivate let kSHA256K: [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
]

/// Shift the value of x n amount to the right.
/// - Parameters:
///   - x: The value to shift.
///   - n: The amount to shift by.
/// - Returns: The shifted value.
fileprivate func shiftRight(_ x: UInt32, _ n: UInt32) -> UInt32 { x >> n }

/// Rotate the value of x n amount of times.
/// - Parameters:
///   - x: The value to rotate.
///   - y: The amount to rotate by.
/// - Returns: The rotated value.
fileprivate func rotateRight(_ x: UInt32, _ y: UInt32) -> UInt32 { (x >> (y & 31)) | (x << (32 - (y & 31))) }

/// Split data into chunks of specified size.
/// - Note: This function will not pad or append data
/// to make sure all the chunks are equal in size.
/// - Parameters:
///   - data: The data to split.
///   - size: The size of a chunk.
/// - Returns: An array containing chunks of specified size (when able).
fileprivate func chunk(_ data: Data, toSize size: Int) -> [Data] {
    stride(from: 0, to: data.count, by: size).map {
        data.subdata(in: [=12=] ..< Swift.min([=12=] + size, data.count))
    }
}

public class SHA256 {
    
    /// The pre-processed data.
    fileprivate let message: Data
    
    fileprivate var hash = [
        kSHA256H0, kSHA256H1, kSHA256H2, kSHA256H3,
        kSHA256H4, kSHA256H5, kSHA256H6, kSHA256H7
    ]
    
    public init(message: Data) {
        self.message = Self.preProcess(message: message)
    }
    
    fileprivate static func preProcess(message: Data) -> Data {
        let L = message.count * 8   // Original message length in bits.
        var K = 0                   // Required padding bits.
        
        while (L + 1 + K + 64) % 512 != 0 {
            K += 1
        }
        
        var padding = Data(repeating: 0, count: K / 8)
        padding.insert(0x80, at: 0) // Insert 1000 0000 into the padding.
        
        var length = UInt64(L).bigEndian
        return message + padding + Data(bytes: &length, count: 8)
    }
    
    public func digest() -> Data {
        let chunks = chunk(message, toSize: 64)
        for chunk in chunks {
            var w = [UInt32](repeating: 0, count: 64)   // 64-entry message schedule array of 32-bit words.
            
            // Copy the chunk into first 16 words w[0..15] of the schedule array.
            for i in 0 ..< 16 {
                let sub = chunk.subdata(in: i ..< i + 4)
                w[i] = sub.withUnsafeBytes { [=12=].load(as: UInt32.self) }.bigEndian
            }
            
            // Extend the first 16 words into the remaining 48 words w[16..63] of the schedule array.
            for i in 16 ..< 64 {
                let s0 = rotateRight(w[i - 15], 7) ^ rotateRight(w[i - 15], 18) ^ shiftRight(w[i - 15], 3)
                let s1 = rotateRight(w[i - 2], 17) ^ rotateRight(w[i - 2], 19) ^ shiftRight(w[i - 2], 10)
                w[i] = s1 &+ w[i - 7] &+ s0 &+ w[i - 16]
            }
            
            // Create some working variables.
            var a = hash[0]
            var b = hash[1]
            var c = hash[2]
            var d = hash[3]
            var e = hash[4]
            var f = hash[5]
            var g = hash[6]
            var h = hash[7]
            
            // Compress function main loop.
            for i in 0 ..< 64 {
                let S1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25)
                let ch = (e & f) ^ (~e & g)
                let T1 = h &+ S1 &+ ch &+ kSHA256K[i] &+ w[i]
                let S0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)
                let maj = (a & b) ^ (a & c) ^ (b & c)
                let T2 = S0 &+ maj
                
                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
        }
        
        return hash.map {
            var num = [=12=].bigEndian
            return Data(bytes: &num, count: 4)
        }.reduce(Data(), +)
    }

}

事实证明,我创建了错误的子数据来构造我的 UInt32 从创建消息计划数组。 (.digest() 函数中的前几行)

旧的是

let sub = chunk.subdata(in: i ..< i + 4)

新的是

let sub = chunk.subdata(in: i * 4 ..< (i * 4) + 4)

这解决了问题