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: JavaThemis #635

Merged
merged 4 commits into from
May 12, 2020
Merged

Conversation

ilammy
Copy link
Collaborator

@ilammy ilammy commented May 6, 2020

With preparatory changes in #634, this PR adds actual passphrase API as described in RFC 3.9.

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

import com.cossacklabs.themis.SecureCell

val cell = SecureCell.SealWithPassphrase("secret")

val message = "precious message".toByteArray()

val encrypted = cell.encrypt(message)
val decrypted = cell.decrypt(encrypted)

assertArrayEquals(decrypted, message)

And the same API in Java:

import com.cossacklabs.themis.SecureCell;

SecureCell.Seal cell = SecureCell.SealWithPassphrase("secret");

byte[] message = "precious message".getBytes(StandardCharsets.UTF_8);

byte[] encrypted = cell.encrypt(message);
byte[] decrypted = cell.decrypt(encrypted);

assertArrayEquals(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 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 com.cossacklabs.themis.SecureCell
import com.cossacklabs.themis.SymmetricKey

// Generate a new key if you don't have one:
val masterKey = SymmetricKey()
// Or use an existing value that you store somewhere:
val masterKey = Base64.Decoder.decode("b0gyNlM4LTFKRDI5anFIRGJ4SmQyLGE7MXN5YWUzR2U=")

val cell = SecureCell.SealWithKey(masterKey)

val message = "precious message".toByteArray()

val encrypted = cell.encrypt(message)
val decrypted = cell.decrypt(encrypted)

assertArrayEquals(decrypted, message)

Unicode considerations

Note that passphrase API uses UTF-8 by default for compatibility with other platforms. If you need to use a different encoding, there are variety of interfaces to allow that:

  • SecureCell#SealWithPassphrase(String) — use UTF-8, the default
  • SecureCell#SealWithPassphrase(String, Charset) — use the specified charset
  • SecureCell#SealWithPassphrase(byte[]) — in case you already have it encoded

Technical notes

There are no deviations from RFC 3.9 with respect to passphrase API.

We introduce a new helper – PassphraseBytes – and extend the JNI API to support passphrases.

Checklist

  • Dependencies are merged: Update JavaThemis Secure Cell API #634, this PR is rebased
  • Change is covered by automated tests
  • Benchmark results are attached (no JVM benches)
  • The coding guidelines are followed
  • Public API has proper documentation
  • Example projects and code samples are up-to-date (later)
  • Changelog is updated

@ilammy ilammy added O-Android 🤖 Operating system: Android W-JavaThemis ☕ Wrapper: Java, Java and Kotlin API labels May 6, 2020
@ilammy ilammy requested review from vixentael and Lagovas May 6, 2020 11:09
@ilammy ilammy mentioned this pull request May 6, 2020
6 tasks
Add a new mode -- MODE_SEAL_PASSPHRASE -- to support Seal encryption
mode secured by passphrase. We do not introduce new API to JNI library
to keep ABI compatibility. Since the cost of doing JNI call is already
that high, it does not win us anything to use static mode-setting. It
makes sense to refactor Java API, but changing this JNI API does not
affect the users so it will be mostly a waste.

Note that Java constant is package-private. We don't want users to use
it with the old API because it will be extremely confusing. The old API
will accept strings, but it will encode them in UTF-16 by default which
is not what the new passphrase API does. Therefore we keep the constant
private. The old constants are getting deprecated too later.
Add a new package-private class SecureCellSealWithPassphrase which
implements SecureCell.Seal interface, but with passphrases instead
of symmetric keys.

The new class can be instantiated with SecureCell#SealWithPassphrase
methods that accept a variety of inputs. Normally the String interface
should be used to get compatible behavior. Other interfaces can be used
if you need some non-UTF-8 encoding for the passphrase, or if you're
doing something weird.

We need to store the passphrase bytes somewhere. A new helper class --
PassphraseBytes -- is added for that. It extends KeyBytes to implement
IKey interface and holds the sensitive data for us. It also hosts the
encodinng conversion routine. We cannot use the standard String#getBytes
because it replaces unknow characters with ?, leading to unexpected and
not really portable behavior.
Once again, for the most part this is rewrite of Swift test suite into
Java. However, it has been augmented with some Java-specific tests to
verify that we handle the old 'passphrase-like' API in a sane way.
@ilammy ilammy marked this pull request as ready for review May 11, 2020 09:33
@ilammy
Copy link
Collaborator Author

ilammy commented May 11, 2020

Whew, this PR is ready for review now.

Copy link
Collaborator

@Lagovas Lagovas left a comment

Choose a reason for hiding this comment

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

lgtm

@Test
public void defaultEncoding() throws SecureCellException {
// Passphrases are encoded in UTF-8 by default.
String passphrase = "暗号";
Copy link
Contributor

Choose a reason for hiding this comment

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

cipher!

try {
// It is an error if the passphrase cannot be represented in the requested charset
// without data loss.
SecureCell.SealWithPassphrase("пароль", StandardCharsets.US_ASCII);
Copy link
Contributor

Choose a reason for hiding this comment

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

so here we test that this method will fail if receive inconsistent string and encoding?

Copy link
Collaborator Author

@ilammy ilammy May 12, 2020

Choose a reason for hiding this comment

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

so here we test that this method will fail if receive inconsistent string and encoding?

Yeah, that's the idea behind this test.

PassphraseBytes#encodePassphrase is not directly tested, but here we ensure that it's actually used, not String#getBytes (which would have turned "пароль" into "??????" here). Encoding conversion happy path is indirectly verified by other passphrase tests.

@ilammy ilammy merged commit 7499550 into cossacklabs:master May 12, 2020
@ilammy ilammy deleted the kdf/java branch May 12, 2020 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
O-Android 🤖 Operating system: Android W-JavaThemis ☕ Wrapper: Java, Java and Kotlin API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants