Просмотр исходного кода

Add CMAC message authenticator (#583)

* Add CMAC message authenticator

* Add CMACTests to LinuxMain.swift

* Using Utils.swift xor method where possible

* Add comment and rename byte array left shift helper method

* Use Utils.swift bitPadding method eliminating need for getPaddedBlock

* Use collection.batched() instead of getBlock()

* Add returns comment to leftShiftOneBit

* Updated changelog

* Added test vectors origin

* Update README.md
TictoDev 7 лет назад
Родитель
Сommit
c1daf29bf7

+ 3 - 0
CHANGELOG

@@ -1,3 +1,6 @@
+0.8.4
+- Added CMAC message authenticator https://tools.ietf.org/html/rfc4493
+
 0.8.3
 - Fix SHA3 padding.
 - Fix Carthage builds.

+ 8 - 0
CryptoSwift.xcodeproj/project.pbxproj

@@ -7,6 +7,8 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		0EE73E71204D598100110E11 /* CMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE73E70204D598100110E11 /* CMAC.swift */; };
+		0EE73E74204D59C200110E11 /* CMACTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE73E72204D599C00110E11 /* CMACTests.swift */; };
 		674A736F1BF5D85B00866C5B /* RabbitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674A736E1BF5D85B00866C5B /* RabbitTests.swift */; };
 		750509991F6BEF2A00394A1B /* PKCS7.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750509981F6BEF2A00394A1B /* PKCS7.swift */; };
 		750CC3EB1DC0CACE0096BE6E /* BlowfishTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */; };
@@ -155,6 +157,8 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		0EE73E70204D598100110E11 /* CMAC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CMAC.swift; sourceTree = "<group>"; };
+		0EE73E72204D599C00110E11 /* CMACTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CMACTests.swift; sourceTree = "<group>"; };
 		674A736E1BF5D85B00866C5B /* RabbitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RabbitTests.swift; sourceTree = "<group>"; };
 		750509981F6BEF2A00394A1B /* PKCS7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKCS7.swift; sourceTree = "<group>"; };
 		750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlowfishTests.swift; sourceTree = "<group>"; };
@@ -295,6 +299,7 @@
 				754BE46719693E190098E6F3 /* DigestTests.swift */,
 				75100F8E19B0BC890005C5F5 /* Poly1305Tests.swift */,
 				758A94271A65C59200E46135 /* HMACTests.swift */,
+				0EE73E72204D599C00110E11 /* CMACTests.swift */,
 				E6200E151FB9B67C00258382 /* HKDFTests.swift */,
 				757DA2541A4ED408002BA3EF /* AESTests.swift */,
 				750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */,
@@ -345,6 +350,7 @@
 				75EC524A1EE8B6CA0048EB3B /* ChaCha20.swift */,
 				75EC524B1EE8B6CA0048EB3B /* Checksum.swift */,
 				75EC524C1EE8B6CA0048EB3B /* Cipher.swift */,
+				0EE73E70204D598100110E11 /* CMAC.swift */,
 				75EC524D1EE8B6CA0048EB3B /* Collection+Extension.swift */,
 				75EC524E1EE8B6CA0048EB3B /* Cryptors.swift */,
 				75EC52501EE8B6CA0048EB3B /* Digest.swift */,
