Skip to content

Commit

Permalink
Split up unlock method so that the KEK can be retrieved/used
Browse files Browse the repository at this point in the history
  • Loading branch information
tobihagemann committed Jul 7, 2021
1 parent 56f6b3b commit 1055df4
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 2 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ let pepper = ... // optional
let masterkey = try masterkeyFile.unlock(passphrase: passphrase, pepper: pepper)
```

The unlock process can also be performed in two steps:

```swift
let masterkeyFile = ...
let passphrase = ...
let pepper = ... // optional
let kek = try masterkeyFile.deriveKey(passphrase: passphrase, pepper: pepper)
let masterkey = try masterkeyFile.unlock(kek: kek)
```

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.

#### Lock

For persisting the masterkey, use this method to export its encrypted/wrapped masterkey and other metadata as JSON data.
Expand Down
23 changes: 22 additions & 1 deletion Sources/CryptomatorCryptoLib/MasterkeyFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,35 @@ public class MasterkeyFile {
- Returns: A masterkey with the unwrapped keys.
*/
public func unlock(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> Masterkey {
// derive keys:
let kek = try deriveKey(passphrase: passphrase, pepper: pepper)
return try unlock(kek: kek)
}

/**
Derives a KEK from the given passphrase and the params from this masterkey file using scrypt.

- Parameter passphrase: The passphrase used during key derivation.
- Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array.
- Returns: A 256-bit key derived from passphrase using scrypt.
*/
public func deriveKey(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> [UInt8] {
let pw = [UInt8](passphrase.precomposedStringWithCanonicalMapping.utf8)
let salt = [UInt8](Data(base64Encoded: content.scryptSalt)!)
var kek = [UInt8](repeating: 0x00, count: kCCKeySizeAES256)
let scryptResult = crypto_scrypt(pw, pw.count, salt + pepper, salt.count + pepper.count, UInt64(content.scryptCostParam), UInt32(content.scryptBlockSize), 1, &kek, kCCKeySizeAES256)
guard scryptResult == 0 else {
throw MasterkeyFileError.keyDerivationFailed
}
return kek
}

/**
Unwraps the stored encryption and MAC keys with the given KEK.

- Parameter kek: The KEK for unwrapping the keys from this masterkey file.
- Returns: A masterkey with the unwrapped keys.
*/
public func unlock(kek: [UInt8]) throws -> Masterkey {
guard let wrappedMasterKey = Data(base64Encoded: content.primaryMasterKey) else {
throw MasterkeyFileError.malformedMasterkeyFile("invalid base64 data in primaryMasterKey")
}
Expand Down
50 changes: 49 additions & 1 deletion Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class MasterkeyFileTests: XCTestCase {
XCTAssertEqual("cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=", masterkeyFile.content.versionMac)
}

func testUnlock() throws {
func testUnlockWithPassphrase() throws {
let expectedKey = [UInt8](repeating: 0x00, count: 32)
let data = """
{
Expand Down Expand Up @@ -142,6 +142,54 @@ class MasterkeyFileTests: XCTestCase {
}
}

func testDeriveKey() throws {
let expectedKey: [UInt8] = [
0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28,
0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2,
0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE,
0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9
]
let data = """
{
"version": 7,
"scryptSalt": "AAAAAAAAAAA=",
"scryptCostParam": 2,
"scryptBlockSize": 8,
"primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
"hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
"versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g="
}
""".data(using: .utf8)!
let masterkeyFile = try MasterkeyFile.withContentFromData(data: data)
let kek = try masterkeyFile.deriveKey(passphrase: "asd", pepper: [UInt8]())
XCTAssertEqual(expectedKey, kek)
}

func testUnlockWithKEK() throws {
let expectedKey = [UInt8](repeating: 0x00, count: 32)
let data = """
{
"version": 7,
"scryptSalt": "AAAAAAAAAAA=",
"scryptCostParam": 2,
"scryptBlockSize": 8,
"primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
"hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==",
"versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g="
}
""".data(using: .utf8)!
let masterkeyFile = try MasterkeyFile.withContentFromData(data: data)
let kek: [UInt8] = [
0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28,
0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2,
0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE,
0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9
]
let masterkey = try masterkeyFile.unlock(kek: kek)
XCTAssertEqual(expectedKey, masterkey.aesMasterKey)
XCTAssertEqual(expectedKey, masterkey.macMasterKey)
}

func testLock() throws {
let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32))
let content = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "asd", pepper: [UInt8](), scryptCostParam: 2, cryptoSupport: CryptoSupportMock())
Expand Down

0 comments on commit 1055df4

Please sign in to comment.