Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secure Cell passphrase API: GoThemis #625

Merged
merged 4 commits into from
Apr 24, 2020

Conversation

ilammy
Copy link
Collaborator

@ilammy ilammy commented Apr 23, 2020

With preparatory changes in #624, this PR adds actual passphrase API as described in RFC 3.7 (with corrections).

User notes

Passphrase-based interface of Secure Cell allows you to use short and memorable passphrases to secure your data. While symmetric keys are more secure, they are also longer and much harder for humans to remember.

Here is how you can use passphrases with Secure Cell in Go:

import "github.com/cossacklabs/themis/gothemis/cell"


scell, err := cell.SealWithPassphrase("secret")
if err != nil {
        return err
}

message := []byte("precious message")

encrypted, err := scell.Encrypt(message, nil)
if err != nil {
        return err
}
decrypted, err := scell.Decrypt(encrypted, nil)
if err != nil {
        return err
}

if bytes.Equal(decrypted, message) {
        fmt.Println("decrypted message is correct")
}

Passphrase API accepts passphrases as relatively short strings, suitable for human memory. Master key API uses randomly generated, long binary keys, which are more suitable for machines to remember. However, they are also much more efficient and generally more secure due to considerable length. You should prefer to use keys over passphrases if there are no humans involved. The interface is almost the same:

import "github.com/cossacklabs/themis/gothemis/keys"

key, err := keys.NewSymmetricKey()
if err != nil {
        return err
}
scell, err := cell.SealWithKey(key)
if err != nil {
        return err
}

message := []byte("precious message")

encrypted, err := scell.Encrypt(message, nil)
if err != nil {
        return err
}
decrypted, err := scell.Decrypt(encrypted, nil)
if err != nil {
        return err
}

if bytes.Equal(decrypted, message) {
        fmt.Println("decrypted message is correct")
}

Technical notes

The only deviation from RFC 3.7 is that the “associated context” argument is now required (syntactically). Go does not support method overloading so it’s not possible to omit it when calling Secure Cell methods. If you do not wish to use additional associated context for encryption, pass nil or empty slice as context.

Checklist

  • Change is covered by automated tests
  • Benchmark results are attached (no specific benchmarks for Go)
  • The coding guidelines are followed
  • Public API has proper documentation
  • Example projects and code samples are up-to-date
  • Changelog is updated

Introduce a new constructor "SealWithPassphrase" which returns a
different instance of Secure Cell that works with passphrases instead of
symmetric keys, using appropriate Themis Core API.

Note that we do not convert the string encoding here. Go's "string" type
is expected to contain UTF-8 data (consistent with what other wrappers
use). However, it can contain arbitrary byte sequences too. We do not
interpret them in any way (e.g., as []rune or whatever) and just use
the bytes as is.
@ilammy ilammy added the W-GoThemis 🐹 Wrapper: GoThemis, Go API label Apr 23, 2020
//
// https://docs.cossacklabs.com/pages/secure-cell-cryptosystem/#seal-mode
type SecureCellSealPassphrase struct {
passphrase string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I see, that it looks inefficient and we should provide a way to convert passphrase to key to allow cache this computation and not calculate key every time...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Here (and in other wrappers) we keep the original passphrase and pass it to C functions that actually perform KDF on every individual encryption or decryption.

It is straightforward, API-wise, to prederive key for encryption and cache it. However, it is not obvious how to handle decryption path which requires not only the passphrase but KDF parameters and salt as well, which both can be different for each message.

Our hope is that users would rarely need passphrase API to encrypt or decrypt massive amounts of data so some inefficiency is okay. If you are only going to use this API to encrypt a master key and then decrypt it relatively rarely then it's okay to pay KDF costs (~200 ms) every time you use this API.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho we can perform kdf and correctly pass context to encryption function in our wrappers, cache generated key and re-use it. Add semi-private (exportable as C code, but not documented anywhere and used only by our wrappers instead users) functions. One, which perform kdf on passphrase and export kdf context + new key. And second, is function for encryption which accept kdf context and symmetric key, and put context to correct place. Then we can use it in our wrappers and store in object symmetric key instead passphrase.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this idea has been explored (RFC 2, “Detached key derivation”). However, it introduces a lot of open questions:

  • API, ABI, data formats need to be backwards compatible
  • decryption path seems to be optimizable only with user assistance
  • making KDF parameters visible is not “simple” for users
  • reusing KDF parameters for multiple messages may have security implications

Immediate benefits include improved performance when encrypting data with the same KDF parameters and a later option to configure KDF for the users.

Given our expectations, this will not improve main user story for passphrase API which we expect to be used relatively rare to decrypt some master secret or a single configuration. It is expected that passphrase API will not be used to decrypt a lot of data chunks (e.g., an passphrased AcraStructs for each individual database cell). With these assumptions caching KDF results is not going to win much performance.

We can introduce optimizations later while keeping high-level API unchanged. The idea is to flesh out passphrase API, get some user input, and then maybe optimize Themis Core requests we do to implement that API.

@ilammy ilammy merged commit 81d8baf into cossacklabs:master Apr 24, 2020
@ilammy ilammy deleted the kdf/go-for-real branch April 24, 2020 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
W-GoThemis 🐹 Wrapper: GoThemis, Go API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants