ソースを参照

general MD5 fixes, tests update

Marcin Krzyżanowski 11 年 前
コミット
85b0de0474

+ 4 - 0
CryptoSwift.xcodeproj/project.pbxproj

@@ -18,6 +18,7 @@
 		755FB1DA199E347D00475437 /* ExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755FB1D9199E347D00475437 /* ExtensionsTest.swift */; };
 		758F3F761992E57D0014BBDA /* Playground in Resources */ = {isa = PBXBuildFile; fileRef = 758F3F751992E57D0014BBDA /* Playground */; };
 		758F3F781992F6CE0014BBDA /* ByteExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758F3F771992F6CE0014BBDA /* ByteExtension.swift */; };
+		7599C9C6199EA28700A3988B /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7599C9C5199EA28700A3988B /* StringExtension.swift */; };
 		75B601EB197D6A6C0009B53D /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 754BE45519693E190098E6F3 /* CryptoSwift.framework */; };
 /* End PBXBuildFile section */
 
@@ -101,6 +102,7 @@
 		755FB1D9199E347D00475437 /* ExtensionsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionsTest.swift; sourceTree = "<group>"; };
 		758F3F751992E57D0014BBDA /* Playground */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Playground; path = CryptoSwift/Playground; sourceTree = "<group>"; };
 		758F3F771992F6CE0014BBDA /* ByteExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ByteExtension.swift; sourceTree = "<group>"; };
+		7599C9C5199EA28700A3988B /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -161,6 +163,7 @@
 				7547195019931802002FA5F1 /* IntExtension.swift */,
 				752DEF7619693EA000E17557 /* NSDataExtension.swift */,
 				754C8FEC19979F94005AD904 /* ArrayExtension.swift */,
+				7599C9C5199EA28700A3988B /* StringExtension.swift */,
 				754BE45819693E190098E6F3 /* Supporting Files */,
 			);
 			path = CryptoSwift;
@@ -309,6 +312,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				7552614E1993051E000D2B20 /* CryptoHash.swift in Sources */,
+				7599C9C6199EA28700A3988B /* StringExtension.swift in Sources */,
 				750A54601992D2680017DA75 /* MD5.swift in Sources */,
 				752DEF7719693EA000E17557 /* NSDataExtension.swift in Sources */,
 				754C8FED19979F94005AD904 /* ArrayExtension.swift in Sources */,

+ 89 - 23
CryptoSwift/IntExtension.swift

@@ -33,13 +33,38 @@ j &<<= 2        //shift left and assign
 
 import Foundation
 
