简体   繁体   中英

Custom SHA256 hash calculation fails on anything but an empty String

I am trying to create my own hashing framework/library, but I've stumbled across an issue. When I calculate the SHA256 hash of an empty string, the hash is calculated successfully, but when I calculate it for anything else, it fails. Can someone help me figure out why?

As provided by Wikipedia , when performed online and using python, this hash matches.

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

But 'Hello world' does not

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

I hope someone can help me. SHA256 implementation below:

/*
 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: $0 ..< Swift.min($0 + 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 { $0.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 = $0.bigEndian
            return Data(bytes: &num, count: 4)
        }.reduce(Data(), +)
    }

}

Turns out, I was creating the wrong sub data to construct my UInt32 's from to create the message schedule array. (The first couple of lines in the .digest() function)

The old one was

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

The new one is

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

This resolves the issue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM