Skip to content

Symmetric Encryption#

What Is The Symmetric Encryption?#

  • Symmetric encryption is a type of encryption where only one secret symmetric key is used to encrypt the plaintext and decrypt the ciphertext. We have some common symmetric encryption methods as AES (Advanced Encryption Standard), 3DES (Triple Data Encryption Standards), Twofish... more information. Let's see example below. We can convert original text to AES encrypted text and back with a secret key.
Original Secret Key AES Encrypted Text
minhduc adwasekewqmdrqwd 6A64685E851ACF474ADD069AB6B4965F

 #zoom - Symmetric encryption is used in file system encryption, database encryption e.g credit card details.

Example With 256-bit AES In Spring Boot#

  • We will do an example with 256-bit AES. Spring Boot has default libraries for us to use encrypt and decrypt with AES, so we don't need to add more any dependency.

Controller#

  • Let's create an controller with some apis as below:
Controller.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
package com.springboot.security.symmetric.encrypt.decrypt.app.controller;

import com.springboot.security.symmetric.encrypt.decrypt.app.model.DataRequest;
import com.springboot.security.symmetric.encrypt.decrypt.app.service.Aes256Service;
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 Controller {

    @Autowired
    private Aes256Service aes256Service;

    @RequestMapping(method = RequestMethod.GET, path = "v1/cipher/encrypt/key", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> createEncryptKey() {
        return new ResponseEntity<>(aes256Service.createEncryptKey(), HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.POST, path = "v1/cipher/encrypt", produces = MediaType.TEXT_PLAIN_VALUE)
    ResponseEntity<String> encryptData(@RequestBody DataRequest inputData) {
        return new ResponseEntity<>(aes256Service.encryptData(inputData.getData()), HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.POST, path = "v1/cipher/decrypt", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> decryptData(@RequestBody DataRequest inputData) {
        return new ResponseEntity<>(aes256Service.decryptData(inputData.getData()), HttpStatus.CREATED);
    }

}
  • We will need to create a simple model for request body as below.

DataRequest.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.springboot.security.symmetric.encrypt.decrypt.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;
    }

}
- Then we also need to create a model that loads environment variables into a spring bean using @ConfigurationProperties as below.

Aes.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
package com.springboot.security.symmetric.encrypt.decrypt.app.model;


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

@Component
@ConfigurationProperties(prefix = "aes")
public class Aes {

    private String secret;
    private String ivSecret;

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public String getIvSecret() {
        return ivSecret;
    }

    public void setIvSecret(String ivSecret) {
        this.ivSecret = ivSecret;
    }
}

Service#

  • Now, let's create a Service for encrypt and decrypt data as below.
Aes256Service.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.springboot.security.symmetric.encrypt.decrypt.app.service;

import com.springboot.security.symmetric.encrypt.decrypt.app.model.Aes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Objects;

@Service
public class Aes256Service {

    private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5PADDING";
    private static final String ENCRYPTION_ALGORITHM = "AES";

    @Autowired
    private Aes aes;

    public String createEncryptKey() {
        try {
            SecureRandom secureRandom = new SecureRandom();
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM);
            keyGenerator.init(256, secureRandom);
            SecretKey key = keyGenerator.generateKey();
            return DatatypeConverter.printHexBinary(key.getEncoded());
        } catch (Exception ex) {
            throw new RuntimeException("Can not generate Secret Key", ex);
        }
    }

    public String encryptData(String data) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
            byte[] key = DatatypeConverter.parseHexBinary(aes.getSecret());
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, ENCRYPTION_ALGORITHM);
            if (Objects.isNull(aes.getIvSecret())) {
                byte[] ivParameterSpecKey = this.generateIvParameterSpec();
                IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParameterSpecKey);
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
                byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
                String ivParameterSpecKeyString = DatatypeConverter.printHexBinary(ivParameterSpecKey);
                String encryptedDataString = DatatypeConverter.printHexBinary(encryptedData);
                return ivParameterSpecKeyString.concat(encryptedDataString);
            }
            IvParameterSpec ivParameterSpec = new IvParameterSpec(aes.getIvSecret().getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
            byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return DatatypeConverter.printHexBinary(encryptedData);
        } catch (Exception ex) {
            throw new RuntimeException("Can not encrypt Data", ex);
        }
    }

    public String decryptData(String data) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
            byte[] key = DatatypeConverter.parseHexBinary(aes.getSecret());
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, ENCRYPTION_ALGORITHM);
            byte[] dataBytes = DatatypeConverter.parseHexBinary(data);
            if (Objects.isNull(aes.getIvSecret()) && dataBytes.length > 16) {
                byte[] ivParameterSpecKey = this.getIvParameterSpecKey(dataBytes);
                byte[] payload = Arrays.copyOfRange(dataBytes, 16, dataBytes.length);
                IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParameterSpecKey);
                cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
                byte[] decryptedData = cipher.doFinal(payload);
                return new String(decryptedData, StandardCharsets.UTF_8);
            }
            IvParameterSpec ivParameterSpec = new IvParameterSpec(aes.getIvSecret().getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
            byte[] decryptedData = cipher.doFinal(dataBytes);
            return new String(decryptedData, StandardCharsets.UTF_8);
        } catch (Exception ex) {
            throw new RuntimeException("Can not decrypt Data", ex);
        }
    }

    private byte[] generateIvParameterSpec() {
        byte[] iv = new byte[16];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        return iv;
    }

    private byte[] getIvParameterSpecKey(byte[] dataBytes) {
        return Arrays.copyOfRange(dataBytes, 0, 16);
    }

}
  • So the first method createEncryptKey is used to create a 256-bit AES secret key. In this method we will use KeyGenerator to generate 256 bits AES secret key. As we know 1 bytes = 8 bits, so 256 bits = 32 bytes and when we convert 32 bytes to string type with the support of DatatypeConverter.printHexBinary, we will received a string with 64 characters.
