Back to Repositories

Validating BCrypt Password Hashing Implementation in litemall

This test suite validates the BCrypt password hashing implementation in the litemall core module. It comprehensively tests salt generation, password hashing, and verification functionality using JUnit and PowerMock frameworks.

Test Coverage Overview

The test suite provides extensive coverage of BCrypt password hashing operations:

  • Salt validation and generation with different rounds
  • Password hashing with various salt configurations
  • Password verification and comparison
  • Edge cases including null inputs, invalid salt formats, and boundary conditions
  • Mock testing of secure random number generation

Implementation Analysis

The testing approach employs JUnit 4 with PowerMock for advanced mocking capabilities. It uses a systematic pattern of negative testing with ExpectedException rule to validate error conditions and proper exception handling. The tests verify both valid and invalid scenarios for salt generation and password hashing operations.

Key implementation patterns include:
  • Annotation-based test configuration with @PrepareForTest
  • Structured error case validation
  • Comprehensive boundary testing

Technical Details

Testing infrastructure includes:
  • JUnit 4 test framework
  • PowerMock for SecureRandom mocking
  • ExpectedException rule for exception testing
  • Custom salt validation patterns
  • BCrypt implementation with configurable work factors

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive error condition coverage
  • Isolation of secure random number generation
  • Systematic validation of input parameters
  • Clear test method naming conventions
  • Proper separation of positive and negative test cases
  • Effective use of test frameworks and annotations

linlinjava/litemall

litemall-core/src/test/java/org/linlinjava/litemall/core/util/bcrypt/BCryptTest.java

            
package org.linlinjava.litemall.core.util.bcrypt;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.security.SecureRandom;

@RunWith(PowerMockRunner.class)
public class BCryptTest {

    @Rule
    public final ExpectedException thrown = ExpectedException.none();

    @Test
    public void testHashpwSaltIsNull() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", null);
    }

    @Test
    public void testHashpwSaltTooShort() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "foo");
    }

    @Test
    public void testHashpwInvalidSaltVersion() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "+2a$10$.....................");
    }

    @Test
    public void testHashpwInvalidSaltVersion2() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$1a$10$.....................");
    }

    @Test
    public void testHashpwInvalidSaltRevision() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$2+$10$.....................");
    }

    @Test
    public void testHashpwInvalidSaltRevision2() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$2a+10$.....................");
    }

    @Test
    public void testHashpwSaltTooShort2() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$2a$10+.....................");
    }

    @Test
    public void testHashpwMissingSaltRounds() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$2$a10$.....................");
    }

    @Test
    public void testHashpwTooLittleRounds() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$2a$03$......................");
    }

    @Test
    public void testHashpwTooManyRounds() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.hashpw("foo", "$2a$32$......................");
    }

    @Test
    public void testHashpw() {
        Assert.assertEquals(
                "$2a$10$......................0li5vIK0lccG/IXHAOP2wBncDW/oa2u",
                BCrypt.hashpw("foo", "$2a$10$......................"));

        Assert.assertEquals(
                "$2$09$......................GlnmyWmDnFB.MnSSUnFsiPvHsC2KPBm",
                BCrypt.hashpw("foo", "$2$09$......................"));
    }

    @PrepareForTest({BCrypt.class, SecureRandom.class})
    @Test
    public void testGensalt() throws Exception {
        PowerMockito.whenNew(SecureRandom.class).withNoArguments()
                .thenReturn(PowerMockito.mock(SecureRandom.class));
        Assert.assertEquals("$2a$10$......................", BCrypt.gensalt());
        Assert.assertEquals("$2a$09$......................", BCrypt.gensalt(9));
    }

    @Test
    public void testGensaltTooLittleRounds() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.gensalt(3);
    }

    @Test
    public void testGensaltTooManyRounds() throws IllegalArgumentException {
        thrown.expect(IllegalArgumentException.class);
        BCrypt.gensalt(32);
    }

    @Test
    public void testCheckpw() {
        Assert.assertFalse(BCrypt.checkpw("foo", "$2a$10$......................"));

        final String hashed = BCrypt.hashpw("foo", BCrypt.gensalt());
        Assert.assertTrue(BCrypt.checkpw("foo", hashed));
        Assert.assertFalse(BCrypt.checkpw("bar", hashed));
    }
}