Skip to content

Example With Pbkdf2 In Spring Boot#

Example With Pbkdf2 In Spring Boot#

  • In this example we will use Password-Based Key Derivation Function 2 (Pbkdf2) which is a pretty easy slow-hashing function that performs an HMAC(Hashed Message Authentication Code) as many times as specified by an iterations argument.
  • The Pbkdf2 has the brilliant feature of having a configurable strength. This means that as computers increase in strength, we can slow down the algorithm by changing the inputs.

Controller#

  • Let's create an controller with some apis as below:
Pbkdf2Controller.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.springboot.security.hash.app.controller;

import com.springboot.security.hash.app.model.DataRequest;
import com.springboot.security.hash.app.model.MatchDataRequest;
import com.springboot.security.hash.app.service.Pbkdf2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Pbkdf2Controller {

    @Autowired
    private Pbkdf2Service pbkdf2Service;

    @RequestMapping(method = RequestMethod.POST, path = "v1/cipher/hash/pbkdf2", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> hashSHA256(@RequestBody DataRequest inputData) {
        return new ResponseEntity<>(this.pbkdf2Service.hashPbkdf2(inputData.getData()), HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.POST, path = "v1/cipher/hash/pbkdf2/check", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Boolean> checkMatchSha256(@RequestBody MatchDataRequest inputData) {
        return new ResponseEntity<>(this.pbkdf2Service.isPbkdf2Match(inputData.getRawData(), inputData.getHashedData()), HttpStatus.OK);
    }

}
  • We will need to create 2 simple models for request body as below. One is used for hashing data and the other one is used for checking raw data and hashed data.
DataRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.springboot.security.hash.app.model;

import org.springframework.lang.NonNull;

public class DataRequest {

    private String data;

    @NonNull
    public String getData() {
        return data;
    }

    public void setData(@NonNull String data) {
        this.data = data;
    }

}
MatchDataRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.springboot.security.hash.app.model;

public class MatchDataRequest {

    private String rawData;
    private String hashedData;

    public String getRawData() {
        return rawData;
    }

    public void setRawData(String rawData) {
        this.rawData = rawData;
    }

    public String getHashedData() {
        return hashedData;
    }

    public void setHashedData(String hashedData) {
        this.hashedData = hashedData;
    }
}
  • Then we also need to create a model that loads environment variables into a spring bean using @ConfigurationProperties as below.
HashConfigProperties.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.springboot.security.hash.app.model;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "hash")
public class HashConfigProperties {

    private Pbkdf2 pbkdf2;

    public Pbkdf2 getPbkdf2() {
        return pbkdf2;
    }

    public void setPbkdf2(Pbkdf2 pbkdf2) {
        this.pbkdf2 = pbkdf2;
    }
}
Pbkdf2.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.springboot.security.hash.app.model;


public class Pbkdf2 {

    private String salt;
    private Integer iterations;
    private Integer hashWidth;

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public Integer getIterations() {
        return iterations;
    }

    public void setIterations(Integer iterations) {
        this.iterations = iterations;
    }

    public Integer getHashWidth() {
        return hashWidth;
    }

    public void setHashWidth(Integer hashWidth) {
        this.hashWidth = hashWidth;
    }
}

Service#

  • Now let's create a service with name Pbkdf2Service with the code as below:
Pbkdf2Service.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.springboot.security.hash.app.service;

import com.springboot.security.hash.app.model.HashConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

@Service
public class Pbkdf2Service {

    private static final String HASH_ALGORITHM_PBKDF2_512 = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512.name();

    @Autowired
    private HashConfigProperties hashConfigProperties;

    public String hashPbkdf2(String data) {
        try {
            byte[] hash = this.hash(data);
            return DatatypeConverter.printHexBinary(hash);
        } catch (Exception ex) {
            throw new RuntimeException("Can not hash Data", ex);
        }
    }

    public boolean isPbkdf2Match(String data, String hashedData) {
        byte[] digested = DatatypeConverter.parseHexBinary(hashedData);
        byte[] reHashData = this.hash(data);
        return MessageDigest.isEqual(digested, reHashData);
    }

    private byte[] hash(String data) {
        try {
            PBEKeySpec pbeKeySpec = new PBEKeySpec(data.toCharArray(),
                hashConfigProperties.getPbkdf2().getSalt().getBytes(StandardCharsets.UTF_8),
                hashConfigProperties.getPbkdf2().getIterations(),
                hashConfigProperties.getPbkdf2().getHashWidth());
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(HASH_ALGORITHM_PBKDF2_512);
            return secretKeyFactory.generateSecret(pbeKeySpec).getEncoded();
        } catch (Exception ex) {
            throw new RuntimeException("Can not hash Data", ex);
        }
    }

    public enum SecretKeyFactoryAlgorithm {

        PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA256, PBKDF2WithHmacSHA512

    }

}
  • In Spring Boot we don't need to add more any dependency to do the Hashing with Pbkdf2, we will use SecretKeyFactory with hash algorithm is PBKDF2WithHmacSHA512 that Spring Boot provided.

There are 3 hash algorithms that we can use for SecretKeyFactory with Pbkdf2: > - PBKDF2WithHmacSHA1 > - PBKDF2WithHmacSHA256 > - PBKDF2WithHmacSHA512

  • After you have initialized the SecretKeyFactory, Then you need to create a PBEKeySpec with 4 parameters as below:

    • Param 1: the byte array of your data.
    • Param 2: the byte array of salt
    • Param 3: the number of iterations (185000 or more)
    • Param 4: the number of hash length (128, 256, 1024)
  • The salt, iterations and hashWidth will be load from the application.yml into the HashConfigProperties component using @ConfigurationProperties. The Salt will be any string, see the example below.

application.yml
1
2
3
4
5
hash:
  pbkdf2:
    salt: B6FC08FA50377E5E646CAE93C6B12E74F01D6D4E4DAE1F54DA53EB4D340EC75C
    iterations: 185000
    hashWidth: 256
  • So now, you can create generate a hash with SecretKeyFactory and PBEKeySpec as in the method hash of example code.
  • Then in the method isPbkdf2Match, you just need to hash the input data and compare with the given hashed by using MessageDigest.isEqual.

Testing#

  • Now, run the Spring Boot project and try to call api v1/cipher/hash/pbkdf2 for testing hasing data. Then you will receive the result as below

 #zoom

  • Then with the hash result above, we will use it to check with the original data by calling api v1/cipher/hash/pbkdf2/check. Then you will see the original data and hashed data are matched.

 #zoom

  • Now let's try to change a single character in original data and check again with hashed data. Then you will see the api return failed.

 #zoom

See Also#

References#