linkBCrypt based Key Derivation Function (BKDF)

Download Build Status Javadocs Coverage Status Maintainability

The aim of this project is to improve on the cryptographic primitive BCrypt with providing well defined modes of operation which includes:

All this is achieved by only adding HKDF as additional building block.

The code is compiled with target Java 7 to be compatible with most Android versions as well as normal Java applications.

Note, that this project is ongoing research and may not be ready for prime-time yet as it requires more feedback from the cryptographic community.

linkQuickstart

Add dependency to your pom.xml (check latest release):

1<dependency>

2 <groupId>at.favre.lib</groupId>

3 <artifactId>bkdf</artifactId>

4 <version>{latest-version}</version>

5</dependency>

A very simple example using the password hasher:

1PasswordHasher hasher = BKDF.createPasswordHasher();

2

3char[] pw = "secret".toCharArray();

4int costFactor = 6; // same as with bcrypt 4-31 doubling the iterations every increase

5

6//returns base64 url-safe encoded string

7String hash = hasher.hash(pw, costFactor);

8

9PasswordHashVerifier verifier = BKDF.createPasswordHashVerifier();

10boolean verified = verifier.verify(pw, hash);

linkFull Example

The BKDF protocol supports 3 use-cases:

linkPassword Hash

A password hash is used to generate a hash from a user-password which can't easily be used to calculate the used password without brute-forcing. An important feature of password hashes are, that they are slow, so it makes it harder (or infeasible) for an attacker to brute force. This property is also called "key-stretching". Well known password hashes are PBKDF2, scrypt and Argon2.

1// provide different version of hash config and provide own impl of secure random for salt gen

2PasswordHasher hasher = BKDF.createPasswordHasher(Version.HKDF_HMAC512, new SecureRandom());

3char[] pw = "secret".toCharArray();

4HashData hashData = hasher.hashRaw("secret".toCharArray(), 4);

5

6// get the raw, non-encoded hash message

7byte[] hashMsgAsBlob = hashData.getAsBlobMessageFormat();

8

9// get the base64 url-safe encoded string

10String hashAsBase64 = hashData.getAsEncodedMessageFormat();

11

12PasswordHashVerifier verifier = BKDF.createPasswordHashVerifier();

13boolean verified = verifier.verify(pw, hashData);

linkPassword Hash Upgrade

BCrypt does not support upgrading the strength of the password hash without the user password. Having legacy password hashes in a DB, the need may arise to improve them, because CPU performance increased of the last couple of years. With this feature a password can be upgraded offline by basically chaining multiple hashes together.

This mode will chain a specific new hash with given cost factor:

1char[] pw = "secret".toCharArray();

2

3// hash with cost factor 5

4String hash = BKDF.createPasswordHasher().hash(pw, 5);

5PasswordHashUpgrader upgrader = new PasswordHashUpgrader.Default(new SecureRandom());

6

7// upgrade hash with an additional cost factor (ie. now needs to calculate 5 + 6 = 32 + 64 = 96 iterations

8CompoundHashData compoundHashData = upgrader.upgradePasswordHashWith(6, hash);

9

10// create base64 url-safe encoded msg and verify

11boolean verified = BKDF.createPasswordHashVerifier().verify(pw, compoundHashData.getAsEncodedMessageFormat());

Another mode will take a target cost factor and calculate the required hashes to achieve it

1char[] pw = "secret".toCharArray();

2

3// hash with cost factor 5

4String hash = BKDF.createPasswordHasher().hash(pw, 5);

5PasswordHashUpgrader upgrader = new PasswordHashUpgrader.Default(new SecureRandom());

6

7// upgrade to have exactly cost factor 8 (aka 2^8 = 256 iterations)

8CompoundHashData compoundHashData = upgrader.upgradePasswordHashTo(8, hash);

9

10// create base64 url-safe encoded msg and verify

11boolean verified = BKDF.createPasswordHashVerifier().verify(pw, compoundHashData.getAsEncodedMessageFormat());

linkKey Derivation Function

It might be useful to have a primitive that generates high-quality key material for e.g. symmetric encryption and not password hashes.

This example creates an AES key from a user password:

1char[] pw = "secret".toCharArray();

2byte[] salt = Bytes.random(16).array();

3int costFactor = 5;

4

5KeyDerivationFunction kdf = new KeyDerivationFunction.Default(Version.HKDF_HMAC512);

6byte[] aesKey = kdf.derive(salt, pw, costFactor, Bytes.from("aes-key").array(), 16);

7

8SecretKey aesSecretKey = new SecretKeySpec(aesKey, "AES");

To generate multiple keys, use the following example, so you are not required to generate the internal bcrypt hash for every key:

1// a entropy source used in your current protocol

2byte[] ikm = Bytes.random(12).array();

3byte[] salt = Bytes.random(16).array();

4int costFactor = 5;

5

6KeyDerivationFunction kdf = new KeyDerivationFunction.Default(Version.HKDF_HMAC512);