<!-- Example 256-bits AES String Key -->

0EFBADA688B68590B23BDF10EA0CBEAC3C94F5CE86A8E62642CD575012A59CF8
  • Then the second method encryptData is used to encrypt data by AES. Spring Boot provide an api java call Cipher to do encrypt and decrypt. So if we want to use AES, we have to getInstance Cipher for AES by using Cipher.getInstance("AES/CBC/PKCS5PADDING"), then init the cipher with 3 main params, Cipher Mode, SecretKeySpec and IvParameterSpec.

  • Cipher Mode: Define the mode of Cipher that we want to use (Encrypt or Decrypt...)

  • SecretKeySpec: SecretKey is an interface and SecretKeySpec is an implementation of SecretKey. Every SecretKey has an associated algorithm name. So to make Cipher understands that which type of keys that you are using, so we need to create SecretKeySpec which contains our secret key and algorithm name.

  • IvParameterSpec: When using AES with a mode known as CBC (Cipher Block Chaining), you need to generate an initialization vector (IV).

  • An initialization vector (IV) or starting variable is a block of bits that is used by several modes to randomize the encryption and hence to produce distinct ciphertexts even if the same plaintext is encrypted multiple times, without the need for a slower re-keying process.
  • In CBC mode, each block of plaintext is XORed with the previous ciphertext block before being encrypted. Each ciphertext block depends on all plaintext blocks processed up to that point. To make each message unique, an initialization vector must be used in the first block.
  • For CBC and CFB, reusing an IV leaks some information about the first block of plaintext, and about any common prefix shared by the two messages, so we need to generate IV randomly for every encoding.
  • More information

  • So in method encryptData you can see we generate random IV every time this method is called, so it will make encrypted data will be different although we use the same input data for encryption. After the encryption has done we will add the IV key together with the encrypted data then the method decryptData will use this IV key for decryption. Let's see the example below

Times Original Encrypted Data Contain IV Key
1 minhduc 8689D232432F34D52BAE769CAF6F8D38BD4C44ADB22445A3F5F0D6825741F437
2 minhduc 70184CA95125BBEC963B17D8E4FE1374C591C967C749C3C8C5378201539CD0FE
3 minhduc 057499CD9561BED01B084CE9C3455A41AB7490CB2B98EC239761AD45CE49AEA1
  • Now, The final method is decryptData, in this method, we just simply extract the encrypted data to get the IV key, then we will do decryption with steps like encryption but we use value Decrypt for Cipher Mode.

Testing#

  • Now, run the Spring Boot project and try to call apis for testing.

  • Firstly, you can use api v1/cipher/encrypt/key to create a random AES-256 secret key or you can also go to this page to get one.

 #zoom

  • Then you can put the AES-256 secret key into the application.yml as below, then you can load it into your Aes Component that you created above with @ConfigurationProperties.
application.yml
1
2
3
aes:
  #ivSecret: 94A15E1F08550FF2
    secret: 1A40C17CA7B7BED888D1E439BFD393C306E53086712AB4CD6940028C51916337
  • Next, you should restart your service again to reload your AES-256 secret key configuration. Then we can call api v1/cipher/encrypt to encrypt your data. Note that, every time you send a request you will always get a different ciphertext although your input data is the same, this is the result of using random IV as explained above.

 #zoom

  • Then we can use api v1/cipher/decrypt to decrypt your ciphertexts above.

 #zoom

See Also#

References#