Exploring the integration of AWS Key Management Service (KMS) with Spring Boot to securely store sensitive data
In Enterprise application, data security is major requirement and consideration. Data security can be achieved using the data at transit and data at rest. But some time some attributes need more strict security and need extra encryption. These data are called sensitive information like user password, SSN number or driving license number etc.
Here in the blog, we’ll go over the challenge and the solution using Spring Boot with KMS symmetric key encryption.
Objective
During the user registration process, it is necessary to save sensitive information via the user microservice. The user is asked to provide their name and other identifying details, such as a driver’s license or passport number, along with other personal details. Even though you can read the data using the query, you won’t be able to decrypt it; thus, the application should securely store this information in the database using strong encryption service. Only the get user API and the key management service enable decryption. Here is the key requirement:
1. The application must use both data in transit and data at rest.
2. Data and encryption/decryption key must be secure.
3. It is imperative that the encryption/decryption key is highly available.
4. It is important for the encryption/decryption key to be able to replicate across different regions.
Solution Approach
Utilizing AWS KMS symmetric key encryption/decryption can provide a valuable solution to tackle the mentioned challenge. Managing keys and ensuring their security and availability is of utmost importance, and the AWS KMS is a highly reliable service for this purpose. In addition, cross-region replication is supported to help ensure the security of the keys in case of a disaster recovery situation.
Step by step Implementation
Here are the steps to implement it:
- Creating KMS CMK.
Log in as a root user and create a KMS CMK key. Go to →Console →KMS →Customer managed keys →Create key.
2.Create a parameter store key and value as below:
Key: user-profile-ms-key
Value: arn:aws:kms:us-west-2:<<aws-account-id>>:key/520bc02b-e71b-446b-a289–03beb9367373
3. Creating a new user and assigning a role to access the KMS CMK.
Please follow the link to create an IAM user and assign programmatical access.
4. Configure the AWS CLI access:
Download and install the AWS CLI refer the link
$ aws configure
AWS Access Key ID [None]: <<ACCESS-KEY>>
AWS Secret Access Key [None]: << SECRET-ACCESS-KEY >>
Default region name [None]: <<REGION>>
Default output format [None]: json
5. Install Java and maven
I am using Amazon Corretto 17, please find the link to install in the window.
6. Create a Spring Boot project:
Please create a spring boot project here, and refer the pom.xml for all the dependencies.
7. In application.properties, define the configurations.
aws.region= us-west-2
cloud.aws.region.auto=false
spring.cloud.aws.parameterstore.region=us-west-2
spring.config.import[0]=aws-parameterstore:/dev/
# #######################################################
# LOGGING
# #######################################################
logging.level.root = ${logging-level-root:INFO}
logging.level.org.springframework=${logging-level-springframework:WARN}
logging.level.org.hibernate=${logging-level-hibernate:WARN}
#cmkKeyARN =arn:aws:kms:us-west-2:<<aws_account_id>>:key/520bc02b-e71b-446b-a289-03beb9367373
cmkKeyARN =${user-profile-ms-key}
8. Create a KMS configuration class.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
@Configuration
public class KMSConfig {
@Value("${aws.region}")
private String appRegion;
@Bean
KmsClient kmsClient() {
return KmsClient.builder().region(Region.of(appRegion)).build();
}
}
9. Create a KMS utility class for encryption and decryption.
import java.util.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.DecryptRequest;
import software.amazon.awssdk.services.kms.model.DecryptResponse;
import software.amazon.awssdk.services.kms.model.EncryptRequest;
import software.amazon.awssdk.services.kms.model.EncryptResponse;
@RequiredArgsConstructor
@Slf4j
@Component
public class KMSUtil {
private final KmsClient kmsClient;
@Value("${cmkKeyARN}")
private String cmkKeyARN;
public String kmsEncrypt(String plainText) {
log.debug("EncryptionUtil::kmsEncrypt: START: ");
EncryptRequest encryptRequest = buildEncryptRequest(plainText);
EncryptResponse encryptResponse = kmsClient.encrypt(encryptRequest);
SdkBytes cipherTextBytes = encryptResponse.ciphertextBlob();
byte[] base64EncodedValue = Base64.getEncoder().encode(cipherTextBytes.asByteArray());
String responseBase64 = new String( base64EncodedValue );
log.debug("EncryptionUtil::kmsEncrypt: RESPONSE: {}" , responseBase64);
log.debug("EncryptionUtil::kmsEncrypt: END: {}" , responseBase64);
return responseBase64;
}
public String kmsDecrypt(String base64EncodedValue) {
log.debug("EncryptionUtil::kmdDecrypt: START: ");
DecryptRequest decryptRequest = buildDecryptRequest( base64EncodedValue );
DecryptResponse decryptResponse = this.kmsClient.decrypt(decryptRequest);
String decryptTest = decryptResponse.plaintext().asUtf8String();
log.debug("EncryptionUtil::kmdDecrypt: END: ");
return decryptTest;
}
private EncryptRequest buildEncryptRequest(String plainText) {
log.debug("EncryptionUtil::buildEncryptRequest: START: ");
SdkBytes plainTextBytes= SdkBytes.fromUtf8String(plainText);
EncryptRequest encryptRequest = EncryptRequest.builder().keyId(cmkKeyARN)
.plaintext(plainTextBytes).build();
log.debug("EncryptionUtil::buildEncryptRequest: END: ");
return encryptRequest;
}
private DecryptRequest buildDecryptRequest(String base64EncodedValue) {
log.debug("EncryptionUtil::buildDecryptRequest: START: ");
SdkBytes encryptBytes = SdkBytes.fromByteArray(Base64.getDecoder().decode(base64EncodedValue));
DecryptRequest decryptRequest = DecryptRequest.builder().keyId(cmkKeyARN)
.ciphertextBlob(encryptBytes).build();
log.debug("EncryptionUtil::buildDecryptRequest: END: ");
return decryptRequest;
}
}
10. Create a user registration service.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.sqs.sqspoc.utils.KMSUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@RestController
@Slf4j
public class UserProfileController {
private final KMSUtil kmsUtil;
private final UserProfileService userProfileService;
@PostMapping("/users")
public String saveUserProfile(@RequestBody UserProfileRequest userProfileRequest) {
log.info("UserProfileController::saveUserProfile:Start");
String driverLicenceCipherTest = kmsUtil.kmsEncrypt(userProfileRequest.getDriverLicence());
userProfileRequest.setDriverLicence(driverLicenceCipherTest);
userProfileService.saveUser(userProfileRequest);
log.info("UserProfileController::saveUserProfile:end");
return "Done!";
}
@GetMapping("/users")
public UserProfileDto getUserProfile(@RequestParam String userEmail) {
log.info("UserProfileController::getUserProfile:Start");
UserProfileDto userProfileDto = userProfileService.getUser(userEmail);
String driverLicencePlainTest = kmsUtil.kmsDecrypt(userProfileDto.getDriverLicence());
userProfileDto.setDriverLicence(driverLicencePlainTest);
log.info("UserProfileController::getUserProfile:end");
return userProfileDto;
}
11.Testing the user profile service.
a. Creating a new user
b. Getting the user
Summary
In this blog post, we learned about the utilization of AWS KMS for the implementation of the symmetric key encryption technique. This solution can address the following issues:
1. The solution has the ability to scale and is designed to be highly available.
2. With encryption staying within the KMS, the data and key remain extremely secure.