108 lines
3.4 KiB
Swift
108 lines
3.4 KiB
Swift
import Foundation
|
|
|
|
/// MD5 (RFC 1321) — pure Swift. Legacy-post-workflow compatibility only;
|
|
/// integrity verification uses xxHash64.
|
|
public struct MD5 {
|
|
private var a0: UInt32 = 0x67452301
|
|
private var b0: UInt32 = 0xefcdab89
|
|
private var c0: UInt32 = 0x98badcfe
|
|
private var d0: UInt32 = 0x10325476
|
|
private var buffer = [UInt8]()
|
|
private var totalLength: UInt64 = 0
|
|
|
|
private static let s: [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,
|
|
]
|
|
|
|
private static let k: [UInt32] = (0..<64).map {
|
|
UInt32(truncatingIfNeeded: Int64(abs(sin(Double($0 + 1))) * 4294967296.0))
|
|
}
|
|
|
|
public init() {}
|
|
|
|
public mutating func update(_ data: Data) {
|
|
totalLength += UInt64(data.count)
|
|
buffer.append(contentsOf: data)
|
|
var offset = 0
|
|
while buffer.count - offset >= 64 {
|
|
processBlock(Array(buffer[offset..<offset + 64]))
|
|
offset += 64
|
|
}
|
|
if offset > 0 {
|
|
buffer.removeFirst(offset)
|
|
}
|
|
}
|
|
|
|
private mutating func processBlock(_ block: [UInt8]) {
|
|
var m = [UInt32](repeating: 0, count: 16)
|
|
for i in 0..<16 {
|
|
m[i] = UInt32(block[i * 4])
|
|
| (UInt32(block[i * 4 + 1]) << 8)
|
|
| (UInt32(block[i * 4 + 2]) << 16)
|
|
| (UInt32(block[i * 4 + 3]) << 24)
|
|
}
|
|
var a = a0, b = b0, c = c0, d = d0
|
|
for i in 0..<64 {
|
|
var f: UInt32
|
|
var g: Int
|
|
switch i {
|
|
case 0..<16:
|
|
f = (b & c) | (~b & d)
|
|
g = i
|
|
case 16..<32:
|
|
f = (d & b) | (~d & c)
|
|
g = (5 * i + 1) % 16
|
|
case 32..<48:
|
|
f = b ^ c ^ d
|
|
g = (3 * i + 5) % 16
|
|
default:
|
|
f = c ^ (b | ~d)
|
|
g = (7 * i) % 16
|
|
}
|
|
f = f &+ a &+ Self.k[i] &+ m[g]
|
|
a = d
|
|
d = c
|
|
c = b
|
|
b = b &+ ((f << Self.s[i]) | (f >> (32 - Self.s[i])))
|
|
}
|
|
a0 = a0 &+ a
|
|
b0 = b0 &+ b
|
|
c0 = c0 &+ c
|
|
d0 = d0 &+ d
|
|
}
|
|
|
|
public mutating func finalize() -> Data {
|
|
let bitLength = totalLength * 8
|
|
// Padding: 0x80 then zeros to 56 mod 64, then length LE64.
|
|
var padding: [UInt8] = [0x80]
|
|
let remainder = (buffer.count + 1) % 64
|
|
let zeros = remainder <= 56 ? 56 - remainder : 120 - remainder
|
|
padding.append(contentsOf: [UInt8](repeating: 0, count: zeros))
|
|
var lenLE = bitLength.littleEndian
|
|
withUnsafeBytes(of: &lenLE) { padding.append(contentsOf: $0) }
|
|
|
|
buffer.append(contentsOf: padding)
|
|
var offset = 0
|
|
while buffer.count - offset >= 64 {
|
|
processBlock(Array(buffer[offset..<offset + 64]))
|
|
offset += 64
|
|
}
|
|
buffer.removeAll()
|
|
|
|
var out = Data()
|
|
for v in [a0, b0, c0, d0] {
|
|
var le = v.littleEndian
|
|
withUnsafeBytes(of: &le) { out.append(contentsOf: $0) }
|
|
}
|
|
return out
|
|
}
|
|
|
|
public static func hexString(_ data: Data) -> String {
|
|
var md5 = MD5()
|
|
md5.update(data)
|
|
return md5.finalize().map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
}
|