@@ -546,6 +552,7 @@
 			files = (
 				75EC52861EE8B8170048EB3B /* CFB.swift in Sources */,
 				75EC52901EE8B81A0048EB3B /* Collection+Extension.swift in Sources */,
+				0EE73E71204D598100110E11 /* CMAC.swift in Sources */,
 				E6200E141FB9A7AE00258382 /* HKDF.swift in Sources */,
 				75EC529F1EE8B8230048EB3B /* HMAC.swift in Sources */,
 				75EC52B91EE8B83D0048EB3B /* ZeroPadding.swift in Sources */,
@@ -626,6 +633,7 @@
 				757DA2591A4ED4D7002BA3EF /* ChaCha20Tests.swift in Sources */,
 				755FB1DA199E347D00475437 /* ExtensionsTest.swift in Sources */,
 				674A736F1BF5D85B00866C5B /* RabbitTests.swift in Sources */,
+				0EE73E74204D59C200110E11 /* CMACTests.swift in Sources */,
 				750CC3EB1DC0CACE0096BE6E /* BlowfishTests.swift in Sources */,
 				757DA2531A4ED0A4002BA3EF /* PaddingTests.swift in Sources */,
 				757DA2551A4ED408002BA3EF /* AESTests.swift in Sources */,

+ 3 - 1
README.md

@@ -55,6 +55,7 @@ Good mood
 #### Message authenticators
 - [Poly1305](http://cr.yp.to/mac/poly1305-20050329.pdf)
 - [HMAC](https://www.ietf.org/rfc/rfc2104.txt) MD5, SHA1, SHA256
+- [CMAC](https://tools.ietf.org/html/rfc4493)
 
 #### Cipher block mode
 - Electronic codebook ([ECB](http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29))
@@ -173,7 +174,7 @@ See: [Package.swift - manual](http://blog.krzyzanowskim.com/2016/08/09/package-s
 
 * [Basics (data types, conversion, ...)](#basics)
 * [Digest (MD5, SHA...)](#calculate-digest)
-* [Message authenticators (HMAC...)](#message-authenticators-1)
+* [Message authenticators (HMAC, CMAC...)](#message-authenticators-1)
 * [Password-Based Key Derivation Function (PBKDF2, ...)](#password-based-key-derivation-functions)
 * [HMAC-based Key Derivation Function (HKDF)](#hmac-based-key-derivation-function)
 * [Data Padding](#data-padding)
@@ -281,6 +282,7 @@ let key:Array<UInt8> = [1,2,3,4,5,6,7,8,9,10,...]
 
 try Poly1305(key: key).authenticate(bytes)
 try HMAC(key: key, variant: .sha256).authenticate(bytes)
+try CMAC(key: key).authenticate(bytes)
 ```
 
 ##### Password-Based Key Derivation Functions

+ 100 - 0
Sources/CryptoSwift/CMAC.swift

@@ -0,0 +1,100 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+public final class CMAC: Authenticator {
+    
+    public enum Error: Swift.Error {
+        case wrongKeyLength
+    }
+
+    private let key: Array<UInt8>
+
+    private static let BlockSize: Int = 16
+    private static let Zero: Array<UInt8> = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    private static let Rb: Array<UInt8> = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87]
+
+    public init(key: Array<UInt8>) throws {
+        self.key = key
+        if key.count != 16 {
+            throw Error.wrongKeyLength
+        }
+    }
+
+    // MARK: Authenticator
+
+    public func authenticate(_ bytes: Array<UInt8>) throws -> Array<UInt8> {
+        let aes = try AES(key: key, blockMode: .CBC(iv: CMAC.Zero), padding: .noPadding)
+        
+        let l = try aes.encrypt(CMAC.Zero)
+        var subKey1 = leftShiftOneBit(l)
+        if (l[0] & 0x80) != 0 {
+            subKey1 = xor(CMAC.Rb, subKey1)
+        }
+        var subKey2 = leftShiftOneBit(subKey1)
+        if (subKey1[0] & 0x80) != 0 {
+            subKey2 = xor(CMAC.Rb, subKey2)
+        }
+
+        let lastBlockComplete: Bool
+        let blockCount = (bytes.count + CMAC.BlockSize - 1) / CMAC.BlockSize
+        if blockCount == 0 {
+            lastBlockComplete = false
+        } else {
+            lastBlockComplete = bytes.count % CMAC.BlockSize == 0
+        }
+        var paddedBytes = bytes
+        if !lastBlockComplete {
+            bitPadding(to: &paddedBytes, blockSize: CMAC.BlockSize)
+        }
+        
+        var blocks = Array(paddedBytes.batched(by: CMAC.BlockSize))
+        var lastBlock = blocks.popLast()!
+        if lastBlockComplete {
+            lastBlock = xor(lastBlock, subKey1)
+        } else {
+            lastBlock = xor(lastBlock, subKey2)
+        }
+        
+        var x = Array<UInt8>(repeating: 0x00, count: CMAC.BlockSize)
+        var y = Array<UInt8>(repeating: 0x00, count: CMAC.BlockSize)
+        for block in blocks {
+            y = xor(block, x)
+            x = try aes.encrypt(y)
+        }
+        y = xor(lastBlock, x)
+        return try aes.encrypt(y)
+    }
+
+    // MARK: Helper methods
+    
+    /**
+     Performs left shift by one bit to the bit string aquired after concatenating al bytes in the byte array
+     - parameters:
+       - bytes: byte array
+     - returns: bit shifted bit string split again in array of bytes
+     */
+    private func leftShiftOneBit(_ bytes: Array<UInt8>) -> Array<UInt8> {
+        var shifted = Array<UInt8>(repeating: 0x00, count: bytes.count)
+        let last = bytes.count - 1
+        for index in 0..<last {
+            shifted[index] = bytes[index] << 1
+            if (bytes[index + 1] & 0x80) != 0 {
+                shifted[index] += 0x01
+            }
+        }
+        shifted[last] = bytes[last] << 1
+        return shifted
+    }
+}

+ 65 - 0
Tests/CryptoSwiftTests/CMACTests.swift

@@ -0,0 +1,65 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+// Test vectors from https://tools.ietf.org/html/rfc4493
+
+import XCTest
+@testable import CryptoSwift
+
+final class CMACTests: XCTestCase {
+    
+    func testMessageLength0() {
+            let key: Array<UInt8> = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]
+            let msg: Array<UInt8> = []
+            let expectedMac: Array<UInt8> = [0xbb, 0x1d, 0x69, 0x29, 0xe9, 0x59, 0x37, 0x28, 0x7f, 0xa3, 0x7d, 0x12, 0x9b, 0x75, 0x67, 0x46]
+        
+            let cmac = try! CMAC(key: key).authenticate(msg)
+            XCTAssertEqual(cmac, expectedMac, "Invalid authentication result")
+    }
+    
+    func testMessageLength16() {
+            let key: Array<UInt8> = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]
+            let msg: Array<UInt8> = [0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a]
+            let expectedMac: Array<UInt8> = [0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c]
+        
+            let cmac = try! CMAC(key: key).authenticate(msg)
+            XCTAssertEqual(cmac, expectedMac, "Invalid authentication result")
+    }
+    
+    func testMessageLength40() {
+            let key: Array<UInt8> = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]
+            let msg: Array<UInt8> = [0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11]
+            let expectedMac: Array<UInt8> = [0xdf, 0xa6, 0x67, 0x47, 0xde, 0x9a, 0xe6, 0x30, 0x30, 0xca, 0x32, 0x61, 0x14, 0x97, 0xc8, 0x27]
+        
+            let cmac = try! CMAC(key: key).authenticate(msg)
+            XCTAssertEqual(cmac, expectedMac, "Invalid authentication result")
+    }
+    
+    func testMessageLength64() {
+            let key: Array<UInt8> = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]
+            let msg: Array<UInt8> = [0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10]
+            let expectedMac: Array<UInt8> = [0x51, 0xf0, 0xbe, 0xbf, 0x7e, 0x3b, 0x9d, 0x92, 0xfc, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3c, 0xfe]
+        
+            let cmac = try! CMAC(key: key).authenticate(msg)
+            XCTAssertEqual(cmac, expectedMac, "Invalid authentication result")
+    }
+    
+    static let allTests = [
+        ("testMessageLength0", testMessageLength0),
+        ("testMessageLength16", testMessageLength16),
+        ("testMessageLength40", testMessageLength40),
+        ("testMessageLength64", testMessageLength64),
+    ]
+}

+ 1 - 0
Tests/LinuxMain.swift

@@ -5,6 +5,7 @@ XCTMain([
     testCase(DigestTests.allTests()),
     testCase(Poly1305Tests.allTests),
     testCase(HMACTests.allTests),
+    testCase(CMACTests.allTests),
     testCase(AESTests.allTests()),
     testCase(BlowfishTests.allTests()),
     testCase(ChaCha20Tests.allTests()),