bcrypt Java Library

repository·main·Indexed 20 days ago

https://github.com/patrickfav/bcrypt

A modernized and refactored implementation of the OpenBSD Blowfish password hashing algorithm (bcrypt) for Java. It supports multiple bcrypt versions ($2a$, $2b$, $2x$, $2y$), cost factors up to 31, and provides security-sensitive APIs using byte and char arrays to allow manual memory clearing. The library is compatible with Java 7 and includes a CLI tool for hashing and verifying passwords.

Tokens
3.8K
Snippets
16
Records
21
Agent score
69%

What's inside bcrypt

  1. Key enhancements in this bcrypt implementation

    main

    This library is a refactored and optimized version of jBcrypt with several key improvements for developers:

    • Expanded Version Support: Supports $2a$, $2b$, $2x$, and $2y$ versions, including support for custom versions.
    • Higher Cost Factor: Allows a cost factor up to 31 (whereas jBcrypt is limited to 30).
    • Improved Security/Memory Management: Uses only byte[] and char[] arrays, which can be wiped from memory after use to reduce the window for memory-scraping attacks.
    • Customization: Provides APIs to use your own salt or a custom SecureRandom instance for salt generation.
    • Long Password Handling: Customizable handling for passwords exceeding the standard 72-byte limit.
    • Raw Hash Access: Provides easy methods to retrieve the raw hash components.
  2. How to specify Bcrypt versions

    main

    The library supports multiple Bcrypt versions (e.g., $2a$, $2b$, $2y$). Use BCrypt.with(BCrypt.Version.VERSION_...) to specify a version. This is useful for compatibility with other implementations, such as the PHP implementation which uses $2y$.

    // Using version 2y (common in PHP)
    char[] bcryptChars = BCrypt.with(BCrypt.Version.VERSION_2Y).hashToChar(6, password.toCharArray());
    
    // Using version 2b
    char[] bcryptChars = BCrypt.with(BCrypt.Version.VERSION_2B).hashToChar(6, password.toCharArray());
  3. Understand the bcrypt Modular Crypt Format (MCF)

    main

    Bcrypt outputs hashes in the Modular Crypt Format (MCF), which includes the version, cost factor, salt, and the hash itself in a single string. This format is designed for easy password storage.

    Format Structure: ${identifier}${cost-factor}${16-bytes-salt-radix64}{23-bytes-hash-radix64}

    Example Hash: $2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V.

    Breakdown of the example:

    • Version ($2a$): The identifier. $2a$ is the most common default. Other versions include $2b$, $2x$, and $2y$.
    • Cost Factor (08): The logarithmic work factor (e.g., 08 means $2^8$ iterations).
    • Salt (cfcvVd2aQ8CMvoMpP2EBfe): 16 bytes encoded using a Radix64 dialect.
    • Hash (odLEkkFJ9umNEfPD18.hUF62qqlC/V.): The resulting 23-byte hash, also encoded in Radix64.
  4. How to choose a bcrypt cost factor

    main

    The cost factor (work factor) determines how many iterations the hashing algorithm performs. Because bcrypt is a 'salted-and-slow' hashing function, you should set the cost factor as high as your server can tolerate without impacting user experience or system availability.

    Best Practices:

    • Aim for the slowest performance that is still acceptable for your specific use case (e.g., 250ms or 3s).
    • Benchmark your server and your users' typical devices (like mobile phones) to find a balance.
    • Note: You cannot increase the cost factor of an existing hash without knowing the original password. To upgrade security, you must re-hash the password when a user next logs in.
  5. Use byte[] or char[] for security-sensitive operations

    main

    To prevent sensitive data from lingering in memory, prefer byte[] or char[] over String. Primitive arrays can be overwritten (zeroed out) after use, whereas String objects are immutable and cannot be wiped. The encoding defaults to UTF-8.

    // Using byte[]
    byte[] bcryptHashBytes = BCrypt.withDefaults().hash(6, password.getBytes(StandardCharsets.UTF_8));
    BCrypt.Result result = BCrypt.verifyer().verify(password.getBytes(StandardCharsets.UTF_8), bcryptHashBytes);
    
    // Using char[]
    char[] bcryptChars = BCrypt.withDefaults().hashToChar(12, password.toCharArray());
    BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), bcryptChars);
  6. Quickstart: Hash and Verify a password

    main

    For most use cases, use the default API which handles versioning and salt generation automatically. Use toCharArray() with passwords to allow for manual memory clearing, as String objects are immutable and cannot be wiped.

    String password = "1234";
    // Hash the password with a cost factor of 12
    String bcryptHashString = BCrypt.withDefaults().hashToString(12, password.toCharArray());
    
    // Verify the password against the hash
    BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), bcryptHashString);
    // result.verified == true
  7. Run JMH benchmarks for the library

    main

    To perform high-fidelity performance testing using the Java Microbenchmark Harness (JMH), follow these steps:

    1. Build the project using Maven:

      ./mvnw clean install

      (Note: You can disable jar signing by setting the <project.skipJarSign> property if needed.)

    2. Execute the benchmark using the generated JAR:

      java -jar modules/benchmark-jmh/target/benchmark-jmh-x.y.z-full.jar
    ./mvnw clean install
    java -jar modules/benchmark-jmh/target/benchmark-jmh-x.y.z-full.jar
  8. Build the project with Maven

    main

    To create a JAR file that includes all dependencies, use the Maven wrapper provided in the repository.

    Requirements:

    • JDK 11 is required to build (Note: not yet JDK 17 compatible).
    • Java 7 source compatibility.
    ./mvnw clean install
  9. Use the local Checkstyle configuration

    main

    The project uses a centralized checkstyle configuration. After running ./mvnw install, the configuration file is copied to your target folder. If you are using an IDE plugin for code style enforcement, point it to this file.

    Path: target/checkstyle-checker.xml

  10. Install the Bcrypt Java Library

    main

    The Bcrypt library is available on Maven Central. You can add it to your project using Maven or Gradle. It is compiled with target Java 7, making it compatible with most Android versions and standard Java applications.

    <!-- Maven -->
    <dependency>
        <groupId>at.favre.lib</groupId>
        <artifactId>bcrypt</artifactId>
        <version>{latest-version}</version>
    </dependency>
    
    <!-- Gradle -->
    implementation("at.favre.lib:bcrypt:{latest-version}")
  11. Configure Jar Signing during build

    main

    If you need to jar sign the output, place a keystore.jks file in the root folder and set the following environment variables:

    • OPENSOURCE_PROJECTS_KS_PW
    • OPENSOURCE_PROJECTS_KEY_PW

    To skip the jar signing process, set the project.skipJarSign property to true in your pom.xml.

    <project.skipJarSign>true</project.skipJarSign>
  12. Handle overlong passwords (> 72 bytes)

    main

    Bcrypt has a 72-byte limit. By default, hash() throws an exception if the password exceeds this. You can use LongPasswordStrategies to change this behavior. Note that using these strategies creates a custom flavor of Bcrypt that may not be compatible with other implementations.

    Available strategies:

    • LongPasswordStrategies.truncate(Version): Truncates the password.
    • LongPasswordStrategies.hashSha512(Version): Hashes the password with SHA-512 first to allow all bytes to be honored.
    • LongPasswordStrategies.none: Standard Bcrypt behavior (passes raw data to the primitive, which ignores anything beyond 72 bytes).
    // Truncate passwords longer than 72 bytes
    BCrypt.with(LongPasswordStrategies.truncate(Version.VERSION_2A)).hash(6, pw);
    
    // Use SHA-512 to handle long passwords
    BCrypt.with(LongPasswordStrategies.hashSha512(Version.VERSION_2A)).hash(6, pw);
    
    // IMPORTANT: Use the same strategy during verification
    BCrypt.verifyer(LongPasswordStrategies.truncate(Version.VERSION_2A)).verify(pw, hash);