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
- Symmetric encryption is used in file system encryption, database encryption e.g credit card details.
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.
packagecom.springboot.security.symmetric.encrypt.decrypt.app.service;importcom.springboot.security.symmetric.encrypt.decrypt.app.model.Aes;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjavax.crypto.Cipher;importjavax.crypto.KeyGenerator;importjavax.crypto.SecretKey;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;importjavax.xml.bind.DatatypeConverter;importjava.nio.charset.StandardCharsets;importjava.security.SecureRandom;importjava.util.Arrays;importjava.util.Objects;@ServicepublicclassAes256Service{privatestaticfinalStringCIPHER_TRANSFORMATION="AES/CBC/PKCS5PADDING";privatestaticfinalStringENCRYPTION_ALGORITHM="AES";@AutowiredprivateAesaes;publicStringcreateEncryptKey(){try{SecureRandomsecureRandom=newSecureRandom();KeyGeneratorkeyGenerator=KeyGenerator.getInstance(ENCRYPTION_ALGORITHM);keyGenerator.init(256,secureRandom);SecretKeykey=keyGenerator.generateKey();returnDatatypeConverter.printHexBinary(key.getEncoded());}catch(Exceptionex){thrownewRuntimeException("Can not generate Secret Key",ex);}}publicStringencryptData(Stringdata){try{Ciphercipher=Cipher.getInstance(CIPHER_TRANSFORMATION);byte[]key=DatatypeConverter.parseHexBinary(aes.getSecret());SecretKeySpecsecretKeySpec=newSecretKeySpec(key,ENCRYPTION_ALGORITHM);if(Objects.isNull(aes.getIvSecret())){byte[]ivParameterSpecKey=this.generateIvParameterSpec();IvParameterSpecivParameterSpec=newIvParameterSpec(ivParameterSpecKey);cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);byte[]encryptedData=cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));StringivParameterSpecKeyString=DatatypeConverter.printHexBinary(ivParameterSpecKey);StringencryptedDataString=DatatypeConverter.printHexBinary(encryptedData);returnivParameterSpecKeyString.concat(encryptedDataString);}IvParameterSpecivParameterSpec=newIvParameterSpec(aes.getIvSecret().getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);byte[]encryptedData=cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));returnDatatypeConverter.printHexBinary(encryptedData);}catch(Exceptionex){thrownewRuntimeException("Can not encrypt Data",ex);}}publicStringdecryptData(Stringdata){try{Ciphercipher=Cipher.getInstance(CIPHER_TRANSFORMATION);byte[]key=DatatypeConverter.parseHexBinary(aes.getSecret());SecretKeySpecsecretKeySpec=newSecretKeySpec(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);IvParameterSpecivParameterSpec=newIvParameterSpec(ivParameterSpecKey);cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);byte[]decryptedData=cipher.doFinal(payload);returnnewString(decryptedData,StandardCharsets.UTF_8);}IvParameterSpecivParameterSpec=newIvParameterSpec(aes.getIvSecret().getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);byte[]decryptedData=cipher.doFinal(dataBytes);returnnewString(decryptedData,StandardCharsets.UTF_8);}catch(Exceptionex){thrownewRuntimeException("Can not decrypt Data",ex);}}privatebyte[]generateIvParameterSpec(){byte[]iv=newbyte[16];SecureRandomrandom=newSecureRandom();random.nextBytes(iv);returniv;}privatebyte[]getIvParameterSpecKey(byte[]dataBytes){returnArrays.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.
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
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.
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.
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.
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.
Then we can use api v1/cipher/decrypt to decrypt your ciphertexts above.