Bladeren bron

AEAD using ChaCha20-Poly1305 (#567)

* add ChaCha20Poly1305 combined AEAD implementation

* add tests for AEAD ChaCha20-Poly1305

* add copyright as per contribution instructions

* add header and link to ietf

* code cleanup

* add error description to tests if failure

* update README with aead usage

* fix bullet

* Add AEAD for potential future use with other AEAD implementations
spatno 7 jaren geleden
bovenliggende
commit
4c35a1db43

+ 12 - 0
CryptoSwift.xcodeproj/project.pbxproj

@@ -7,6 +7,9 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		14156CE320111B5C00DDCFBC /* ChaCha20Poly1305.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14156CE220111B5C00DDCFBC /* ChaCha20Poly1305.swift */; };
+		14156CE52011422400DDCFBC /* ChaCha20Poly1305Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14156CE42011422400DDCFBC /* ChaCha20Poly1305Tests.swift */; };
+		1467460F2017BB3600DF04ED /* AEAD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1467460E2017BB3600DF04ED /* AEAD.swift */; };
 		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 */; };
@@ -157,6 +160,9 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		14156CE220111B5C00DDCFBC /* ChaCha20Poly1305.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaCha20Poly1305.swift; sourceTree = "<group>"; };
+		14156CE42011422400DDCFBC /* ChaCha20Poly1305Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaCha20Poly1305Tests.swift; sourceTree = "<group>"; };
+		1467460E2017BB3600DF04ED /* AEAD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AEAD.swift; sourceTree = "<group>"; };
 		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>"; };
@@ -295,6 +301,7 @@
 		754BE46419693E190098E6F3 /* Tests */ = {
 			isa = PBXGroup;
 			children = (
+				14156CE42011422400DDCFBC /* ChaCha20Poly1305Tests.swift */,
 				E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */,
 				754BE46719693E190098E6F3 /* DigestTests.swift */,
 				75100F8E19B0BC890005C5F5 /* Poly1305Tests.swift */,
@@ -381,6 +388,8 @@
 				75EC52771EE8B6CA0048EB3B /* Updatable.swift */,
 				75EC52781EE8B6CA0048EB3B /* Utils.swift */,
 				75EC52791EE8B6CA0048EB3B /* ZeroPadding.swift */,
+				14156CE220111B5C00DDCFBC /* ChaCha20Poly1305.swift */,
+				1467460E2017BB3600DF04ED /* AEAD.swift */,
 				754310432050111A003FB1DF /* CompactMap.swift */,
 			);
 			path = CryptoSwift;
@@ -582,6 +591,7 @@
 				750509991F6BEF2A00394A1B /* PKCS7.swift in Sources */,
 				75EC52B51EE8B83D0048EB3B /* UInt64+Extension.swift in Sources */,
 				75EC52AF1EE8B83D0048EB3B /* SHA1.swift in Sources */,
+				14156CE320111B5C00DDCFBC /* ChaCha20Poly1305.swift in Sources */,
 				75EC52801EE8B8130048EB3B /* Bit.swift in Sources */,
 				75EC52971EE8B8200048EB3B /* ChaCha20+Foundation.swift in Sources */,
 				75EC52871EE8B8170048EB3B /* CTR.swift in Sources */,
@@ -600,6 +610,7 @@
 				75EC527D1EE8B8130048EB3B /* Array+Extension.swift in Sources */,
 				75EC52B31EE8B83D0048EB3B /* UInt16+Extension.swift in Sources */,
 				75EC52A81EE8B8390048EB3B /* PKCS5.swift in Sources */,
+				1467460F2017BB3600DF04ED /* AEAD.swift in Sources */,
 				75EC528A1EE8B8170048EB3B /* PCBC.swift in Sources */,
 				75EC528D1EE8B81A0048EB3B /* ChaCha20.swift in Sources */,
 				75EC52851EE8B8170048EB3B /* CBC.swift in Sources */,
@@ -636,6 +647,7 @@
 				0EE73E74204D59C200110E11 /* CMACTests.swift in Sources */,
 				750CC3EB1DC0CACE0096BE6E /* BlowfishTests.swift in Sources */,
 				757DA2531A4ED0A4002BA3EF /* PaddingTests.swift in Sources */,
+				14156CE52011422400DDCFBC /* ChaCha20Poly1305Tests.swift in Sources */,
 				757DA2551A4ED408002BA3EF /* AESTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 12 - 2
README.md

@@ -76,6 +76,9 @@ Good mood
 - [Zero padding](https://en.wikipedia.org/wiki/Padding_(cryptography)#Zero_padding)
 - No padding
 
+#### Authenticated Encryption with Associated Data (AEAD)
+- [ChaCha20/Poly1305](https://tools.ietf.org/html/rfc7539)
+
 ## Why
 [Why?](https://github.com/krzyzanowskim/CryptoSwift/issues/5) [Because I can](https://github.com/krzyzanowskim/CryptoSwift/issues/5#issuecomment-53379391).
 
@@ -182,7 +185,7 @@ See: [Package.swift - manual](http://blog.krzyzanowskim.com/2016/08/09/package-s
 * [Rabbit](#rabbit)
 * [Blowfish](#blowfish)
 * [Advanced Encryption Standard (AES)](#aes)
-
+* [Authenticated Encryption with Associated Data (AEAD)](#aead)
 
 also check [Playground](/CryptoSwift.playground/Contents.swift)
 
@@ -346,7 +349,7 @@ AES-256 example
 ```swift
 try AES(key: [1,2,3,...,32], blockMode: .CBC(iv: [1,2,3,...,16]), padding: .pkcs7)
 ```
- 
+
 ###### All at once
 ```swift
 do {
@@ -409,6 +412,13 @@ let encrypted = try! plain.encrypt(ChaCha20(key: key, iv: iv))
 let decrypted = try! encrypted.decrypt(ChaCha20(key: key, iv: iv))
 ```
 
+##### AEAD
+
+```swift
+let encrypt = try ChaCha20Poly1305.encrypt(message: message, header: header, key: key, nonce: nonce)
+let decrypt = try ChaCha20Poly1305.decrypt(cipher: cipher, header: header, key: key, nonce: nonce, tag: tag)
+```
+
 ## Author
 
 CryptoSwift is owned and maintained by [Marcin Krzyżanowski](http://www.krzyzanowskim.com)

+ 22 - 0
Sources/CryptoSwift/AEAD.swift

@@ -0,0 +1,22 @@
+//
+//  AEAD.swift
+//  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 protocol AEAD {
+    static func encrypt( message:Array<UInt8>, header:Array<UInt8>, key:Array<UInt8>, nonce:Array<UInt8> ) throws -> (cipher:Array<UInt8>, tag:Array<UInt8>)
+    
+    static func decrypt( cipher:Array<UInt8>, header:Array<UInt8>, key:Array<UInt8>, nonce:Array<UInt8>, tag:Array<UInt8> ) throws -> (message:Array<UInt8>, success:Bool)
+}

+ 77 - 0
Sources/CryptoSwift/ChaCha20Poly1305.swift

@@ -0,0 +1,77 @@
+//
+//  ChaCha20Poly1305.swift
+//  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.
+//
+//
+//  https://tools.ietf.org/html/rfc7539#section-2.8.1
+
+public final class ChaCha20Poly1305: AEAD {
+
+    public static func encrypt( message:Array<UInt8>, header:Array<UInt8>, key:Array<UInt8>, nonce:Array<UInt8> ) throws -> (cipher:Array<UInt8>, tag:Array<UInt8>) {
+        
+        let chacha = try ChaCha20(key: key, iv: nonce)
+        
+        var polykey = Array<UInt8>(repeating: 0, count: 32)
+        var toEncrypt = Array<UInt8>(reserveCapacity: message.count + 64)
+        
+        toEncrypt += polykey
+        polykey = try chacha.encrypt(polykey)
+        toEncrypt += polykey
+        toEncrypt += message
+        
+        let fullCipher = try chacha.encrypt(toEncrypt)
+        let cipher = Array(fullCipher.dropFirst(64))
+        
+        let tag = try ChaCha20Poly1305.calculateTag(cipher: cipher, header: header, encodedKey: polykey)
+        
+        return (cipher, tag)
+    }
+    
+    public static func decrypt( cipher:Array<UInt8>, header:Array<UInt8>, key:Array<UInt8>, nonce:Array<UInt8>, tag:Array<UInt8> ) throws -> (message:Array<UInt8>, success:Bool) {
+        
+        let chacha = try ChaCha20(key: key, iv: nonce)
+        
+        var polykey = Array<UInt8>(repeating: 0, count: 32)
+        polykey = try chacha.encrypt(polykey)
+        
+        let mac = try ChaCha20Poly1305.calculateTag(cipher: cipher, header: header, encodedKey: polykey)
+        
+        guard mac == tag else { return (cipher, false) }
+        
+        var toDecrypt = Array<UInt8>(reserveCapacity: cipher.count + 64)
+        toDecrypt += polykey
+        toDecrypt += polykey
+        toDecrypt += cipher
+        let decrypted = try chacha.decrypt(toDecrypt)
+        let message = Array(decrypted.dropFirst(64))
+        return (message, true)
+    }
+    
+    private class func calculateTag( cipher:Array<UInt8>, header:Array<UInt8>, encodedKey:Array<UInt8> ) throws -> Array<UInt8> {
+        
+        let poly1305 = Poly1305(key: encodedKey)
+        var mac = Array<UInt8>()
+        mac += header
+        let headerPadding = ((16 - (header.count & 0xf)) & 0xf)
+        mac += Array<UInt8>(repeating: 0, count: headerPadding)
+        mac += cipher
+        let cipherPadding = ((16 - (cipher.count & 0xf)) & 0xf)
+        mac += Array<UInt8>(repeating: 0, count: cipherPadding)
+        mac += UInt64(bigEndian: UInt64(header.count)).bytes()
+        mac += UInt64(bigEndian: UInt64(cipher.count)).bytes()
+        
+        return try poly1305.authenticate(mac)
+    }
+    
+}

+ 86 - 0
Tests/CryptoSwiftTests/ChaCha20Poly1305Tests.swift

@@ -0,0 +1,86 @@
+//
+//  ChaCha20Poly1305Tests.swift
+//  CryptoSwiftTests
+//
+//  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.
+//
+//
+//  https://tools.ietf.org/html/rfc7539#section-2.8.1
+
+import XCTest
+@testable import CryptoSwift
+
+class ChaCha20Poly1305Tests: XCTestCase {
+    
+    static let allTests = [
+        ("testCCPoly1", test1),
+        ("testCCPoly2", test2),
+        ("testCCPoly3", test3)
+    ]
+    
+    func test1() {
+        executeTestCase("6b65792e6b65792e6b65792e6b65792e6b65792e6b65792e6b65792e6b65792e",
+               "6e6f6e63652e6e6f6e63652e",
+               "",
+               "6d657373616765",
+               "5d9c0a9fe7d5e5",
+               "c93aa61fc3cc66a819ac96f6ce365aee")
+    }
+    
+    func test2() {
+        /* Test vector from section 2.8.2. */
+        executeTestCase("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+               "070000004041424344454647",
+               "50515253c0c1c2c3c4c5c6c7",
+               "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e",
+               "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116",
+               "1ae10b594f09e26a7e902ecbd0600691")
+    }
+    
+    func test3() {
+        /* Test vector from A.5. */
+        executeTestCase("1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
+               "000000000102030405060708",
+               "f33388860000000000004e91",
+               "496e7465726e65742d4472616674732061726520647261667420646f63756d656e74732076616c696420666f722061206d6178696d756d206f6620736978206d6f6e74687320616e64206d617920626520757064617465642c207265706c616365642c206f72206f62736f6c65746564206279206f7468657220646f63756d656e747320617420616e792074696d652e20497420697320696e617070726f70726961746520746f2075736520496e7465726e65742d447261667473206173207265666572656e6365206d6174657269616c206f7220746f2063697465207468656d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67726573732e2fe2809d",
+               "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb24c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c8559797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523eaf4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a1049e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29a6ad5cb4022b02709b",
+               "eead9d67890cbb22392336fea1851f38")
+    }
+    
+    func executeTestCase( _ key:String, _ nonce:String, _ header:String, _ message:String, _ cipher:String, _ tag:String ) {
+        
+        let keyArr     = Array<UInt8>(hex: key)
+        let nonceArr   = Array<UInt8>(hex: nonce)
+        let headerArr  = Array<UInt8>(hex: header)
+        let messageArr = Array<UInt8>(hex: message)
+        let cipherArr  = Array<UInt8>(hex: cipher)
+        let tagArr     = Array<UInt8>(hex: tag)
+        
+        do {
+            let encryptResult = try ChaCha20Poly1305.encrypt(message: messageArr, header: headerArr, key: keyArr, nonce: nonceArr)
+            
+            XCTAssertEqual(encryptResult.cipher, cipherArr, "cipher not equal")
+            XCTAssertEqual(encryptResult.tag, tagArr,  "tag not equal")
+        } catch {
+            XCTAssert(false, "Encryption Failed with error: \(error.localizedDescription)")
+        }
+        
+        do {
+            let decryptResult = try ChaCha20Poly1305.decrypt(cipher: cipherArr, header: headerArr, key: keyArr, nonce: nonceArr, tag: tagArr)
+            
+            XCTAssertEqual(decryptResult.success, true,  "decrypt mac check failed")
+            XCTAssertEqual(decryptResult.message, messageArr, "message not equal")
+        } catch {
+            XCTAssert(false, "Encryption Failed with error: \(error.localizedDescription)")
+        }
+    }
+}