|
|
@@ -7,13 +7,13 @@
|
|
|
//
|
|
|
|
|
|
final class MD5: DigestType {
|
|
|
- let message: Array<UInt8>
|
|
|
-
|
|
|
- init (_ message: Array<UInt8>) {
|
|
|
- self.message = message
|
|
|
- }
|
|
|
static let blockSize:Int = 64
|
|
|
static let digestSize:Int = 16 // 128 / 8
|
|
|
+ fileprivate static let hashInitialValue:Array<UInt32> = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
|
|
|
+
|
|
|
+ fileprivate var accumulated = Array<UInt8>()
|
|
|
+ fileprivate var accumulatedLength: Int = 0
|
|
|
+ fileprivate var accumulatedHash:Array<UInt32> = MD5.hashInitialValue
|
|
|
|
|
|
/** specifies the per-round shift amounts */
|
|
|
private let s: Array<UInt32> = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
|
|
@@ -38,81 +38,105 @@ final class MD5: DigestType {
|
|
|
0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
|
|
|
0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,
|
|
|
0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391]
|
|
|
-
|
|
|
- private let h:Array<UInt32> = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
|
|
|
-
|
|
|
- func calculate() -> Array<UInt8> {
|
|
|
- var tmpMessage = bitPadding(to: self.message, blockSize: 64, allowance: 64 / 8)
|
|
|
- tmpMessage.reserveCapacity(tmpMessage.count + 4)
|
|
|
-
|
|
|
- // initialize hh with hash values
|
|
|
- var hh = h
|
|
|
-
|
|
|
- // Step 2. Append Length a 64-bit representation of lengthInBits
|
|
|
- let lengthInBits = (message.count * 8)
|
|
|
- let lengthBytes = lengthInBits.bytes(totalBytes: 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 = chunk.toUInt32Array()
|
|
|
- assert(M.count == 16, "Invalid array")
|
|
|
-
|
|
|
- // Initialize hash value for this chunk:
|
|
|
- var A:UInt32 = hh[0]
|
|
|
- var B:UInt32 = hh[1]
|
|
|
- var C:UInt32 = hh[2]
|
|
|
- var D:UInt32 = hh[3]
|
|
|
-
|
|
|
- var dTemp:UInt32 = 0
|
|
|
-
|
|
|
- // Main loop
|
|
|
- for j in 0..<k.count {
|
|
|
- var g = 0
|
|
|
- var F:UInt32 = 0
|
|
|
-
|
|
|
- switch (j) {
|
|
|
- case 0...15:
|
|
|
- F = (B & C) | ((~B) & D)
|
|
|
- g = j
|
|
|
- break
|
|
|
- case 16...31:
|
|
|
- F = (D & B) | (~D & C)
|
|
|
- g = (5 * j + 1) % 16
|
|
|
- break
|
|
|
- case 32...47:
|
|
|
- F = B ^ C ^ D
|
|
|
- g = (3 * j + 5) % 16
|
|
|
- break
|
|
|
- case 48...63:
|
|
|
- F = C ^ (B | (~D))
|
|
|
- g = (7 * j) % 16
|
|
|
- break
|
|
|
- default:
|
|
|
- break
|
|
|
- }
|
|
|
- dTemp = D
|
|
|
- D = C
|
|
|
- C = B
|
|
|
- B = B &+ rotateLeft(A &+ F &+ k[j] &+ M[g], by: s[j])
|
|
|
- A = dTemp
|
|
|
+
|
|
|
+ func calculate(for bytes: Array<UInt8>) -> Array<UInt8> {
|
|
|
+ do {
|
|
|
+ return try self.update(withBytes: bytes, isLast: true)
|
|
|
+ } catch {
|
|
|
+ fatalError()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // mutating currentHash in place is way faster than returning new result
|
|
|
+ fileprivate func process<C: Collection>(block chunk: C, currentHash: inout Array<UInt32>) where C.Iterator.Element == UInt8, C.Index == Int {
|
|
|
+
|
|
|
+ // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
|
|
|
+ var M = chunk.toUInt32Array()
|
|
|
+ assert(M.count == 16, "Invalid array")
|
|
|
+
|
|
|
+ // Initialize hash value for this chunk:
|
|
|
+ var A:UInt32 = currentHash[0]
|
|
|
+ var B:UInt32 = currentHash[1]
|
|
|
+ var C:UInt32 = currentHash[2]
|
|
|
+ var D:UInt32 = currentHash[3]
|
|
|
+
|
|
|
+ var dTemp:UInt32 = 0
|
|
|
+
|
|
|
+ // Main loop
|
|
|
+ for j in 0..<k.count {
|
|
|
+ var g = 0
|
|
|
+ var F:UInt32 = 0
|
|
|
+
|
|
|
+ switch (j) {
|
|
|
+ case 0...15:
|
|
|
+ F = (B & C) | ((~B) & D)
|
|
|
+ g = j
|
|
|
+ break
|
|
|
+ case 16...31:
|
|
|
+ F = (D & B) | (~D & C)
|
|
|
+ g = (5 * j + 1) % 16
|
|
|
+ break
|
|
|
+ case 32...47:
|
|
|
+ F = B ^ C ^ D
|
|
|
+ g = (3 * j + 5) % 16
|
|
|
+ break
|
|
|
+ case 48...63:
|
|
|
+ F = C ^ (B | (~D))
|
|
|
+ g = (7 * j) % 16
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ break
|
|
|
}
|
|
|
-
|
|
|
- hh[0] = hh[0] &+ A
|
|
|
- hh[1] = hh[1] &+ B
|
|
|
- hh[2] = hh[2] &+ C
|
|
|
- hh[3] = hh[3] &+ D
|
|
|
+ dTemp = D
|
|
|
+ D = C
|
|
|
+ C = B
|
|
|
+ B = B &+ rotateLeft(A &+ F &+ k[j] &+ M[g], by: s[j])
|
|
|
+ A = dTemp
|
|
|
}
|
|
|
|
|
|
+ currentHash[0] = currentHash[0] &+ A
|
|
|
+ currentHash[1] = currentHash[1] &+ B
|
|
|
+ currentHash[2] = currentHash[2] &+ C
|
|
|
+ currentHash[3] = currentHash[3] &+ D
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension MD5: Updatable {
|
|
|
+ func update<T: Sequence>(withBytes bytes: T, isLast: Bool = false) throws -> Array<UInt8> where T.Iterator.Element == UInt8 {
|
|
|
+ self.accumulated += bytes
|
|
|
+ self.accumulatedLength += Array(bytes).count //FIXME: oh no! no nononoonooono. I need that but it's bad for performance. Shouldn't need it ¯\_(ツ)_/¯
|
|
|
+
|
|
|
+ if isLast {
|
|
|
+ // Step 1. Append padding
|
|
|
+ self.accumulated = bitPadding(to: self.accumulated, blockSize: MD5.blockSize, allowance: 64 / 8)
|
|
|
+
|
|
|
+ // Step 2. Append Length a 64-bit representation of lengthInBits
|
|
|
+ let lengthInBits = self.accumulatedLength * 8
|
|
|
+ let lengthBytes = lengthInBits.bytes(totalBytes: 64 / 8) // A 64-bit representation of b
|
|
|
+ self.accumulated += lengthBytes.reversed()
|
|
|
+ }
|
|
|
+
|
|
|
+ for chunk in BytesSequence(chunkSize: MD5.blockSize, data: self.accumulated) {
|
|
|
+ if (isLast || self.accumulated.count >= MD5.blockSize) {
|
|
|
+ self.process(block: chunk, currentHash: &self.accumulatedHash)
|
|
|
+ self.accumulated.removeFirst(chunk.count)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // output current hash
|
|
|
var result = Array<UInt8>()
|
|
|
- result.reserveCapacity(hh.count / 4)
|
|
|
-
|
|
|
- hh.forEach {
|
|
|
- let itemLE = $0.littleEndian
|
|
|
- result += [UInt8(itemLE & 0xff), UInt8((itemLE >> 8) & 0xff), UInt8((itemLE >> 16) & 0xff), UInt8((itemLE >> 24) & 0xff)]
|
|
|
+ result.reserveCapacity(self.accumulatedHash.count / 4)
|
|
|
+
|
|
|
+ for hElement in self.accumulatedHash {
|
|
|
+ let hLE = hElement.littleEndian
|
|
|
+ result += [UInt8(hLE & 0xff), UInt8((hLE >> 8) & 0xff), UInt8((hLE >> 16) & 0xff), UInt8((hLE >> 24) & 0xff)]
|
|
|
}
|
|
|
+
|
|
|
+ // reset hash value for instance
|
|
|
+ if isLast {
|
|
|
+ self.accumulatedHash = MD5.hashInitialValue
|
|
|
+ }
|
|
|
+
|
|
|
return result
|
|
|
}
|
|
|
}
|