Using Jasypt for Configuration and Sensitive Field Data Masking in Spring Boot
This article explains how to prevent data leaks by encrypting configuration files and masking sensitive fields in Spring Boot applications using Jasypt, covering Maven setup, encryption/decryption methods, custom annotations, AOP implementation, and the underlying PBE algorithm details.
Recently our company discovered internal data leaks because some interns accidentally committed source code, including account credentials, to GitHub , exposing core data.
I once suffered a similar incident where I mistakenly committed a database username and plaintext password to GitHub , which led to a colleague deleting the test database. Since then I have encrypted all configuration files and always apply data masking to sensitive information.
If you are unfamiliar with data masking, you can refer to my previous article "Six Data Masking Solutions Used by Large Companies" for a brief overview. Below I share two common masking scenarios in practice.
Configuration Masking
I use the Java encryption library Jasypt to mask configuration values. It provides two encryption modes: single‑key symmetric encryption and asymmetric encryption .
Symmetric encryption uses one key (with salt) for both encryption and decryption; asymmetric encryption uses a public key and a private key pair.
Below is a Spring Boot example that integrates the single‑key symmetric mode.
First, add the jasypt-spring-boot-starter dependency:
<!-- Configuration file encryption -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>In the configuration file, set the secret key with jasypt.encryptor.password and replace the values that need masking with the encrypted form, e.g. ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l) .
You can define a custom prefix/suffix, such as abc[ encrypted ] , by configuring the following:
jasypt:
encryptor:
property:
prefix: "abc["
suffix: "]"The ENC(...) format is recognized by Jasypt; values not following this pattern remain unchanged.
Because the secret key is highly sensitive, it is recommended to inject it at startup with a -D parameter or store it in a configuration center, e.g.:
java -jar -Djasypt.encryptor.password=1123 springboot-jasypt-2.3.RELEASE.jarYou can generate encrypted values programmatically:
@Autowired
private StringEncryptor stringEncryptor;
public void encrypt(String content) {
String encryptStr = stringEncryptor.encrypt(content);
System.out.println("Encrypted content: " + encryptStr);
}Or use the Jasypt CLI jar:
java -cp D:\maven_lib\org\jasypt\jasypt\1.9.3\jasypt-1.9.3.jar \
org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="root" password=xiaofu algorithm=PBEWithMD5AndDESSensitive Field Masking
In production, user privacy data such as phone numbers, ID numbers, or account configurations must be masked before persisting to the database. This requires real‑time masking on input and de‑masking on output, which can be conveniently achieved with an AOP aspect.
First, define two custom annotations:
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
String[] value() default "";
}
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptMethod {
String type() default ENCRYPT;
}The aspect intercepts methods annotated with @EncryptMethod , checks parameters for @EncryptField , and encrypts those fields; it also decrypts the return value.
@Slf4j
@Aspect
@Component
public class EncryptHandler {
@Autowired
private StringEncryptor stringEncryptor;
@Pointcut("@annotation(com.xiaofu.annotation.EncryptMethod)")
public void pointCut() {}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
/** encrypt */
encrypt(joinPoint);
/** decrypt */
Object decrypt = decrypt(joinPoint);
return decrypt;
}
public void encrypt(ProceedingJoinPoint joinPoint) {
try {
Object[] objects = joinPoint.getArgs();
if (objects.length != 0) {
for (Object o : objects) {
if (o instanceof String) {
encryptValue(o);
} else {
handler(o, ENCRYPT);
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public Object decrypt(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
Object obj = joinPoint.proceed();
if (obj != null) {
if (obj instanceof String) {
decryptValue(obj);
} else {
result = handler(obj, DECRYPT);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
// ... other helper methods
}Example controller method and data class:
@EncryptMethod
@PostMapping("test")
@ResponseBody
public Object testEncrypt(@RequestBody UserVo user, @EncryptField String name) {
return insertUser(user, name);
}
private UserVo insertUser(UserVo user, String name) {
System.out.println("Encrypted data: user" + JSON.toJSONString(user));
return user;
}
@Data
public class UserVo implements Serializable {
private Long userId;
@EncryptField
private String mobile;
@EncryptField
private String address;
private String age;
}When the endpoint is called, the annotated fields are encrypted before storage, while the response returns the original (de‑masked) data.
How Jasypt Works Internally
Jasypt integrates with Spring Boot via the jasypt-spring-boot-starter . The starter registers EnableEncryptablePropertiesConfiguration , which adds EnableEncryptablePropertiesBeanFactoryPostProcessor to the Spring context.
This post‑processor receives the ConfigurableEnvironment and an EncryptablePropertySourceConverter . It wraps each PropertySource with EncryptablePropertySourceWrapper , overriding getProperty(String name) to decrypt any value wrapped in ENC(...) before it is used.
Jasypt uses the JCE library and implements Password‑Based Encryption (PBE). The default algorithm is PBEWITHMD5ANDDES , which combines a password, a random 8‑byte salt, and a configurable iteration count.
public byte[] encrypt(byte[] message) {
final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
byte[] salt = saltGenerator.generateSalt(8);
final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations);
SecretKey key = factory.generateSecret(keySpec);
final Cipher cipherEncrypt = Cipher.getInstance(algorithm1);
cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
byte[] params = cipherEncrypt.getParameters().getEncoded();
byte[] encryptedMessage = cipherEncrypt.doFinal(message);
return ByteBuffer.allocate(1 + params.length + encryptedMessage.length)
.put((byte) params.length)
.put(params)
.put(encryptedMessage)
.array();
}The encrypted payload consists of a one‑byte length, the salt/parameters, and the ciphertext. During decryption, the wrapper extracts the parameters, rebuilds the secret key, and decrypts the message:
public byte[] decrypt(byte[] encryptedMessage) {
int paramsLength = Byte.toUnsignedInt(encryptedMessage[0]);
int messageLength = encryptedMessage.length - paramsLength - 1;
byte[] params = new byte[paramsLength];
byte[] message = new byte[messageLength];
System.arraycopy(encryptedMessage, 1, params, 0, paramsLength);
System.arraycopy(encryptedMessage, paramsLength + 1, message, 0, messageLength);
final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKey key = factory.generateSecret(keySpec);
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algorithm1);
algorithmParameters.init(params);
final Cipher cipherDecrypt = Cipher.getInstance(algorithm1);
cipherDecrypt.init(Cipher.DECRYPT_MODE, key, algorithmParameters);
return cipherDecrypt.doFinal(message);
}Understanding this flow makes it easy to customize the algorithm, replace the salt generator, or build your own masking tool.
Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.