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: PyThemis #596

Merged
merged 10 commits into from
Mar 18, 2020
Merged

Conversation

ilammy
Copy link
Collaborator

@ilammy ilammy commented Feb 28, 2020

Add support of Secure Cell passphrase API to PyThemis. The API is described in RFC 3.3 (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:

from pythemis.scell import SCellSeal

cell = SCellSeal(passphrase='my little password: secrets are magic')

plaintext = b'precious message'

encrypted = cell.encrypt(plaintext)
decrypted = cell.decrypt(encrypted)

assert decrypted == message

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 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:

from pythemis.skeygen import GenerateSymmetricKey

# Get a new key if you don't have one already:
master_key = GenerateSymmetricKey()
# Or use an existing value that you store somewhere:
master_key = base64.b64decode(b'b0gyNlM4LTFKRDI5anFIRGJ4SmQyLGE7MXN5YWUzR2U=')

cell = SCellSeal(key=master_key)

plaintext = b'precious message'

encrypted = cell.encrypt(plaintext)
decrypted = cell.decrypt(encrypted)

assert decrypted == message

API compatibility

Explicit choice between passphrases and keys

Normally you should use SCellSeal constructor for Secure Cell in Seal mode:

cell = SCellSeal(key=master_key)

cell = SCellSeal(passphrase=user_passphrase)

However, you can also import the SCellSealPassphrase class and construct it directly:

cell = SCellSealPassphrase(user_passphrase)

Only SCellSeal supports passphrase argument and passphrase encryption. SCellTokenProtect and SCellContextImprint work only with key.

Warnings about master key API misuse

Master keys should be provided as binary data. Previously PyThemis allowed to pass Unicode strings as master keys but this usage is now discouraged. PyThemis will emit warnings if strings are passed to master key API. If you see warnings like

UserWarning: master key should be "bytes" ...

then you should evaluate the call site and apply appropriate change:

  • Explicitly encode Unicode into bytes – if you believe that the passphrase is long enough to be securely used as a master key.
    • We recommend using 256 bits of entropy for master key API. Typical human-readable passphrase needs to be around 100 characters or 20 words long for that.
  • Migrate to using symmetric keys – if the secret will not be input by a human.
    • Generate new symmetric key using skeygen.GenerateSymmetricKey()
    • Update encryption and decryption call sites to use new keys
    • Reencrypt existing data, or provide fallback decryption path
  • Migrate to using passphrase= API – if the secret is going to be entered by a human.
    • Follow the same general process as described above.

Master keys and positional arguments

Previously, Secure Cell could be initialized with master key as a simple positional argument:

cell = SCellSeal(master_key)

This API is still available and supported. However, it is a good idea to explicitly say key= or passphrase= when using SCellSeal unless it is obvious from context.

Unicode considerations

Master keys are always binary data (bytes). Passphrases may be provided either as Unicode strings (str) or as encoded bytes. If you provide a Unicode passphrase it will be encoded in UTF-8 for compatibility with other Themis platforms. If you need a different encoding, please use encoding= optional parameter, for example:

cell = SCellSeal(passphrase=user_passphrase, encoding='utf-16')

In Python 2 master keys are expected as str values and passphrases may be provided either as str (raw bytes used as is), or as unicode (will be encoded in UTF-8 by default, use encoding= to change). Take care with string encoding.

Technical notes

__special__.__python__.__magic__.__with__.__new__ allows us to maintain syntax compatibility and introduce passphrase= as an extension. Initially a different syntax was planned:

SCellSeal.with_passphrase('a secret')

SCellSeal.with_key(binary_master_key)

It had a noble idea of maintaining similarity with other languages which do not have named arguments. However, after updating tests and examples this syntax came off as unnatural and non-Pythonic. The Zen of Python says:

There should be one — and preferably only one — obvious way to do it.

So now we have exactly one way to construct a Secure Cell in each mode: via SCellMode constructor. (Well, you can also import SCellSealPassphrase if you like that more.)

Note that another language with named arguments (Swift) is also going to get a similar API, so this is sort of a prior example.

Checklist

  • Change is covered by automated tests
  • The coding guidelines are followed
  • Public API has proper documentation
  • Example projects and code samples are up-to-date
  • Changelog is updated

Add support for passphrase API by extending SCellSeal construction API.
It is possible to add via Python's __new__ special method which may
return an instance of a diffrent class (typically, more appropriate
subclass). We introduce a SCellSealPassphrase subclass with the same API
but different implementation of "encrypt" and "decrypt" methods.

To make things easier we also add a SecureCellError to provide more
contexts in exceptions without making error messages too long.

Initially a different syntax was planned:

    SCellSeal.with_passphrase('a secret')

    SCellSeal.with_key(binary_master_key)

It had a noble idea of maintaining similarity with other languages which
do not have named arguments. However, after updating tests and examples
this syntax came off as unnatural and non-Pythonic.
Those are mostly straighforward. Also, update existing tests for master
key API and ensure that positional arguments to Secure Cell constuctors
are interpreted as keys, not passphrases.

Another change is update of tests to use a proper master key instead of
a fixed password with master key API.
The main showcase has been reworked completely to demonstrate API.
Other examples got cosmetic updates to avoid using 'passwords' when in
fact they work with master keys.
@ilammy ilammy added the W-PyThemis 🐍 Wrapper: PyThemis, Python API label Feb 28, 2020
src/wrappers/themis/python/pythemis/scell.py Outdated Show resolved Hide resolved
src/wrappers/themis/python/pythemis/scell.py Outdated Show resolved Hide resolved
src/wrappers/themis/python/pythemis/scell.py Show resolved Hide resolved
src/wrappers/themis/python/pythemis/scell.py Outdated Show resolved Hide resolved
src/wrappers/themis/python/pythemis/scell.py Outdated Show resolved Hide resolved
tools/python/scell_seal_string_echo_pw.py Show resolved Hide resolved
tools/python/scell_seal_string_echo_pw.py Show resolved Hide resolved
Instead of forcing the users to do '.encode(...)' calls themselves,
adhere to standard Python practice of accepting an "encoding" argument
with specified default encoding.
Instead of writing type hacks, use a compatibility library. This should
make it easier to convert PyThemis into Python 3-only when we decide to
drop Python 2 compatibility. We will need to replace all "six" calls
with native equivalents.
@ilammy
Copy link
Collaborator Author

ilammy commented Mar 2, 2020

I’ve merged PHP PR which enables integration testing of passphrase API between platforms. Now integration tests between Python and PHP are broken 🤔 Okay, tests, just with that you have paid off all effort that has been put into you. However, this also raises some questions:

  1. Where is the bug? Since both wrappers seem to use correct API. PHP 7 was wrong.
  2. How much I should be afraid, given that C++, Android, Objective-C/Swift, and Themis Core itself do not have integration tests with the rest of the wrappers?

Raise warnings instead of exceptions when we suspect that master key API
is misused with strings. Since we're not introducing a new API, it would
be rude to break Existing Code with unannounced exceptions. Howerver, we
cannot pass on the possible misuse either.

Master key API should not be used with strings. Currently, if you pass a
Unicode string, it will be encoded in some internal encoding (usually
UTF-8, but this may not be the case on Windows). Master key API should
not be used with human-readable strings due to security concerns.

Produce a warning when we see master key API to be used with types that
we previously allowed but no longer recommend. Provide suggestions on
what API should be used instead.

This warns the user of possible misuse (if they pay attention to
warnings) and does not break production code, which will keep the old
behavior of misusing strings as master keys.
@ilammy ilammy merged commit 1949e65 into cossacklabs:master Mar 18, 2020
@ilammy ilammy deleted the kdf/python branch March 18, 2020 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
W-PyThemis 🐍 Wrapper: PyThemis, Python API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants