facebookarchive / conceal

Conceal provides easy Android APIs for performing fast encryption and authentication of data.
http://facebook.github.io/conceal/
Other
2.96k stars 431 forks source link

[Question] Password based key derivation usage #187

Closed idish closed 6 years ago

idish commented 6 years ago

I've been trying to use conceal to encrypt/decrypt data based on a user's passphrase and not a random key.

byte[] key = AndroidConceal.get().createPasswordBasedKeyDerivation()
                           .setIterations(10000)
                           .setPassword(pin)
                           .setSalt(buffer)
                           .setKeyLengthInBytes(32) // in bytes
                           .generate();

2 questions:

  1. Does my key length should be 32 bytes as in my code lines and not 16 (as it is currently in the defaults), since I've read in the README file that keys should use 256 bit keys, and not 128 since version 1.1.3?
  2. How do I create a Crypto object out of this byte[] type key. Do I need to generate a KeyChain object before? How? Unfortunately I couldn't find any hint about this one, also in the tests package.

Thank you!

helios175 commented 6 years ago

Very on point:

1) You should use the key length for the crypto you are using. If you are already using 128 bits, that's fine. Generate 16 bits. if you want to upgrade change it. That's the key length that will be generated, so it depends on what you need.

If I'm not wrong the AndroidConceal.createDefaultCrypto(...) default creates one with 256 bits. But you can still use the createCrypto128Bits(...) method.

2) For now there's only one implementation for the KeyChain, the one that randomly generates one and stores it in SharedPreferences, but there should be more.

There's some work to do here around key handling (which is as important as encryption itself!). I didn't work lately on any encryption related projects so I couldn't contribute on it.

Said that. If you are basing your encryption key on a password, that's cool because it doesn't get stored anyway (only in user's brain) so there's no problem of how to store/communicate the key securely.

What you need to do is implement a KeyChain interface based on the generator.

public class PasswordGeneratedKeyChain implements KeyChain {
  private final CryptoConfig mConfig;
  private final PasswordBasedKeyDerivation mDerivation;
  private final byte[] mKey;

  public PasswordGeneratedKeyChain(CryptoConfig config) {
    mConfig = config;
    mDerivation = new PasswordBasedKeyDerivation(AndroidConceal.get().nativeLibrary);
    mDerivation.setKeyLengthInBytes(config.keyLength);
  }

  public void setPassword(String pwd) { mDerivation.setPassword(pwd); }

  // used only for reading, it should read the salt from the same place the encrypted content is
  public void setSalt(byte[] salt) { mDerivation.setSalt(salt); }

  // used only for encrypting, you will need to store it in the same place you're writing the encrypted content
  public byte[] getSalt() { return mDerivation.getSalt(); }

  public void generate() {
    mKey = mDerivation.generate();
  }

  /// implementing Key Chain

  // key for encryption
  public byte[] getCipherKey() {
    if (mKey == null) throw new IllegalStateException("You need to call generate() first");
    return mKey;
  }

  // key for mac
  public byte[] getMacKey() {
    // if you need mac you need a second derivation object
    throw new UnsupportedOperationException("implemented only for encryption, not mac");
  }

  // this is just a glorified "get me a new nonce"
  public byte[] getNewIV() {
    byte[] result = new byte[mConfig.ivLength];
    AndroidConceal.get().secureRandom.nextBytes(result);
    return result;
  }

  public void destroyKeys() {
    Arrays.fill(mKey, (byte) 0);
    mKey = null;
  }
}

And then you can use your PasswordGeneratedKeyChain where you need it:

PasswordGeneratedKeyChain pgkc = new PasswordGeneratedKeyChain(CryptoConfig.KEY_256);
pgkc.setPassword(pwd);
pgkc.generate(); // generates a new salt + key
Crypto c = AndroidConceal.get().createDefaultCrypto(pgkc);
// use c to encrypt

and

PasswordGeneratedKeyChain pgkc = new PasswordGeneratedKeyChain(CryptoConfig.KEY_256);
pgkc.setPassword(pwd);
pgkc.setSalt(saltStoredAlongTheEncryptedContent);
pgkc.generate(); // generates a new salt + key
Crypto c = AndroidConceal.get().createDefaultCrypto(pgkc);
// use c to decrypt
helios175 commented 6 years ago

I should probably add something like this to the library so you don't need to reimplement it. For now copy that and check it out!

helios175 commented 6 years ago

NOTICE: the previous message assumed that each password/pin was per encrypted content.

In the case you're using the same password/pin per user, or unique, the salt could be the same for several different encrypted contents as it has nothing to do with it:

USER_PIN + SALT -> USER_KEY

In that case you could store it somewhere (shared prefs?). But that depends on how to intend to use it.

idish commented 6 years ago

Thank you, that is great, works as expected! I've implemented another KeyChain representing a key chain based on a given key, and not generated one. If you would like to add this one as well to the library, here it is (or would you like me to PR?) I'm not sure about the naming of the class tho:

public class ConstKeyChain implements KeyChain {

    private byte[] mKey;
    private final CryptoConfig mConfig;

    public ConstKeyChain(byte[] key, CryptoConfig config) {
        if (key.length != config.keyLength) {
            throw new IllegalStateException("key must be the same length as the config states");
        }
        mConfig = config;
        mKey = key;
    }

    @Override
    public byte[] getCipherKey() throws KeyChainException {
        return mKey;
    }

    @Override
    public byte[] getMacKey() throws KeyChainException {
        throw new UnsupportedOperationException("implemented only for encryption, not mac");
    }

    // this is just a glorified "get me a new nonce"
    public byte[] getNewIV() {
        byte[] result = new byte[mConfig.ivLength];
        AndroidConceal.get().secureRandom.nextBytes(result);
        return result;
    }

    @Override
    public void destroyKeys() {
        Arrays.fill(mKey, (byte) 0);
        mKey = null;
    }
}
helios175 commented 6 years ago

I have to sit down and do some housekeeping. I will take the opportunity to upload it then. Thanks for the testing!