+/** array of bytes, little-endian representation */
+private func bytesArray<T>(value:T, totalBytes:Int) -> [Byte] {
+    var bytes = [Byte](count: totalBytes, repeatedValue: 0)
+    var data = NSData(bytes: [value] as [T], length: totalBytes)
+    
+    // then convert back to bytes, byte by byte
+    for i in 0..<data.length {
+        data.getBytes(&bytes[totalBytes - 1 - i], range:NSRange(location:i, length:sizeof(Byte)))
+    }
+    
+    return bytes
+}
+
+extension UInt32 {
+    public func bytes(_ totalBytes: Int = sizeof(UInt32)) -> [Byte] {
+        return bytesArray(self, totalBytes)
+    }
+}
+
+extension UInt64 {
+    public func bytes(_ totalBytes: Int = sizeof(UInt64)) -> [Byte] {
+        return bytesArray(self, totalBytes)
+    }
+}
+
 extension Int {
     
     private init(bits: [Bool]) {
         var bitPattern:UInt = 0
         for (idx,b) in enumerate(bits) {
             if (b == true) {
-                let bit:UInt = UInt(1) << UInt(idx)
+                var bit:UInt = UInt(1) << UInt(idx)
                 bitPattern = bitPattern | bit
             }
         }
@@ -49,27 +74,16 @@ extension Int {
     
     /** Array of bytes with optional padding (little-endian) */
     public func bytes(_ totalBytes: Int = sizeof(Int)) -> [Byte] {
-        var bytes:[Byte] = [Byte](count: totalBytes, repeatedValue: 0)
-        
-        // first convert to data
-        let data = NSData(bytes: [self] as [Int], length: totalBytes)
-        
-        // then convert back to bytes, byte by byte
-        for i in 0..<Swift.min(sizeof(Int),totalBytes) {
-            var b:Byte = 0
-            data.getBytes(&b, range: NSRange(location: i,length: 1))
-            bytes[totalBytes - 1 - i] = b
-        }
-        return bytes
+        return bytesArray(self, totalBytes)
     }
 
     /** Int with array bytes (little-endian) */
     public static func withBytes(bytes: [Byte]) -> Int {
         var i:Int = 0
-        let totalBytes = Swift.min(bytes.count, sizeof(Int))
+        var totalBytes = Swift.min(bytes.count, sizeof(Int))
         
         // get slice of Int
-        let start = Swift.max(bytes.count - sizeof(Int),0)
+        var start = Swift.max(bytes.count - sizeof(Int),0)
         var intarr = Array<Byte>(bytes[start..<(start + totalBytes)])
         
         // extend to Int size if necessary
@@ -77,25 +91,63 @@ extension Int {
             intarr.insert(0 as Byte, atIndex: 0)
         }
         
-        let data = NSData(bytes: intarr, length: intarr.count)
+        var data = NSData(bytes: intarr, length: intarr.count)
         data.getBytes(&i, length: sizeof(Int));
         return i.byteSwapped
     }
 }
 
+/** Bit operations */
+extension UInt32 {
+    
+    /** Shift bits to the left. All bits are shifted (including sign bit) */
+    private mutating func shiftLeft(count: UInt32) -> UInt32 {
+        var bitsCount = UInt32(sizeof(UInt32) * 8)
+        var shiftCount = Swift.min(count, bitsCount - 1)
+        var shiftedValue:UInt32 = 0;
+        
+        for bitIdx in 0..<bitsCount {
+            // if bit is set then copy to result and shift left 1
+            var bit = 1 << bitIdx
+            if ((self & bit) == bit) {
+                shiftedValue = shiftedValue | (bit << shiftCount)
+            }
+        }
+        self = shiftedValue
+        return self
+    }
+    
+    /** Shift bits to the right. All bits are shifted (including sign bit) */
+    private mutating func shiftRight(count: UInt32) -> UInt32 {
+        var bitsCount = UInt32(sizeof(UInt32) * 8)
+        var maxBitsForValue = UInt32(floor(log2(Double(self)) + 1))
+        var shiftCount = Swift.min(count, maxBitsForValue)
+        var shiftedValue:UInt32 = 0;
+        
+        for bitIdx in 0..<bitsCount {
+            // if bit is set then copy to result and shift left 1
+            var bit = 1 << bitIdx
+            if ((self & bit) == bit) {
+                shiftedValue = shiftedValue | (bit >> shiftCount)
+            }
+        }
+        self = shiftedValue
+        return self
+    }
+}
 
 /** Bit operations */
 extension Int {
     
     /** Shift bits to the left. All bits are shifted (including sign bit) */
     private mutating func shiftLeft(count: Int) -> Int {
-        let bitsCount = sizeof(Int) * 8
-        let shiftCount = Swift.min(count, bitsCount - 1)
+        var bitsCount = sizeof(Int) * 8
+        var shiftCount = Swift.min(count, bitsCount - 1)
         var shiftedValue:Int = 0;
         
         for bitIdx in 0..<bitsCount {
             // if bit is set then copy to result and shift left 1
-            let bit = 1 << bitIdx
+            var bit = 1 << bitIdx
             if ((self & bit) == bit) {
                 shiftedValue = shiftedValue | (bit << shiftCount)
             }
@@ -106,14 +158,14 @@ extension Int {
     
     /** Shift bits to the right. All bits are shifted (including sign bit) */
     private mutating func shiftRight(count: Int) -> Int {
-        let bitsCount = sizeof(Int) * 8
-        let maxBitsForValue = Int(floor(log2(Double(self)) + 1))
-        let shiftCount = Swift.min(count, maxBitsForValue)
+        var bitsCount = sizeof(Int) * 8
+        var maxBitsForValue = Int(floor(log2(Double(self)) + 1))
+        var shiftCount = Swift.min(count, maxBitsForValue)
         var shiftedValue:Int = 0;
         
         for bitIdx in 0..<bitsCount {
             // if bit is set then copy to result and shift left 1
-            let bit = 1 << bitIdx
+            var bit = 1 << bitIdx
             if ((self & bit) == bit) {
                 shiftedValue = shiftedValue | (bit >> shiftCount)
             }
@@ -148,6 +200,13 @@ func &<< (lhs: Int, rhs: Int) -> Int {
     return l
 }
 
+/** shift left with bits truncation */
+func &<< (lhs: UInt32, rhs: UInt32) -> UInt32 {
+    var l = lhs;
+    l.shiftLeft(rhs)
+    return l
+}
+
 // Right operator
 
 infix operator &>>= {
@@ -170,4 +229,11 @@ func &>> (lhs: Int, rhs: Int) -> Int {
     var l = lhs;
     l.shiftRight(rhs)
     return l
+}
+
+/** shift right and assign with bits truncation */
+func &>> (lhs: UInt32, rhs: UInt32) -> UInt32 {
+    var l = lhs;
+    l.shiftRight(rhs)
+    return l
 }

+ 52 - 54
CryptoSwift/MD5.swift

@@ -8,16 +8,16 @@
 
 import Foundation
 
-class MD5 {
+public class MD5 {
 
     /** specifies the per-round shift amounts */
-    let s: [UInt32] = [7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
+    private 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]
     
     /** binary integer part of the sines of integers (Radians) */
-    let K: [UInt32] = [0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
+    private let K: [UInt32] = [0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
                        0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
                        0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,
                        0x6b901122,0xfd987193,0xa679438e,0x49b40821,
@@ -34,20 +34,27 @@ class MD5 {
                        0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,
                        0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391]
     
-    var a0: UInt32 = 0x67452301
-    var b0: UInt32 = 0xefcdab89
-    var c0: UInt32 = 0x98badcfe
-    var d0: UInt32 = 0x10325476
+    let a0: UInt32 = 0x67452301
+    let b0: UInt32 = 0xefcdab89
+    let c0: UInt32 = 0x98badcfe
+    let d0: UInt32 = 0x10325476
     
-    var message: NSData
+    private var message: NSData
     
-    init(_ message: NSData) {
-        self.message = message;
+    //MARK: Public
+    
+    public init(_ message: NSData) {
+        self.message = message
     }
     
-    func calculate() -> NSData? {
+    public func calculate() -> NSData? {
         var tmpMessage: NSMutableData = NSMutableData(data: message)
         
+        var aa = a0
+        var bb = b0
+        var cc = c0
+        var dd = d0
+        
         // Step 1. Append Padding Bits
         tmpMessage.appendBytes([0x80]) // append one bit (Byte with one bit) to message
         
@@ -57,22 +64,23 @@ class MD5 {
         }
         
         // Step 2. Append Length a 64-bit representation of lengthInBits
-        let lengthInBits:Int = (message.length * 8).bigEndian
-        tmpMessage.appendBytes(lengthInBits.bytes(64 / 8));
+        var lengthInBits = (message.length * 8)
+        var lengthBytes = lengthInBits.bytes(64 / 8)
+        tmpMessage.appendBytes(reverse(lengthBytes));
         
         // Process the message in successive 512-bit chunks:
         let chunkSizeBytes = 512 / 8
         var leftMessageBytes = tmpMessage.length
         for var i = 0; i < tmpMessage.length; i = i + chunkSizeBytes, leftMessageBytes -= chunkSizeBytes {
-            var chunk = tmpMessage.subdataWithRange(NSRange(location: i, length: min(chunkSizeBytes,leftMessageBytes)))
+            let chunk = tmpMessage.subdataWithRange(NSRange(location: i, length: min(chunkSizeBytes,leftMessageBytes)))
             
             // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
-            var M:[UInt32] = [UInt32]()
-            for x in 0...15 {
-                var word:UInt32 = 0;
-                var range:NSRange = NSRange(location:x * sizeof(UInt32), length: sizeof(UInt32));
-                chunk.getBytes(&word, range: range);
-                M.append(word)
+            let wordSize = sizeof(UInt32)
+            // println("wordSize \(wordSize)");
+            var M:[UInt32] = [UInt32](count: 16, repeatedValue: 0)
+            for x in 0..<M.count {
+                var range = NSRange(location:x * wordSize, length: wordSize)
+                chunk.getBytes(&M[x], range:range);
             }
             
             // Initialize hash value for this chunk:
@@ -81,30 +89,29 @@ class MD5 {
             var C:UInt32 = c0
             var D:UInt32 = d0
             
-            var dTemp:UInt32
+            var dTemp:UInt32 = 0
             
             // Main loop
-            for i in 0...63 {
-                var g:Int = 0
+            for j in 0...63 {
+                var g = 0
                 var F:UInt32 = 0
                 
-                switch (i) {
+                switch (j) {
                 case 0...15:
                     F = (B & C) | ((~B) & D)
-                    //F = D ^ (B & (C ^ D))
-                    g = i
+                    g = j
                     break
                 case 16...31:
                     F = (D & B) | (~D & C)
-                    g = (5 * i + 1) % 16
+                    g = (5 * j + 1) % 16
                     break
                 case 32...47:
                     F = B ^ C ^ D
-                    g = (3 * i + 5) % 16
+                    g = (3 * j + 5) % 16
                     break
                 case 48...63:
                     F = C ^ (B | (~D))
-                    g = (7 * i) % 16
+                    g = (7 * j) % 16
                     break
                 default:
                     break
@@ -112,45 +119,36 @@ class MD5 {
                 dTemp = D
                 D = C
                 C = B
-                B = B &+ rotateLeft((A &+ F &+ K[i] &+ M[g]), s[i])
-                A = dTemp
+                B = B &+ rotateLeft((A &+ F &+ K[j] &+ M[g]), s[j])
+                A = dTemp    
             }
-            a0 = a0 &+ A
-            b0 = b0 &+ B
-            c0 = c0 &+ C
-            d0 = d0 &+ D
+            
+            aa = aa &+ A
+            bb = bb &+ B
+            cc = cc &+ C
+            dd = dd &+ D
         }
 
         var buf: NSMutableData = NSMutableData();
-        buf.appendBytes(&a0, length: sizeof(UInt32))
-        buf.appendBytes(&b0, length: sizeof(UInt32))
-        buf.appendBytes(&c0, length: sizeof(UInt32))
-        buf.appendBytes(&d0, length: sizeof(UInt32))
+        buf.appendBytes(&aa, length: sizeof(UInt32))
+        buf.appendBytes(&bb, length: sizeof(UInt32))
+        buf.appendBytes(&cc, length: sizeof(UInt32))
+        buf.appendBytes(&dd, length: sizeof(UInt32))
         
         return buf.copy() as? NSData;
     }
+
+    //MARK: Class
     
     class func calculate(message: NSData) -> NSData?
     {
         return MD5(message).calculate();
     }
     
-    private func rotateLeft(x:UInt32, _ n:UInt32) -> UInt32 {
-        return (x << n) | (x >> (32 - n))
-    }
-}
-
-/** String extension */
-extension String {
+    //MARK: Private
     
-    /** Calculate MD5 hash */
-    public func md5() -> String? {
-        var stringData:NSData? = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
-        if let data = stringData {
-            let md5Data = MD5.calculate(data)
-            return md5Data?.toHexString()
-        }
-        return nil
+    private func rotateLeft(x:UInt32, _ n:UInt32) -> UInt32 {
+        return (x &<< n) | (x &>> (32 - n))
     }
 }
 

+ 2 - 2
CryptoSwift/NSDataExtension.swift

@@ -11,7 +11,7 @@ import Foundation
 extension NSMutableData {
     
     /** Convenient way to append bytes */
-    internal func appendBytes(arrayOfBytes: Array<Byte>) {
+    internal func appendBytes(arrayOfBytes: [Byte]) {
         self.appendBytes(arrayOfBytes, length: arrayOfBytes.count)
     }
     
@@ -37,7 +37,7 @@ extension NSData {
     }
     
     public func md5() -> NSData? {
-        return MD5.calculate(self);
+        return MD5(self).calculate()
     }
     
     internal func toHexString() -> String {

+ 4 - 0
CryptoSwift/Playground/MyPlayground.playground/section-1.swift

@@ -2,3 +2,7 @@
 
 import UIKit
 
+var i:Int = 24
+i.bigEndian
+i.littleEndian
+

+ 22 - 0
CryptoSwift/StringExtension.swift

@@ -0,0 +1,22 @@
+//
+//  StringExtension.swift
+//  CryptoSwift
+//
+//  Created by Marcin Krzyzanowski on 15/08/14.
+//  Copyright (c) 2014 Marcin Krzyzanowski. All rights reserved.
+//
+
+import Foundation
+
+/** String extension */
+extension String {
+    
+    /** Calculate MD5 hash */
+    public func md5() -> String? {
+        var stringData = self.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
+        if var data = stringData!.md5() {
+            return data.hexString
+        }
+        return nil
+    }
+}

+ 2 - 2
CryptoSwiftTests/ExtensionsTest.swift

@@ -34,9 +34,9 @@ class ExtensionsTest: XCTestCase {
     }
     
     func testBytes() {
-        let size = sizeof(Int) // 32 or 64  bit
+        let size = sizeof(UInt32) // 32 or 64  bit
         
-        let i:Int = 1024
+        let i:UInt32 = 1024
         var bytes = i.bytes()
         XCTAssertTrue(bytes.count == size, "Invalid bytes length =  \(bytes.count)")
         

+ 19 - 6
CryptoSwiftTests/HashTests.swift

@@ -20,16 +20,29 @@ class CryptoSwiftTests: XCTestCase {
     }
     
     func testMD5() {
-        var data:NSData = NSData(bytes: [49, 50, 51] as [Byte], length: 3) // "1", "2", "3"
-        var md5data = data.md5()
-        XCTAssertNotNil(md5data, "MD5 calculation failed")
+        let data1:NSData = NSData(bytes: [0x31, 0x32, 0x33] as [Byte], length: 3) // "1", "2", "3"
+        let data2:NSData = NSData(bytes: [0x31, 0x32, 0x33] as [Byte], length: 3) // "1", "2", "3"
         
-        if let data = md5data {
-            XCTAssertEqual(data.hexString, "202CB962AC59075B964B07152D234B70", "MD5 calculation failed");
+        if let d = MD5(data1).calculate() {
+            XCTAssertEqual(d.hexString, "202CB962AC59075B964B07152D234B70", "MD5 calculation failed");
+        } else {
+            XCTFail("MD5 fail")
         }
         
-        if let hash = "123".md5() {
+        //FIXME: fail on 32-bit simulator for some unknown reason.
+        //       because it's working fine for the first time, and break for the second
+        //       time with the very same input data
+        if let d = MD5(data2).calculate() {
+            XCTAssertEqual(d.hexString, "202CB962AC59075B964B07152D234B70", "MD5 calculation failed");
+        } else {
+            XCTFail("MD5 fail")
+        }
+
+        let string = "123"
+        if let hash = string.md5() {
             XCTAssertEqual(hash, "202CB962AC59075B964B07152D234B70", "MD5 calculation failed");
+        } else {
+            XCTFail("MD5 fail")
         }
 
     }

+ 11 - 0
README.md

@@ -14,10 +14,21 @@ Good mood
         println(data.hexString)
     }
     
+ direct or with helpers
+	
+	let hash = MD5(data).calculate()
+	let hash = data.md5()
+	
+srtaight from String
+
     /* Calculate hash for string with convenience extension */
     var string:String = "123"
     if let hash = string.md5() {
         println(string.md5())
     }
     
+##Contact
+Marcin Krzyżanowski [@krzyzanowskim](http://twitter.com/krzyzanowskim)
 
+##Licence
+see LICENSE file