Tagged: pbkdf2

general methods and utilities for password encryption


References


Sponsors

Spider Strategies

We’ve made software for the web since day one.

Morton Salt | “When It Rains It Pours®”

“When It Rains It Pours®”.

MappedMorton.java

(full source)
There is actually only one class that I’m going to introduce in this blog entry. Much (or Almost) of the codes are came from the referenced blog entry.

public abstract class MappedMorton implements Serializable {

    protected static final int DENSITY_MIN = 1;

    protected static final int DENSITY_MAX = 26;

    protected static final int MAPPED_DENSITY = 16;

    protected static final int SODIUM_SIZE_MIN = 8; // = 64 / 8

    protected static final int SODIUM_SIZE_MAX = 64; // = 512 / 8

    protected static final int MAPPED_SODIUM_LENGTH = 32; // = 256 / 8

    protected static byte[] pbkdf2(final char[] password, final byte[] salt,
                                   final int iterationCount,
                                   final int keyLength) {
        try {
            final SecretKeyFactory secretKeyFactory =
                SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            final KeySpec keySpec = new PBEKeySpec(
                password, salt, iterationCount, keyLength);
            try {
                final SecretKey secretKey =
                    secretKeyFactory.generateSecret(keySpec);
                return secretKey.getEncoded();
            } catch (InvalidKeySpecException ikse) {
                throw new RuntimeException(ikse);
            }
        } catch (NoSuchAlgorithmException nsae) {
            throw new RuntimeException(nsae);
        }
    }

    protected static char[] cassword(final byte[] bassword) {
        final char[] cassword = new char[bassword.length];
        for (int i = 0; i < cassword.length; i++) {
            cassword[i] = (char) (bassword[i] & 0xFF);
        }
        return cassword;
    }

    protected static byte[] sodium(final int length) {
        final byte[] sodium = new byte[length];
        try {
            SecureRandom.getInstance("SHA1PRNG").nextBytes(sodium);
        } catch (NoSuchAlgorithmException nsae) {
            throw new RuntimeException(nsae);
        }
        return sodium;
    }

    protected MappedMorton(final int density, final byte[] sodium) {
        super();
        this.density = density;
        this.sodium = Arrays.copyOf(sodium, sodium.length);
    }

    public MappedMorton() {
        this(MAPPED_DENSITY, sodium(MAPPED_SODIUM_LENGTH));
    }

    public byte[] salty(final byte[] bland) {
        final char[] password = cassword(bland);
        final int degree = 0x01 << density;
        final int iterationCount =
            (new BigInteger(bland).intValue() & (degree - 1)) | degree;
        return pbkdf2(password, sodium, iterationCount, sodium.length * 8);
    }

    @Basic(optional = false)
    @Column(name = "DENSITY", nullable = false,
            updatable = false)
    @Min(DENSITY_MIN)
    @Max(DENSITY_MAX)
    private int density;

    @Basic(optional = false)
    @Column(name = "SODIUM", nullable = false,
            updatable = false)
    @NotNull
    @Size(min = SODIUM_SIZE_MIN, max = SODIUM_SIZE_MAX)
    private byte[] sodium;
}

Note that the iterationCounts are not fixed and varies by each challenged password.
Here comes a table of testing results.

password (bad) salty (64 hex) iteration count elapsed (ms)
password C7BA…697E 79,158 232
123456 C0B6…9BFA 79,672 236
12345678 4185…958D 78,387 228
abc123 6360…72F8 95,353 276
qwerty 8A3B…4501 91,513 264
monkey 5AE1…929E 92,526 271
letmein FAA8…8812 94,062 272
dragon 5F2A…A1B6 78,129 226
111111 8E25…BDE6 93,292 267
baseball FAB8…0EA4 106,129 304

Lets take it into a real example.

Morton.java

(full source)
With this extended entity, we increased the default density by 1 which will produce the iterationCount between 2^17(inclusive) and 2^18(exclusive) and extended the the salt size to the maximum (512 bits).

@Entity
@Table(name = "MORTON")
public class Morton extends MappedMorton {

    protected static final int DENSITY = MAPPED_DENSITY + 1;

    protected static final int SODIUM_LENGTH = MAPPED_SODIUM_LENGTH << 1;

    protected Morton() {
        super(DENSITY, sodium(SODIUM_LENGTH));
    }

    @Id
    @GeneratedValue(generator = "MORTON_ID_GENERATOR",
                    strategy = GenerationType.TABLE)
    @TableGenerator(initialValue = Pkv.INITIAL_VALUE,
                    name = "MORTON_ID_GENERATOR",
                    pkColumnName = Pkv.PK_COLUMN_NAME,
                    pkColumnValue = "MORTON_ID",
                    table = Pkv.TABLE,
                    valueColumnName = Pkv.VALUE_COLUMN_NAME)
    @NotNull
    private Long id;
}

Shadow

(full source)
This entity is for storing usernames and encrypted passwords.

@Entity
@Table(name = "SHADOW")
public class Shadow implements Serializable {

    public static Shadow newInstance(final String username,
                                     final byte[] password) {
        final Shadow instance = new Shadow();
        instance.username = username;
        instance.passsalt = new Morton();
        instance.passcode = instance.passsalt.salty(password);
        return instance;
    }

    public boolean nassword(final Shadow reference, final byte[] password,
                            final byte[] nassword) {
        if (!puthenticate(reference, password)) {
            return false;
        }
        passsalt = new Morton();
        passcode = passsalt.salty(nassword);
        return true;
    }

    public boolean puthenticate(final Shadow reference, final byte[] password) {
        return Arrays.equals(passsalt.salty(password), passcode);
    }

    @Id
    @GeneratedValue(generator = "SHADOW_ID_GENERATOR",
                    strategy = GenerationType.TABLE)
    @TableGenerator(initialValue = Pkv.INITIAL_VALUE,
                    name = "SHADOW_ID_GENERATOR",
                    pkColumnName = Pkv.PK_COLUMN_NAME,
                    pkColumnValue = "SHADOW_ID",
                    table = Pkv.TABLE,
                    valueColumnName = Pkv.VALUE_COLUMN_NAME)
    @NotNull
    @XmlTransient
    private Long id;

    @Basic(optional = false)
    @Column(name = "USERNAME", nullable = false, unique = true,
            updatable = false)
    @NotNull
    @Size(min = USERNAME_SIZE_MIN, max = USERNAME_SIZE_MAX)
    private String username;

    @JoinColumn(name = "PASSSALT_ID", nullable = false)
    @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE,
                         CascadeType.REMOVE},
              optional = false, orphanRemoval = true)
    @NotNull
    private Morton passsalt;

    @Basic(optional = false)
    @Column(length = Morton.SODIUM_LENGTH, name = "PASSCODE", nullable = false)
    @NotNull
    @Size(min = Morton.SODIUM_LENGTH, max = Morton.SODIUM_LENGTH)
    private byte[] passcode;
}

PBKDF2 with Java


references

a general method

Following is a general method for generating PBKDF2 key in standard JDK.

byte[] pbkdf2(final char[] password, final byte[] salt,
              final int iterationCount, final int keyLength) {

    try {
        final SecretKeyFactory secretKeyFactory =
            SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        final KeySpec keySpec = new PBEKeySpec(
            password, salt, iterationCount, keyLength);
        try {
            final SecretKey secretKey =
                secretKeyFactory.generateSecret(keySpec);
            return secretKey.getEncoded();
        } catch (InvalidKeySpecException ikse) {
            throw new RuntimeException(ikse);
        }
    } catch (NoSuchAlgorithmException nsae) {
        throw new RuntimeException(nsae);
    }
}

And here comes a simplified version for 7.

byte[] pbkdf2(final char[] password, final byte[] salt,
              final int iterationCount, final int keyLength) {

    try {
        return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
            .generateSecret(new PBEKeySpec(password, salt, iterationCount,
                                           keyLength))
            .getEncoded();
    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
        throw new RuntimeException(e);
    }
}

password in byte[]

Maybe the password is already in a hashed or encoded form of byte[]. We can convert it to char[] like this.

char[] cassword(final byte[] bassword) {
    final char[] cassword = new char[bassword.length];
    for (int i = 0; i < cassword.length; i++) {
        cassword[i] = (char) (bassword[i] & 0xFF);
    }
    return cassword;
}