Skip to content

Commit

Permalink
Add nonce-accepting overload of secretBox.seal
Browse files Browse the repository at this point in the history
As good as the current API is at some times it is necessary to have
reproducible results of the encryption for several reasons:

* It is already supported by `.box.seal()`
* It is necessary for distributed systems where remote peers must
  agree on the hash of encrypted content upon re-encryption.

This commit introduces:

```
sodium.secureBox.seal(message: [...], secretKey: [...], nonce: [...])
```

That takes the usual `message` and `secretKey`, and an additional
user-provided `nonce` to generate the result that is completely defined
by those inputs and does not rely on any entropy.
  • Loading branch information
indutny committed Aug 5, 2019
1 parent 419a1c0 commit c782d4e
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -90,6 +90,8 @@ This API encrypts a message. The decryption process check that they haven't been

Messages encrypted that way are independent: if multiple messages are sent that way, the recipient cannot detect if some messages have been duplicated, deleted or reordered without including additional data to each message.

Optionally, `SecretBox` provides the ability to utilize a user-defined nonce via `seal(message: secretKey: nonce:)`.

## Public-key Cryptography

With public-key cryptography, each peer has two keys: a secret key, that has to remain secret, and a public key that anyone can use to send an encrypted message to that peer. That public key can be only be used to encrypt a message. The related secret is required to decrypt it.
Expand Down
22 changes: 20 additions & 2 deletions Sodium/SecretBox.swift
Expand Up @@ -32,9 +32,27 @@ extension SecretBox {
- Returns: The authenticated ciphertext and encryption nonce.
*/
public func seal(message: Bytes, secretKey: Key) -> (authenticatedCipherText: Bytes, nonce: Nonce)? {
let nonce = self.nonce()

guard let authenticatedCipherText = seal(message: message, secretKey: secretKey, nonce: nonce) else {
return nil
}

return (authenticatedCipherText: authenticatedCipherText, nonce: nonce)
}

/**
Encrypts a message with a shared secret key and a user-provided nonce.
- Parameter message: The message to encrypt.
- Parameter secretKey: The shared secret key.
- Parameter nonce: The encryption nonce.
- Returns: The authenticated ciphertext.
*/
public func seal(message: Bytes, secretKey: Key, nonce: Nonce) -> Bytes? {
guard secretKey.count == KeyBytes else { return nil }
var authenticatedCipherText = Bytes(count: message.count + MacBytes)
let nonce = self.nonce()

guard .SUCCESS == crypto_secretbox_easy (
&authenticatedCipherText,
Expand All @@ -43,7 +61,7 @@ extension SecretBox {
secretKey
).exitCode else { return nil }

return (authenticatedCipherText: authenticatedCipherText, nonce: nonce)
return authenticatedCipherText
}

/**
Expand Down
4 changes: 4 additions & 0 deletions Tests/SodiumTests/SodiumTests.swift
Expand Up @@ -119,6 +119,10 @@ class SodiumTests: XCTestCase {
let (encrypted4, nonce4, mac4) = sodium.secretBox.seal(message: message, secretKey: secretKey)!
XCTAssertNil(sodium.secretBox.open(cipherText: encrypted4, secretKey: secretKey, nonce: nonce3, mac: mac4), "Shouldn't be able to decrypt with an invalid MAC")
XCTAssertNil(sodium.secretBox.open(cipherText: encrypted4, secretKey: secretKey, nonce: nonce4, mac: mac3), "Shouldn't be able to decrypt with an invalid nonce")

// reproduce encryption result with user-provided nonce
let encrypted5 = sodium.secretBox.seal(message: message, secretKey: secretKey, nonce: nonce2)!
XCTAssertEqual(encrypted5, encrypted2)
}

func testGenericHash() {
Expand Down

0 comments on commit c782d4e

Please sign in to comment.