We create a random salt by first creating a instance of SecureRandom
to ensure that our bytes are truly random for a secuirty context. We then create a empty byte array and pass this into SecureRandom::nextBytes(byte[] bytes)
function to fill the byte array with random bytes.
SecureRandom secureRandom = new SecureRandom();
public byte[] generateSalt()
{
byte[] salt = new byte[4];
secureRandom.nextBytes(salt);
return salt;
}
We use Java's SecretKeyFactory
to help with hashing. We first create an instance by giving it our HASH_FUNCTION
. WE then specify our PBEKeySpec
by passing in our: password
, salt
, ITERATIONS
, and KEY_BIT_LENGTH
. We then pass this into the SecretKeyFactory::generateSecret(PBEKeySpec keySpec)
function giving us a a SecretKey
instance. This contains our salt+hashed password that is safe to store in our database. Use the SecretKey::getEncoded()
function to get our byte array and encode it, as well as our salt, into a base64 String using Java's Base64
.
Note: The HASH_FUNCTION
, ITERATIONS
, and KEY_BIT_LENGTH
are constant values provided to you.
SecretKeyFactory skf = SecretKeyFactory.getInstance(HASH_FUNCTION);
char[] password = "SuperSecretPassword".toCharArray();
byte[] salt = generateSalt(); // From the example above
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_BIT_LENGTH);
SecretKey key = skf.generateSecret(spec);
byte[] encodedPassword = key.getEncoded();
String base64EncodedHashedPassword = Base64.getEncoder().encodeToString(encodedPassword);
String base64EncodedHashedSalt = Base64.getEncoder().encodeToString(salt);
To verify a user, repeat the steps above with the users Stored Salt and their Given Password. If the resulting Hashed Password equals the stored Hashed Password then they have given a valid password. If not then their password is not correct and return the appropriate response.
A JSON Web Token is created by first creating a JWTClaimsSet
and JWSHeader
combining them into SignedJWT
, and then signing
the resulting SignedJWT
A claim set is the claims we want to declare about the given user. For this project we want to make claims about the users: email, id, roles, as well as set the issueTime and expirationTime.
JWTClaimsSet claimsSet =
new JWTClaimsSet.Builder()
.subject(email)
.expirationTime(expireTime)
.claim(JWTManager.CLAIM_ID, userId) // we set claims like values in a map
.claim(JWTManager.CLAIM_ROLES, roles)
.issueTime(Date.from(Instant.now()))
.build();
When we create out header we will be using the constants found in the provided JWTManager
class as well as attaching our EcKey
's id to it.
JWSHeader header =
new JWSHeader.Builder(JWTManager.JWS_ALGORITHM)
.keyID(manager.getEcKey().getKeyID())
.type(JWTManager.JWS_TYPE)
.build();
Once we have our JWSHeader
and JWTClaimsSet
we can create a SignedJWT
and sign it using the provided JWTManager
.
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
signedJWT.sign(manager.getSigner());
We can verify if a SignedJWT
was created by us and that it has not been modified by using the provided JWTManager
.
try {
// Rebuild the SignedJWT from the serialized String
SignedJWT signedJWT = SignedJWT.parse(serialized);
signedJWT.verify(manager.getVerifier());
manager.getJwtProcessor().process(signedJWT, null);
// Do logic to check if expired manually
signedJWT.getJWTClaimsSet().getExpirationTime();
} catch (IllegalStateException | JOSEException | BadJOSEException | ParseException e) {
LOG.error("This is not a real token, DO NOT TRUST");
e.printStackTrace();
// If the verify function throws an error that we know the
// token can not be trusted and the request should not be continued
}
In Java the best way to deal with time is using the Instant
class. This class has some convient functions for creating the current time, comparing times, and adding times.
Getting a new instant of the current time:
Instant currentTime = Instant.now;
Comparing two times:
Instant expireTime = getExpireTime();
if (Instant.now().isAfter(expireTime)) {
// The current time is AFTER the expire time
}
In Java we use the Duration
class to keep a specific "Time Duration".
You can create a Duration
by using the static builders in Duration
Duration expire = Duration.ofHours(2); // Much more readable then using EPOCH
Adding a Duration to Instant
Instant currentTime = Instant.now();
Duration expireDuration = Duration.ofHours(2);
Instant expireTime = currentTime.plus(expireDuration);