Skip to content

Commit a20478e

Browse files
Add GCM ContentCryptor
1 parent 406ac20 commit a20478e

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

CryptomatorCryptoLib.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
9EB822C1248AF82200879838 /* AesCtr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C0248AF82200879838 /* AesCtr.swift */; };
3636
9EB822C3248AF9C500879838 /* AesCtrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C2248AF9C500879838 /* AesCtrTests.swift */; };
3737
9EBEC947283782E6002210DE /* CtrCryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */; };
38+
9EBEC94928378308002210DE /* GcmCryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEC94828378308002210DE /* GcmCryptorTests.swift */; };
3839
/* End PBXBuildFile section */
3940

4041
/* Begin PBXContainerItemProxy section */
@@ -99,6 +100,7 @@
99100
9EB822C0248AF82200879838 /* AesCtr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtr.swift; sourceTree = "<group>"; };
100101
9EB822C2248AF9C500879838 /* AesCtrTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtrTests.swift; sourceTree = "<group>"; };
101102
9EBEC946283782E6002210DE /* CtrCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CtrCryptorTests.swift; sourceTree = "<group>"; };
103+
9EBEC94828378308002210DE /* GcmCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GcmCryptorTests.swift; sourceTree = "<group>"; };
102104
/* End PBXFileReference section */
103105

104106
/* Begin PBXFrameworksBuildPhase section */
@@ -174,6 +176,7 @@
174176
9E44EEA724599C7800A37B01 /* AesSivTests.swift */,
175177
9E35C4EA24576A3D0006E50C /* CryptorTests.swift */,
176178
9EBEC946283782E6002210DE /* CtrCryptorTests.swift */,
179+
9EBEC94828378308002210DE /* GcmCryptorTests.swift */,
177180
74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */,
178181
74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */,
179182
9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */,
@@ -451,6 +454,7 @@
451454
74A5B57625A869DD002D10F7 /* MasterkeyFileTests.swift in Sources */,
452455
9E9BB81624558DFF00F9FF51 /* MasterkeyTests.swift in Sources */,
453456
9E35C4EB24576A3D0006E50C /* CryptorTests.swift in Sources */,
457+
9EBEC94928378308002210DE /* GcmCryptorTests.swift in Sources */,
454458
);
455459
runOnlyForDeploymentPostprocessing = 0;
456460
};

Sources/CryptomatorCryptoLib/ContentCryptor.swift

+26
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import CommonCrypto
10+
import CryptoKit
1011
import Foundation
1112

1213
protocol ContentCryptor {
@@ -35,6 +36,31 @@ protocol ContentCryptor {
3536
func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]...) throws -> [UInt8]
3637
}
3738

