Swift实现MD5

作者: LimChihi | 来源:发表于2017-04-02 14:20 被阅读106次

    前言

    我在简书或者别的地方找的MD5计算几乎全部都是使用了Objective-C的库。前几日在看喵神Kingfisher的源码的时候发现有纯Swift实现的MD5,然后我把这部分剥离出来,并做提升了大概25%的速度(没卵用的提升,因为原本的就已经很快)。

    Github

    环境

    • Xcode 8.2
    • Swift 3.0
    • macOS 10.12.3

    实现

    主要的实现都在MD5.swift

    struct MD5 {
        let message: [UInt8]
        
        init (_ message: [UInt8]) {
            self.message = message
        }
        
        /** specifies the per-round shift amounts */
        private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
                                        5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
                                        4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
                                        6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]
        
        /** binary integer part of the sines of integers (Radians) */
        private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
                                       0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
                                       0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
                                       0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
                                       0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
                                       0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
                                       0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
                                       0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
                                       0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
                                       0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
                                       0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
                                       0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
                                       0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
                                       0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
                                       0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
                                       0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]
        
        private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
        
        func calculate() -> [UInt8] {
            var tmpMessage = prepare(64)
            tmpMessage.reserveCapacity(tmpMessage.count + 4)
            
            // hash values
            var hh = hashes
            
            // Step 2. Append Length a 64-bit representation of lengthInBits
            let lengthInBits = (message.count * 8)
            let lengthBytes = lengthInBits.bytes(64 / 8)
            tmpMessage += lengthBytes.reversed()
            
            // Process the message in successive 512-bit chunks:
            let chunkSizeBytes = 512 / 8 // 64
            
            for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
                // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
                var M = toUInt32Array(chunk)
                assert(M.count == 16, "Invalid array")
                
                // Initialize hash value for this chunk:
                var A = hh[0]
                var B = hh[1]
                var C = hh[2]
                var D = hh[3]
                
                var dTemp: UInt32 = 0
                
                // Main loop
                for j in 0 ..< sines.count {
                    var g = 0
                    var F: UInt32 = 0
                    
                    switch j {
                    case 0...15:
                        F = (B & C) | ((~B) & D)
                        g = j
                    case 16...31:
                        F = (D & B) | (~D & C)
                        g = (5 * j + 1) % 16
                    case 32...47:
                        F = B ^ C ^ D
                        g = (3 * j + 5) % 16
                    case 48...63:
                        F = C ^ (B | (~D))
                        g = (7 * j) % 16
                    default:
                        break
                    }
                    dTemp = D
                    D = C
                    C = B
                    B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j])
                    A = dTemp
                }
                
                hh[0] = hh[0] &+ A
                hh[1] = hh[1] &+ B
                hh[2] = hh[2] &+ C
                hh[3] = hh[3] &+ D
            }
            
            var result = [UInt8]()
            result.reserveCapacity(hh.count / 4)
            
            hh.forEach {
                let itemLE = $0.littleEndian
                let r1 = UInt8(itemLE & 0xff)
                let r2 = UInt8((itemLE >> 8) & 0xff)
                let r3 = UInt8((itemLE >> 16) & 0xff)
                let r4 = UInt8((itemLE >> 24) & 0xff)
                result += [r1, r2, r3, r4]
            }
            return result
        }
        
        private func toUInt32Array(_ slice: ArraySlice<UInt8>) -> Array<UInt32> {
            var result = Array<UInt32>()
            result.reserveCapacity(16)
            
            for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout<UInt32>.size) {
                let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24
                let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16
                let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8
                let d3 = UInt32(slice[idx])
                let val: UInt32 = d0 | d1 | d2 | d3
                
                result.append(val)
            }
            return result
        }
        
        private func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 {
            return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits))
        }
        
        private func prepare(_ len: Int) -> Array<UInt8> {
            var tmpMessage = message
            
            // Step 1. Append Padding Bits
            tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
            
            // append "0" bit until message length in bits ≡ 448 (mod 512)
            var msgLength = tmpMessage.count
            var counter = 0
            
            while msgLength % len != (len - 8) {
                counter += 1
                msgLength += 1
            }
            
            tmpMessage += Array<UInt8>(repeating: 0, count: counter)
            return tmpMessage
        }
        
    }
    
    
    extension MD5 {
        fileprivate struct BytesIterator: IteratorProtocol {
            
            let chunkSize: Int
            let data: [UInt8]
            
            init(chunkSize: Int, data: [UInt8]) {
                self.chunkSize = chunkSize
                self.data = data
            }
            
            var offset = 0
            
            mutating func next() -> ArraySlice<UInt8>? {
                let end = min(chunkSize, data.count - offset)
                let result = data[offset..<offset + end]
                offset += result.count
                return result.count > 0 ? result : nil
            }
        }
        
        fileprivate struct BytesSequence: Sequence {
            let chunkSize: Int
            let data: [UInt8]
            
            func makeIterator() -> BytesIterator {
                return BytesIterator(chunkSize: chunkSize, data: data)
            }
        }
    }
    
    

    其中使用到了Int的一个拓展

    extension Int {
        /** Array of bytes with optional padding (little-endian) */
        func bytes(_ totalBytes: Int = MemoryLayout<Int>.size) -> [UInt8] {
            let valuePointer = UnsafeMutablePointer<Int>.allocate(capacity: 1)
            valuePointer.pointee = self
            
            let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in
                var bytes = [UInt8](repeating: 0, count: totalBytes)
                let minNum = MemoryLayout<Int>.size < totalBytes ? MemoryLayout<Int>.size : totalBytes
                for j in 0..<minNum {
                    bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
                }
                return bytes
            }
            
            valuePointer.deinitialize()
            valuePointer.deallocate(capacity: 1)
            
            return bytes
        }
    }
    

    最后在拓展String

    extension String {
        var md5: String {
            if let data = self.data(using: .utf8, allowLossyConversion: true) {
                
                let message = data.withUnsafeBytes { bytes -> [UInt8] in
                    return Array(UnsafeBufferPointer(start: bytes, count: data.count))
                }
                
                let MD5Calculator = MD5(message)
                let MD5Data = MD5Calculator.calculate()
                var md5String = String()
                for c in MD5Data {
                    md5String += String(format: "%02x", c)
                }
                return md5String
            } else {
                return self
            }
        }
    }
    
    

    使用:

    
    let md5 = "HelloWorld".md5
    

    相关文章

      网友评论

      • 安勒个安:我也在找纯swift库的时候看到了喵神自己撸的MD5,但是没法使用- -感谢楼主

      本文标题:Swift实现MD5

      本文链接:https://www.haomeiwen.com/subject/gtjgottx.html