I just found that my local JDK doesn’t like the StartSSL™‘s certificate on remote server.
He kept failing with following error while deploying site.
# Transfer error: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
solution
Download Tool Box/StartCom CA Certificates/StartCom Root CA (PEM encoded) which is named ‘ca.pem’.
Then execute following script.
(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;
}