39+
class GcmContentCryptor: ContentCryptor {
40+
let nonceLen = 12 // 96 bit
41+
let tagLen = 16 // 128 bit
42+
43+
func encrypt(_ chunk: [UInt8], key keyBytes: [UInt8], nonce nonceBytes: [UInt8], ad: [UInt8]...) throws -> [UInt8] {
44+
let concatAd = ad.reduce([], +)
45+
let key = SymmetricKey(data: keyBytes)
46+
let nonce = try AES.GCM.Nonce(data: nonceBytes)
47+
let encrypted = try AES.GCM.seal(chunk, using: key, nonce: nonce, authenticating: concatAd)
48+
49+
return [UInt8](encrypted.nonce + encrypted.ciphertext + encrypted.tag)
50+
}
51+
52+
func decrypt(_ chunk: [UInt8], key keyBytes: [UInt8], ad: [UInt8]...) throws -> [UInt8] {
53+
assert(chunk.count >= nonceLen + tagLen, "ciphertext chunk must at least contain nonce + tag")
54+
55+
let concatAd = ad.reduce([], +)
56+
let key = SymmetricKey(data: keyBytes)
57+
let encrypted = try AES.GCM.SealedBox(combined: chunk)
58+
let decrypted = try AES.GCM.open(encrypted, using: key, authenticating: concatAd)
59+
60+
return [UInt8](decrypted)
61+
}
62+
}
63+
3864
class CtrThenHmacContentCryptor: ContentCryptor {
3965
let nonceLen = kCCBlockSizeAES128
4066
let tagLen = Int(CC_SHA256_DIGEST_LENGTH)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// CryptorTests.swift
3+
// CryptomatorCryptoLibTests
4+
//
5+
// Created by Sebastian Stenzel on 27.04.20.
6+
// Copyright © 2020 Skymatic GmbH. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import CryptomatorCryptoLib
11+
12+
class GcmCryptorTests: CryptorTests {
13+
override class var defaultTestSuite: XCTestSuite {
14+
return XCTestSuite(forTestCaseClass: GcmCryptorTests.self)
15+
}
16+
17+
override func setUpWithError() throws {
18+
let aesKey = [UInt8](repeating: 0x55, count: 32)
19+
let macKey = [UInt8](repeating: 0x77, count: 32)
20+
let masterkey = Masterkey.createFromRaw(aesMasterKey: aesKey, macMasterKey: macKey)
21+
let cryptoSupport = CryptoSupportMock()
22+
let contentCryptor = GcmContentCryptor()
23+
24+
try super.setUpWithError(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor)
25+
}
26+
27+
func testCreateHeader() throws {
28+
let header = try cryptor.createHeader()
29+
XCTAssertEqual([UInt8](repeating: 0xF0, count: 12), header.nonce)
30+
XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), header.contentKey)
31+
}
32+
33+
func testEncryptHeader() throws {
34+
let header = try cryptor.createHeader()
35+
let encrypted = try cryptor.encryptHeader(header)
36+
37+
// echo -n "///////////w8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8A==" | base64 --decode \
38+
// | openssl enc -aes-256-gcm -K 5555555555555555555555555555555555555555555555555555555555555555 -iv F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0 -a
39+
let expected: [UInt8] = [
40+
// nonce
41+
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
42+
0xF0, 0xF0, 0xF0, 0xF0,
43+
// ciphertext
44+
0x1C, 0x87, 0x19, 0xF0, 0x31, 0x22, 0x86, 0x8F,
45+
0xDB, 0x9D, 0x97, 0x03, 0xA0, 0x86, 0x08, 0xD5,
46+
0x88, 0x58, 0x96, 0xC2, 0xE6, 0x60, 0x4B, 0xB9,
47+
0xEA, 0x64, 0x31, 0xD4, 0xA0, 0x5D, 0x47, 0x6F,
48+
0xE4, 0x1F, 0x32, 0x31, 0xF2, 0xC0, 0x61, 0x1F,
49+
// tag
50+
0x6D, 0x42, 0x98, 0x82, 0x43, 0xF2, 0x1F, 0x43,
51+
0xF6, 0x44, 0xFD, 0x6D, 0xF7, 0xA9, 0x3F, 0x0B
52+
]
53+
XCTAssertEqual(expected, encrypted)
54+
}
55+
56+
func testDecryptHeader() throws {
57+
let ciphertext: [UInt8] = [
58+
// nonce
59+
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
60+
0xF0, 0xF0, 0xF0, 0xF0,
61+
// ciphertext
62+
0x1C, 0x87, 0x19, 0xF0, 0x31, 0x22, 0x86, 0x8F,
63+
0xDB, 0x9D, 0x97, 0x03, 0xA0, 0x86, 0x08, 0xD5,
64+
0x88, 0x58, 0x96, 0xC2, 0xE6, 0x60, 0x4B, 0xB9,
65+
0xEA, 0x64, 0x31, 0xD4, 0xA0, 0x5D, 0x47, 0x6F,
66+
0xE4, 0x1F, 0x32, 0x31, 0xF2, 0xC0, 0x61, 0x1F,
67+
// tag
68+
0x6D, 0x42, 0x98, 0x82, 0x43, 0xF2, 0x1F, 0x43,
69+
0xF6, 0x44, 0xFD, 0x6D, 0xF7, 0xA9, 0x3F, 0x0B
70+
]
71+
let decrypted = try cryptor.decryptHeader(ciphertext)
72+
XCTAssertEqual([UInt8](repeating: 0xF0, count: 12), decrypted.nonce)
73+
XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), decrypted.contentKey)
74+
}
75+
}

0 commit comments

Comments
 (0)