package at.favre.lib.armadillo; import android.os.StrictMode; import androidx.annotation.Nullable; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.spec.InvalidKeySpecException; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; /** * In cryptography, PBKDF2 (Password-Based Key Derivation Function 2) are key derivation * functions with a sliding computational cost, aimed to reduce the vulnerability of encrypted keys * to brute force attacks. PBKDF2 is part of RSA Laboratories' Public-Key Cryptography Standards (PKCS) * series, specifically PKCS #5 v2.0, also published as Internet Engineering Task Force's RFC 2898. * <p> * This uses SHA1 as the underlying cryptographic hash functions, since SHA256+ are usually not * provided by standard JCA providers in Android. * * @author Patrick Favre-Bulle * @since 19.12.2017 */ public final class PBKDF2KeyStretcher implements KeyStretchingFunction { private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; private static final int PBKDF2_MIN_ITERATIONS = 1_000; private static final int PBKDF2_DEFAULT_ITERATIONS = 10_000; private final int iterations; private final Provider provider; /** * Create a new instance with default iteration count (see {@link #PBKDF2_DEFAULT_ITERATIONS} */ public PBKDF2KeyStretcher() { this(PBKDF2_DEFAULT_ITERATIONS, null); } /** * Creates a new instance. See https://cryptosense.com/parameter-choice-for-pbkdf2/ for * idea on what iteration count to choose * * @param iterations computational cost * @param provider optional security provider (might be null if default should be chosen) */ public PBKDF2KeyStretcher(int iterations, @Nullable Provider provider) { this.iterations = Math.max(PBKDF2_MIN_ITERATIONS, iterations); this.provider = provider; } @Override public byte[] stretch(byte[] salt, char[] password, int outLengthByte) { try { return pbkdf2(provider, password, salt, iterations, outLengthByte); } catch (Exception e) { throw new IllegalStateException("could not stretch with pbkdf2", e); } } /** * Computes the PBKDF2 hash of a password. * * @param password the password to hash. * @param salt the salt * @param iterations the iteration count (slowness factor) * @param outBytes the length of the hash to compute in bytes * @return the PBDKF2 hash of the password */ private static byte[] pbkdf2(Provider provider, char[] password, byte[] salt, int iterations, int outBytes) throws NoSuchAlgorithmException, InvalidKeySpecException { StrictMode.noteSlowCall("pbkdf2 is a very expensive call and should not be done on the main thread"); PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, outBytes * 8); SecretKeyFactory skf = provider != null ? SecretKeyFactory.getInstance(PBKDF2_ALGORITHM, provider) : SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); return skf.generateSecret(spec).getEncoded(); } }