/* * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ package com.facebook.crypto.keygen; import java.security.SecureRandom; import com.facebook.crypto.exception.CryptoInitializationException; import com.facebook.crypto.util.NativeCryptoLibrary; /** * Generates encryption keys derived from arbitrary String passwords. * Implementation of PBKDF2: Password-Based Key Derivation Function v2. * https://en.wikipedia.org/wiki/PBKDF2 * It uses OpenSSL's PBKDF2-HMAC-SHA256. * <p> * Caller provides a password and optionally the salt to use. * If not provided a random 128-bit salt will be used and can be retrieved with getSalt(). * Iterations can be set before calling. * <p> * Usage: * <code> * byte[] key = * new PasswordBasedKeyDerivation(nativeCryptoLibrary) * .setIterations(10000) * .setPassword("P4$$word") * .setSalt(buffer) * .setKeyLengthInBytes(16) // in bytes * .generate(); * </code> * <p> * Bear in mind that SecureRandom will be used to automatically generate a default salt. * Android's SecureRandom implementation is not secure and SecureRandomFix must be invoked first. * See {@link com.facebook.android.crypto.keychain.SecureRandomFix} */ public class PasswordBasedKeyDerivation { private static final int MINIMUM_SALT_LENGTH = 4; private static final int DEFAULT_SALT_LENGTH = 16; public static final int MINIMUM_ITERATIONS = 1; public static final int DEFAULT_ITERATIONS = 4096; public static final int MINIMUM_KEY_LENGTH = 8; public static final int DEFAULT_KEY_LENGTH = 16; private final NativeCryptoLibrary mNativeLibrary; private final SecureRandom mSecureRandom; private int mIterations; private String mPassword; private byte[] mSalt; private int mKeyLengthInBytes; private byte[] mGeneratedKey; /** * @param secureRandom this will be used to generate the salt. * Use FixedSecureRandom for Android, never java.util.SecureRandom. */ public PasswordBasedKeyDerivation(SecureRandom secureRandom, NativeCryptoLibrary library) { mSecureRandom = secureRandom; mNativeLibrary = library; mIterations = DEFAULT_ITERATIONS; mKeyLengthInBytes = DEFAULT_KEY_LENGTH; } public PasswordBasedKeyDerivation setIterations(int iterations) { if (iterations < MINIMUM_ITERATIONS) { throw new IllegalArgumentException("Iterations cannot be less than " + MINIMUM_ITERATIONS); } mIterations = iterations; return this; } public PasswordBasedKeyDerivation setPassword(String password) { if (password == null) { throw new IllegalArgumentException("Password cannot be null"); } mPassword = password; return this; } public PasswordBasedKeyDerivation setSalt(byte[] salt) { if (salt != null && salt.length < MINIMUM_SALT_LENGTH) { throw new IllegalArgumentException("Salt cannot be shorter than 8 bytes"); } mSalt = salt; return this; } public PasswordBasedKeyDerivation setKeyLengthInBytes(int keyLengthInBytes) { if (keyLengthInBytes < MINIMUM_KEY_LENGTH) { throw new IllegalArgumentException("Key length cannot be less than 8 bytes"); } mKeyLengthInBytes = keyLengthInBytes; return this; } public byte[] generate() throws CryptoInitializationException { if (mPassword == null) { throw new IllegalStateException("Password was not set"); } if (mSalt == null) { mSalt = new byte[DEFAULT_SALT_LENGTH]; mSecureRandom.nextBytes(mSalt); } mGeneratedKey = new byte[mKeyLengthInBytes]; mNativeLibrary.ensureCryptoLoaded(); int result = nativePbkdf2(mPassword, mSalt, mIterations, mGeneratedKey); if (result != 1) { throw new RuntimeException("Native PBKDF2 failed..."); } return mGeneratedKey; } private native int nativePbkdf2( String password, byte[] salt, int iterations, byte[] result); public int getIterations() { return mIterations; } public String getPassword() { return mPassword; } public byte[] getSalt() { return mSalt; } public int getKeyLengthInBytes() { return mKeyLengthInBytes; } public byte[] getGeneratedKey() { return mGeneratedKey; } }