Skip to content

Commit 1055df4

Browse files
committed
Split up unlock method so that the KEK can be retrieved/used
1 parent 56f6b3b commit 1055df4

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ let pepper = ... // optional
7777
let masterkey = try masterkeyFile.unlock(passphrase: passphrase, pepper: pepper)
7878
```
7979

80+
The unlock process can also be performed in two steps:
81+
82+
```swift
83+
let masterkeyFile = ...
84+
let passphrase = ...
85+
let pepper = ... // optional
86+
let kek = try masterkeyFile.deriveKey(passphrase: passphrase, pepper: pepper)
87+
let masterkey = try masterkeyFile.unlock(kek: kek)
88+
```
89+
90+
This is useful if you'd like to derive the key in an extra step since the function is memory-intensive (using scrypt). The result can then be used elsewhere, e.g. in a memory-restricted process.
91+
8092
#### Lock
8193

8294
For persisting the masterkey, use this method to export its encrypted/wrapped masterkey and other metadata as JSON data.

Sources/CryptomatorCryptoLib/MasterkeyFile.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,35 @@ public class MasterkeyFile {
7575
- Returns: A masterkey with the unwrapped keys.
7676
*/
7777
public func unlock(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> Masterkey {
78-
// derive keys:
78+
let kek = try deriveKey(passphrase: passphrase, pepper: pepper)
79+
return try unlock(kek: kek)
80+
}
81+
82+
/**
83+
Derives a KEK from the given passphrase and the params from this masterkey file using scrypt.
84+
85+
- Parameter passphrase: The passphrase used during key derivation.
86+
- Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array.
87+
- Returns: A 256-bit key derived from passphrase using scrypt.
88+
*/
89+
public func deriveKey(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> [UInt8] {
7990
let pw = [UInt8](passphrase.precomposedStringWithCanonicalMapping.utf8)
8091
let salt = [UInt8](Data(base64Encoded: content.scryptSalt)!)
8192
var kek = [UInt8](repeating: 0x00, count: kCCKeySizeAES256)
8293
let scryptResult = crypto_scrypt(pw, pw.count, salt + pepper, salt.count + pepper.count, UInt64(content.scryptCostParam), UInt32(content.scryptBlockSize), 1, &kek, kCCKeySizeAES256)
8394
guard scryptResult == 0 else {
8495
throw MasterkeyFileError.keyDerivationFailed
8596
}
97+
return kek
98+
}
99+
100+
/**
101+
Unwraps the stored encryption and MAC keys with the given KEK.
102+
103+
- Parameter kek: The KEK for unwrapping the keys from this masterkey file.
104+
- Returns: A masterkey with the unwrapped keys.
105+
*/
106+
public func unlock(kek: [UInt8]) throws -> Masterkey {
86107
guard let wrappedMasterKey = Data(base64Encoded: content.primaryMasterKey) else {
87108
throw MasterkeyFileError.malformedMasterkeyFile("invalid base64 data in primaryMasterKey")
88109
}

Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class MasterkeyFileTests: XCTestCase {
3333
XCTAssertEqual("cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=", masterkeyFile.content.versionMac)
3434
}
3535

36-
func testUnlock() throws {
36+
func testUnlockWithPassphrase() throws {
3737
let expectedKey = [UInt8](repeating: 0x00, count: 32)
3838
let data = """
3939
{
@@ -142,6 +142,54 @@ class MasterkeyFileTests: XCTestCase {
142142
}
143143
}
144144

145+
func testDeriveKey() throws {
146+
let expectedKey: [UInt8] = [
147+
0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28,
148+
0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2,
149+
0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE,
150+
0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9
151+
]
152+
let data = """
153+
{
154+
"version": 7,
155+
"scryptSalt": "AAAAAAAAAAA=",
156+
"scryptCostParam": 2,
157+
"scryptBlockSize": 8,
158+
"primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
159+
"hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
160+
"versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g="
161+
}
162+
""".data(using: .utf8)!
163+
let masterkeyFile = try MasterkeyFile.withContentFromData(data: data)
164+
let kek = try masterkeyFile.deriveKey(passphrase: "asd", pepper: [UInt8]())
165+
XCTAssertEqual(expectedKey, kek)
166+
}
167+
168+
func testUnlockWithKEK() throws {
169+
let expectedKey = [UInt8](repeating: 0x00, count: 32)
170+
let data = """
171+
{
172+
"version": 7,
173+
"scryptSalt": "AAAAAAAAAAA=",
174+
"scryptCostParam": 2,
175+
"scryptBlockSize": 8,
176+
"primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
177+
"hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
178+
"versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g="
179+
}
180+
""".data(using: .utf8)!
181+
let masterkeyFile = try MasterkeyFile.withContentFromData(data: data)
182+
let kek: [UInt8] = [
183+
0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28,
184+
0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2,
185+
0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE,
186+
0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9
187+
]
188+
let masterkey = try masterkeyFile.unlock(kek: kek)
189+
XCTAssertEqual(expectedKey, masterkey.aesMasterKey)
190+
XCTAssertEqual(expectedKey, masterkey.macMasterKey)
191+
}
192+
145193
func testLock() throws {
146194
let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32))
147195
let content = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "asd", pepper: [UInt8](), scryptCostParam: 2, cryptoSupport: CryptoSupportMock())

0 commit comments

Comments
 (0)