7List<KeyDerivationFunction.KdfConfig> config = Arrays.asList(

8 new KeyDerivationFunction.KdfConfig(Bytes.from("aes-key").array(), 16),

9 new KeyDerivationFunction.KdfConfig(Bytes.from("mac-key").array(), 32)

10);

11List<byte[]> keys = kdf.deriveMulti(salt, ikm, costFactor, config);

12

13SecretKey aesSecretKey = new SecretKeySpec(keys.get(0), "AES");

14SecretKey macSecretKey = new SecretKeySpec(keys.get(1), "HmacSHA512");

linkDescription

In the following the details of each of the protocols are discussed.

In the example the following functions are used:

1bcrypt(cost_factor {4-31}, user_pw, [16-byte-salt])

2hkdf_extract(salt, input_key_material)

3hkdf_expand(output_key_material, info_param, out_length_byte)

The HMAC used by HKDF is defined by the used hash version, currently only HMAC-SHA512 is supported.

linkPassword Hash Protocol

linkStep 1: Extract User Password

First create uniformly distributed entropy byte string with through HKDF "extract" from user password. Convert the user password to a byte array using UTF-8 encoding. Use an empty byte array as salt with the length of the underyling hash output length (aka HMAC-SHA512 == 64 byte)

1utf8PwBytes = user_password.getUtf8Bytes()

2extractedPw = hkdf_extract(empty_byte_array, utf8PwBytes)

linkStep 2: Stretch with BCrypt

tbd.

linkPassword Upgrade Protocol

tbd.

linkKDF Protocol

tbd.

linkDownload

The artifacts are deployed to jcenter and Maven Central.

linkMaven

Add dependency to your pom.xml:

1<dependency>

2 <groupId>at.favre.lib</groupId>

3 <artifactId>bkdf</artifactId>

4 <version>{latest-version}</version>

5</dependency>

linkGradle

Add to your build.gradle module dependencies:

1compile group: 'at.favre.lib', name: 'bkdf', version: '{latest-version}'

linkLocal Jar

Grab jar from latest release.

linkSecurity Relevant Information

linkOWASP Dependency Check

This project uses the OWASP Dependency-Check which is a utility that identifies project dependencies and checks if there are any known, publicly disclosed, vulnerabilities against a NIST database. The build will fail if any issue is found.

linkDigital Signatures

linkSigned Jar

The provided JARs in the Github release page are signed with my private key:

1CN=Patrick Favre-Bulle, OU=Private, O=PF Github Open Source, L=Vienna, ST=Vienna, C=AT

2Validity: Thu Sep 07 16:40:57 SGT 2017 to: Fri Feb 10 16:40:57 SGT 2034

3SHA1: 06:DE:F2:C5:F7:BC:0C:11:ED:35:E2:0F:B1:9F:78:99:0F:BE:43:C4

4SHA256: 2B:65:33:B0:1C:0D:2A:69:4E:2D:53:8F:29:D5:6C:D6:87:AF:06:42:1F:1A:EE:B3:3C:E0:6D:0B:65:A1:AA:88

Use the jarsigner tool (found in your $JAVA_HOME/bin folder) folder to verify.

linkSigned Commits

All tags and commits by me are signed with git with my private key:

1GPG key ID: 4FDF85343912A3AB

2Fingerprint: 2FB392FB05158589B767960C4FDF85343912A3AB

linkBuild

linkJar Sign

If you want to jar sign you need to provide a file keystore.jks in the root folder with the correct credentials set in environment variables ( OPENSOURCE_PROJECTS_KS_PW and OPENSOURCE_PROJECTS_KEY_PW); alias is set as pfopensource.

If you want to skip jar signing just change the skip configuration in the pom.xml jar sign plugin to true:

1true

linkBuild with Maven

Use the Maven wrapper to create a jar including all dependencies

1mvnw clean install

linkCheckstyle Config File

This project uses my common-parent which centralized a lot of the plugin versions aswell as providing the checkstyle config rules. Specifically they are maintained in checkstyle-config. Locally the files will be copied after you mvnw install into your target folder and is called target/checkstyle-checker.xml. So if you use a plugin for your IDE, use this file as your local configuration.

linkTech Stack

linkLicense

Copyright 2018 Patrick Favre-Bulle

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

1http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Star
BCrypt based Key Derivation Function (BKDF)QuickstartFull ExamplePassword HashPassword Hash UpgradeKey Derivation FunctionDescriptionPassword Hash ProtocolStep 1: Extract User PasswordStep 2: Stretch with BCryptPassword Upgrade ProtocolKDF ProtocolDownloadMavenGradleLocal JarSecurity Relevant InformationOWASP Dependency CheckDigital SignaturesSigned JarSigned CommitsBuildJar SignBuild with MavenCheckstyle Config FileTech StackRelated LibrariesLicense

Home

OpenSourcechevron_right



Patrick Favre-Bulle 2020