Auto‑Encrypt Sensitive Fields in Spring Boot with MyBatis Annotations

This article explains how to eliminate repetitive manual encryption code in Java applications by using a custom @Encrypted annotation together with a MyBatis interceptor, enabling transparent AES‑GCM encryption and decryption of sensitive fields such as phone numbers, emails, and ID numbers.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Auto‑Encrypt Sensitive Fields in Spring Boot with MyBatis Annotations

Background

Sensitive user data (phone, ID, bank card) must not be stored in plain text. Manual encryption in each query leads to code duplication, high maintenance cost, and scattered logic.

Solution Overview

Use an annotation‑driven approach with a MyBatis interceptor to encrypt fields automatically.

Mark fields with @Encrypted.

Interceptor encrypts parameters on update / insert and decrypts result sets on query.

Business code works with plain objects; encryption is transparent.

Performance impact is minimal.

Technical Design

Annotation

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypted {
    boolean supportFuzzyQuery() default false;
}

Encryption Utility (AES‑GCM)

public class CryptoUtil {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int IV_LENGTH = 12;

    public static String encrypt(String plaintext) {
        byte[] iv = new byte[IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
        byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        byte[] encryptedData = new byte[iv.length + ciphertext.length];
        System.arraycopy(iv, 0, encryptedData, 0, iv.length);
        System.arraycopy(ciphertext, 0, encryptedData, iv.length, ciphertext.length);
        return Base64.getEncoder().encodeToString(encryptedData);
    }

    public static String decrypt(String encryptedText) {
        byte[] encryptedData = Base64.getDecoder().decode(encryptedText);
        byte[] iv = Arrays.copyOfRange(encryptedData, 0, IV_LENGTH);
        byte[] ciphertext = Arrays.copyOfRange(encryptedData, IV_LENGTH, encryptedData.length);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
        byte[] plaintext = cipher.doFinal(ciphertext);
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    public static boolean isEncrypted(String value) {
        return value != null && value.contains(":");
    }
}

MyBatis Interceptor

@Intercepts({
    @Signature(type = Executor.class, method = "update",
               args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class, method = "query",
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class EncryptionInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String method = invocation.getMethod().getName();
        if ("update".equals(method)) {
            Object param = getParameter(invocation);
            if (shouldEncrypt(param)) {
                encryptFields(param);
            }
        }
        Object result = invocation.proceed();
        if ("query".equals(method)) {
            decryptResult(result);
        }
        return result;
    }

    private void encryptFields(Object obj) { /* reflect @Encrypted fields and encrypt */ }
    private void decryptResult(Object result) { /* handle List or single object */ }
    private void decryptFields(Object obj) { /* reverse of encryptFields */ }
    // helper methods omitted for brevity
}

Automatic Configuration

@Configuration
@ConditionalOnProperty(name = "encryption.enabled", havingValue = "true", matchIfMissing = true)
public class EncryptionAutoConfiguration {
    @Bean
    public ConfigurationCustomizer encryptionConfigurationCustomizer() {
        return configuration -> configuration.addInterceptor(new EncryptionInterceptor());
    }
}

Enable the interceptor with:

encryption:
  enabled: true

Usage Example

Entity

public class User {
    private Long id;
    private String username;
    @Encrypted
    private String phone;
    @Encrypted
    private String email;
}

Service

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public void createUser(User user) {
        user.setPhone("13812345678");
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

    public User getUser(Long id) {
        User user = userMapper.findById(id);
        // phone and email are already decrypted
        return user;
    }
}

Security Considerations

Key Management

Load the AES key from environment variables, a configuration server, or a dedicated KMS instead of hard‑coding.

@Configuration
public class EncryptionConfig {
    @Value("${encryption.key}")
    private String encryptionKey;

    @Bean
    public SecretKey secretKey() {
        byte[] keyBytes = Base64.getDecoder().decode(encryptionKey);
        return new SecretKeySpec(keyBytes, "AES");
    }
}

Logging Safety

Avoid logging raw encrypted values; provide masking utilities.

public class SensitiveDataLogger {
    public String maskPhone(String phone) {
        if (phone == null || phone.length() != 11) return phone;
        return phone.substring(0,3) + "****" + phone.substring(7);
    }
    public String maskEmail(String email) {
        if (email == null) return email;
        int at = email.indexOf('@');
        if (at <= 2) return "***" + email.substring(at);
        return email.substring(0,2) + "***" + email.substring(at);
    }
}

Advantages and Limitations

Ease of use: Add @Encrypted to a field.

Clean code: Business logic stays free of encryption code.

Security: Uses standard AES‑GCM.

Maintainability: All encryption logic is centralized in the interceptor.

Not suitable for: Extremely latency‑sensitive workloads, complex encrypted queries, or massive‑scale data sets.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootMyBatisSecurityannotationField Encryption
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.