google-site-verification: google61fe8ba583a51912.html
Secure Tokenization Service in Java: A Comprehensive Guide

Build a robust, production-ready tokenization service to protect sensitive data while maintaining usability in your applications.

What is Tokenization?

Tokenization replaces sensitive data with non-sensitive equivalents (tokens) that have no extrinsic or exploitable meaning. Unlike encryption, tokenization is not mathematically reversible without accessing the token vault.

Use Cases

  • Payment card data (PCI DSS compliance)
  • Personally Identifiable Information (PII)
  • Healthcare data (HIPAA compliance)
  • API keys and credentials
  • Social Security Numbers

Architecture Overview

┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ │ Application │───▶│ Tokenization │───▶│ Token │ │ Layer │ │ Service │ │ Vault │ └─────────────┘ └──────────────────┘ └─────────────┘ │ ┌─────────────┐ │ Key │ │ Management │ └─────────────┘

Core Dependencies

Maven Configuration

<properties> <junit.version>5.9.2</junit.version> <guava.version>32.1.2-jre</guava.version> </properties> <dependencies> <!-- Spring Boot Starter (Optional) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.1.0</version> </dependency> <!-- Data Persistence --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.220</version> <scope>runtime</scope> </dependency> <!-- Security & Cryptography --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> <version>6.1.0</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava.version}</version> </dependency> <!-- Utilities --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.13.0</version> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>3.1.0</version> <scope>test</scope> </dependency> </dependencies>

Core Tokenization Service

1. Token Generator Interface & Implementations

public interface TokenGenerator { String generateToken(String sensitiveData); String generateToken(String sensitiveData, String tokenType); } /** * Secure random token generator */ @Component public class SecureRandomTokenGenerator implements TokenGenerator { private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final int DEFAULT_TOKEN_LENGTH = 32; private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @Override public String generateToken(String sensitiveData) { return generateToken(sensitiveData, "DEFAULT"); } @Override public String generateToken(String sensitiveData, String tokenType) { int tokenLength = getTokenLengthForType(tokenType); StringBuilder token = new StringBuilder(tokenLength); // Add prefix based on token type token.append(getTokenPrefix(tokenType)); // Generate random part for (int i = 0; i < tokenLength - token.length(); i++) { int index = SECURE_RANDOM.nextInt(CHARACTERS.length()); token.append(CHARACTERS.charAt(index)); } return token.toString(); } private String getTokenPrefix(String tokenType) { return switch (tokenType.toUpperCase()) { case "CREDIT_CARD" -> "CC_"; case "SSN" -> "SSN_"; case "EMAIL" -> "EML_"; case "PHONE" -> "PHN_"; default -> "TK_"; }; } private int getTokenLengthForType(String tokenType) { return switch (tokenType.toUpperCase()) { case "CREDIT_CARD" -> 20; case "SSN" -> 16; case "EMAIL" -> 24; case "PHONE" -> 18; default -> DEFAULT_TOKEN_LENGTH; }; } } /** * Format-preserving token generator */ @Component public class FormatPreservingTokenGenerator implements TokenGenerator { private static final SecureRandom RANDOM = new SecureRandom(); @Override public String generateToken(String sensitiveData) { return generateToken(sensitiveData, "DEFAULT"); } @Override public String generateToken(String sensitiveData, String tokenType) { if (sensitiveData == null || sensitiveData.trim().isEmpty()) { throw new IllegalArgumentException("Sensitive data cannot be null or empty"); } return switch (tokenType.toUpperCase()) { case "CREDIT_CARD" -> generateCreditCardToken(sensitiveData); case "SSN" -> generateSSNToken(sensitiveData); case "PHONE" -> generatePhoneToken(sensitiveData); case "EMAIL" -> generateEmailToken(sensitiveData); default -> generateDefaultToken(sensitiveData); }; } private String generateCreditCardToken(String creditCard) { String cleaned = creditCard.replaceAll("[^0-9]", ""); // Keep first 6 (BIN) and last 4 digits, tokenize the middle if (cleaned.length() >= 16) { String bin = cleaned.substring(0, 6); String lastFour = cleaned.substring(cleaned.length() - 4); String middle = generateNumericToken(cleaned.length() - 10); return bin + middle + lastFour; } return generateNumericToken(cleaned.length()); } private String generateSSNToken(String ssn) { String cleaned = ssn.replaceAll("[^0-9]", ""); if (cleaned.length() == 9) { return "XXX-XX-" + cleaned.substring(5); } return generateNumericToken(cleaned.length()); } private String generatePhoneToken(String phone) { String cleaned = phone.replaceAll("[^0-9]", ""); if (cleaned.length() == 10) { return "XXX-XXX-" + cleaned.substring(6); } return generateNumericToken(cleaned.length()); } private String generateEmailToken(String email) { String[] parts = email.split("@"); if (parts.length == 2) { String localPart = parts[0]; String domain = parts[1]; // Keep first and last character of local part if (localPart.length() >= 2) { String maskedLocal = localPart.charAt(0) + "***" + localPart.charAt(localPart.length() - 1); return maskedLocal + "@" + domain; } } return "***@" + (email.contains("@") ? email.split("@")[1] : "example.com"); } private String generateDefaultToken(String data) { // For generic data, maintain similar length but replace with random chars char[] token = new char[data.length()]; for (int i = 0; i < data.length(); i++) { if (Character.isDigit(data.charAt(i))) { token[i] = (char) ('0' + RANDOM.nextInt(10)); } else if (Character.isLetter(data.charAt(i))) { token[i] = (char) ('A' + RANDOM.nextInt(26)); } else { token[i] = data.charAt(i); // Keep separators } } return new String(token); } private String generateNumericToken(int length) { char[] token = new char[length]; for (int i = 0; i < length; i++) { token[i] = (char) ('0' + RANDOM.nextInt(10)); } return new String(token); } }

2. Data Encryption Service

public interface EncryptionService { String encrypt(String data); String decrypt(String encryptedData); } @Component public class AESEncryptionService implements EncryptionService { private static final String ALGORITHM = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; private static final int IV_LENGTH_BYTE = 12; private final SecretKey secretKey; private final Cipher cipher; public AESEncryptionService(@Value("${encryption.secret.key}") String base64Key) throws GeneralSecurityException { byte[] keyBytes = Base64.getDecoder().decode(base64Key); this.secretKey = new SecretKeySpec(keyBytes, "AES"); this.cipher = Cipher.getInstance(ALGORITHM); } @Override public String encrypt(String data) { try { byte[] iv = new byte[IV_LENGTH_BYTE]; SecureRandom random = new SecureRandom(); random.nextBytes(iv); GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); byte[] cipherText = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); byte[] encrypted = new byte[iv.length + cipherText.length]; System.arraycopy(iv, 0, encrypted, 0, iv.length); System.arraycopy(cipherText, 0, encrypted, iv.length, cipherText.length); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new RuntimeException("Encryption failed", e); } } @Override public String decrypt(String encryptedData) { try { byte[] decoded = Base64.getDecoder().decode(encryptedData); byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH_BYTE); byte[] cipherText = Arrays.copyOfRange(decoded, IV_LENGTH_BYTE, decoded.length); GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); byte[] plainText = cipher.doFinal(cipherText); return new String(plainText, StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException("Decryption failed", e); } } }

3. Token Entity and Repository

@Entity @Table(name = "tokens", indexes = { @Index(name = "idx_token_value", columnList = "tokenValue"), @Index(name = "idx_token_type", columnList = "tokenType"), @Index(name = "idx_created_at", columnList = "createdAt") }) public class Token { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String tokenValue; @Column(nullable = false, length = 1000) private String encryptedData; @Column(nullable = false) private String tokenType; @Column private String metadata; @Column(nullable = false) private LocalDateTime createdAt; @Column private LocalDateTime expiresAt; @Column(nullable = false) private boolean active = true; // Constructors public Token() {} public Token(String tokenValue, String encryptedData, String tokenType) { this.tokenValue = tokenValue; this.encryptedData = encryptedData; this.tokenType = tokenType; this.createdAt = LocalDateTime.now(); } // Getters and Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTokenValue() { return tokenValue; } public void setTokenValue(String tokenValue) { this.tokenValue = tokenValue; } public String getEncryptedData() { return encryptedData; } public void setEncryptedData(String encryptedData) { this.encryptedData = encryptedData; } public String getTokenType() { return tokenType; } public void setTokenType(String tokenType) { this.tokenType = tokenType; } public String getMetadata() { return metadata; } public void setMetadata(String metadata) { this.metadata = metadata; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getExpiresAt() { return expiresAt; } public void setExpiresAt(LocalDateTime expiresAt) { this.expiresAt = expiresAt; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } } @Repository public interface TokenRepository extends JpaRepository<Token, Long> { Optional<Token> findByTokenValue(String tokenValue); Optional<Token> findByTokenValueAndActiveTrue(String tokenValue); List<Token> findByTokenType(String tokenType); List<Token> findByExpiresAtBeforeAndActiveTrue(LocalDateTime dateTime); boolean existsByTokenValue(String tokenValue); @Query("SELECT COUNT(t) FROM Token t WHERE t.createdAt >= :startDate AND t.createdAt < :endDate") Long countTokensCreatedBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); @Modifying @Query("UPDATE Token t SET t.active = false WHERE t.expiresAt < :now") int deactivateExpiredTokens(@Param("now") LocalDateTime now); }

4. Main Tokenization Service

public interface TokenizationService { TokenizationResponse tokenize(TokenizationRequest request); DetokenizationResponse detokenize(DetokenizationRequest request); boolean validateToken(String token); boolean deleteToken(String token); TokenMetadata getTokenMetadata(String token); } @Service @Transactional public class TokenizationServiceImpl implements TokenizationService { private final TokenRepository tokenRepository; private final EncryptionService encryptionService; private final TokenGenerator secureTokenGenerator; private final TokenGenerator formatPreservingGenerator; private final Map<String, TokenGenerator> tokenGenerators; public TokenizationServiceImpl(TokenRepository tokenRepository, EncryptionService encryptionService, SecureRandomTokenGenerator secureTokenGenerator, FormatPreservingTokenGenerator formatPreservingGenerator) { this.tokenRepository = tokenRepository; this.encryptionService = encryptionService; this.secureTokenGenerator = secureTokenGenerator; this.formatPreservingGenerator = formatPreservingGenerator; this.tokenGenerators = Map.of( "SECURE_RANDOM", secureTokenGenerator, "FORMAT_PRESERVING", formatPreservingGenerator ); } @Override public TokenizationResponse tokenize(TokenizationRequest request) { try { // Validate input validateTokenizationRequest(request); // Select appropriate token generator TokenGenerator generator = selectTokenGenerator(request); // Generate unique token String token = generateUniqueToken(generator, request.getData(), request.getTokenType()); // Encrypt sensitive data String encryptedData = encryptionService.encrypt(request.getData()); // Create and save token entity Token tokenEntity = new Token(token, encryptedData, request.getTokenType()); // Set expiration if provided if (request.getExpiryMinutes() != null) { tokenEntity.setExpiresAt(LocalDateTime.now().plusMinutes(request.getExpiryMinutes())); } // Set metadata if (request.getMetadata() != null) { tokenEntity.setMetadata(request.getMetadata()); } tokenRepository.save(tokenEntity); return TokenizationResponse.builder() .success(true) .token(token) .tokenType(request.getTokenType()) .createdAt(tokenEntity.getCreatedAt()) .expiresAt(tokenEntity.getExpiresAt()) .build(); } catch (Exception e) { return TokenizationResponse.builder() .success(false) .errorMessage(e.getMessage()) .build(); } } @Override public DetokenizationResponse detokenize(DetokenizationRequest request) { try { // Find active token Token token = tokenRepository.findByTokenValueAndActiveTrue(request.getToken()) .orElseThrow(() -> new IllegalArgumentException("Invalid or inactive token")); // Check expiration if (token.getExpiresAt() != null && token.getExpiresAt().isBefore(LocalDateTime.now())) { throw new IllegalArgumentException("Token has expired"); } // Decrypt data String decryptedData = encryptionService.decrypt(token.getEncryptedData()); return DetokenizationResponse.builder() .success(true) .originalData(decryptedData) .tokenType(token.getTokenType()) .build(); } catch (Exception e) { return DetokenizationResponse.builder() .success(false) .errorMessage(e.getMessage()) .build(); } } @Override public boolean validateToken(String token) { return tokenRepository.findByTokenValueAndActiveTrue(token) .map(t -> t.getExpiresAt() == null || t.getExpiresAt().isAfter(LocalDateTime.now())) .orElse(false); } @Override public boolean deleteToken(String token) { return tokenRepository.findByTokenValue(token) .map(t -> { t.setActive(false); tokenRepository.save(t); return true; }) .orElse(false); } @Override public TokenMetadata getTokenMetadata(String token) { return tokenRepository.findByTokenValue(token) .map(t -> TokenMetadata.builder() .tokenType(t.getTokenType()) .createdAt(t.getCreatedAt()) .expiresAt(t.getExpiresAt()) .active(t.isActive()) .metadata(t.getMetadata()) .build()) .orElse(null); } private void validateTokenizationRequest(TokenizationRequest request) { if (request.getData() == null || request.getData().trim().isEmpty()) { throw new IllegalArgumentException("Data cannot be null or empty"); } if (request.getTokenType() == null || request.getTokenType().trim().isEmpty()) { throw new IllegalArgumentException("Token type cannot be null or empty"); } // Validate specific token types switch (request.getTokenType().toUpperCase()) { case "CREDIT_CARD": validateCreditCard(request.getData()); break; case "SSN": validateSSN(request.getData()); break; case "EMAIL": validateEmail(request.getData()); break; } } private void validateCreditCard(String creditCard) { String cleaned = creditCard.replaceAll("[^0-9]", ""); if (cleaned.length() < 13 || cleaned.length() > 19) { throw new IllegalArgumentException("Invalid credit card number length"); } // Basic Luhn check if (!isValidLuhn(cleaned)) { throw new IllegalArgumentException("Invalid credit card number"); } } private void validateSSN(String ssn) { String cleaned = ssn.replaceAll("[^0-9]", ""); if (cleaned.length() != 9) { throw new IllegalArgumentException("SSN must be 9 digits"); } // Check for invalid SSN patterns if (cleaned.startsWith("000") || cleaned.startsWith("666") || cleaned.substring(3, 5).equals("00") || cleaned.substring(5).equals("0000")) { throw new IllegalArgumentException("Invalid SSN pattern"); } } private void validateEmail(String email) { String emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"; if (!email.matches(emailRegex)) { throw new IllegalArgumentException("Invalid email format"); } } private boolean isValidLuhn(String number) { int sum = 0; boolean alternate = false; for (int i = number.length() - 1; i >= 0; i--) { int n = Integer.parseInt(number.substring(i, i + 1)); if (alternate) { n *= 2; if (n > 9) { n = (n % 10) + 1; } } sum += n; alternate = !alternate; } return (sum % 10 == 0); } private TokenGenerator selectTokenGenerator(TokenizationRequest request) { String generatorType = request.getTokenGeneratorType(); if (generatorType != null && tokenGenerators.containsKey(generatorType.toUpperCase())) { return tokenGenerators.get(generatorType.toUpperCase()); } // Default based on token type return switch (request.getTokenType().toUpperCase()) { case "CREDIT_CARD", "SSN", "PHONE" -> formatPreservingGenerator; default -> secureTokenGenerator; }; } private String generateUniqueToken(TokenGenerator generator, String data, String tokenType) { String token; int attempts = 0; int maxAttempts = 5; do { token = generator.generateToken(data, tokenType); attempts++; if (attempts > maxAttempts) { throw new RuntimeException("Failed to generate unique token after " + maxAttempts + " attempts"); } } while (tokenRepository.existsByTokenValue(token)); return token; } @Scheduled(cron = "0 0 2 * * ?") // Run daily at 2 AM public void cleanupExpiredTokens() { int deactivated = tokenRepository.deactivateExpiredTokens(LocalDateTime.now()); logger.info("Deactivated {} expired tokens", deactivated); } }

5. DTO Classes

@Data @Builder @NoArgsConstructor @AllArgsConstructor public class TokenizationRequest { @NotBlank private String data; @NotBlank private String tokenType; // CREDIT_CARD, SSN, EMAIL, PHONE, etc. private String tokenGeneratorType; // SECURE_RANDOM, FORMAT_PRESERVING private Integer expiryMinutes; private String metadata; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class TokenizationResponse { private boolean success; private String token; private String tokenType; private LocalDateTime createdAt; private LocalDateTime expiresAt; private String errorMessage; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class DetokenizationRequest { @NotBlank private String token; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class DetokenizationResponse { private boolean success; private String originalData; private String tokenType; private String errorMessage; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class TokenMetadata { private String tokenType; private LocalDateTime createdAt; private LocalDateTime expiresAt; private boolean active; private String metadata; }

6. REST Controller

@RestController @RequestMapping("/api/v1/tokenization") @Validated public class TokenizationController { private final TokenizationService tokenizationService; public TokenizationController(TokenizationService tokenizationService) { this.tokenizationService = tokenizationService; } @PostMapping("/tokenize") public ResponseEntity<TokenizationResponse> tokenize( @Valid @RequestBody TokenizationRequest request) { TokenizationResponse response = tokenizationService.tokenize(request); HttpStatus status = response.isSuccess() ? HttpStatus.CREATED : HttpStatus.BAD_REQUEST; return ResponseEntity.status(status).body(response); } @PostMapping("/detokenize") public ResponseEntity<DetokenizationResponse> detokenize( @Valid @RequestBody DetokenizationRequest request) { DetokenizationResponse response = tokenizationService.detokenize(request); HttpStatus status = response.isSuccess() ? HttpStatus.OK : HttpStatus.BAD_REQUEST; return ResponseEntity.status(status).body(response); } @GetMapping("/validate/{token}") public ResponseEntity<Map<String, Object>> validateToken(@PathVariable String token) { boolean isValid = tokenizationService.validateToken(token); TokenMetadata metadata = tokenizationService.getTokenMetadata(token); Map<String, Object> response = new HashMap<>(); response.put("valid", isValid); response.put("metadata", metadata); return ResponseEntity.ok(response); } @DeleteMapping("/{token}") public ResponseEntity<Map<String, Object>> deleteToken(@PathVariable String token) { boolean deleted = tokenizationService.deleteToken(token); Map<String, Object> response = new HashMap<>(); response.put("deleted", deleted); return deleted ? ResponseEntity.ok(response) : ResponseEntity.notFound().build(); } @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<TokenizationResponse> handleIllegalArgument(IllegalArgumentException ex) { TokenizationResponse response = TokenizationResponse.builder() .success(false) .errorMessage(ex.getMessage()) .build(); return ResponseEntity.badRequest().body(response); } }

Configuration

application.yml

server: port: 8080 spring: datasource: url: jdbc:h2:mem:tokendb driverClassName: org.h2.Driver username: sa password: password jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: create-drop show-sql: true h2: console: enabled: true encryption: secret: key: "your-base64-encoded-256-bit-key-here" # Generate with: openssl rand -base64 32 tokenization: default: expiry-minutes: 1440 # 24 hours cleanup: enabled: true cron: "0 0 2 * * ?" logging: level: com.yourcompany.tokenization: DEBUG

Security Configuration

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(authz -> authz .requestMatchers("/api/v1/tokenization/**").authenticated() .anyRequest().permitRequired() ) .httpBasic(Customizer.withDefaults()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); } }

Testing

Unit Tests

@ExtendWith(MockitoExtension.class) class TokenizationServiceTest { @Mock private TokenRepository tokenRepository; @Mock private EncryptionService encryptionService; @Mock private TokenGenerator tokenGenerator; @InjectMocks private TokenizationServiceImpl tokenizationService; @Test void testTokenizeCreditCard() { // Given TokenizationRequest request = TokenizationRequest.builder() .data("4111111111111111") .tokenType("CREDIT_CARD") .build(); when(tokenGenerator.generateToken(anyString(), anyString())) .thenReturn("CC_1234567890123456"); when(encryptionService.encrypt(anyString())) .thenReturn("encrypted-data"); when(tokenRepository.existsByTokenValue(anyString())) .thenReturn(false); when(tokenRepository.save(any(Token.class))) .thenAnswer(invocation -> invocation.getArgument(0)); // When TokenizationResponse response = tokenizationService.tokenize(request); // Then assertTrue(response.isSuccess()); assertEquals("CC_1234567890123456", response.getToken()); assertEquals("CREDIT_CARD", response.getTokenType()); } @Test void testDetokenizeValidToken() { // Given Token token = new Token("test-token", "encrypted-data", "CREDIT_CARD"); token.setActive(true); when(tokenRepository.findByTokenValueAndActiveTrue("test-token")) .thenReturn(Optional.of(token)); when(encryptionService.decrypt("encrypted-data")) .thenReturn("4111111111111111"); DetokenizationRequest request = new DetokenizationRequest("test-token"); // When DetokenizationResponse response = tokenizationService.detokenize(request); // Then assertTrue(response.isSuccess()); assertEquals("4111111111111111", response.getOriginalData()); } }

Key Management Best Practices

@Component public class KeyManagementService { @Value("${encryption.secret.key}") private String currentKey; private final Map<String, String> keyVersions = new ConcurrentHashMap<>(); @PostConstruct public void init() { keyVersions.put("v1", currentKey); } public void rotateKey() { try { // Generate new key KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); SecretKey newKey = keyGen.generateKey(); String newKeyBase64 = Base64.getEncoder().encodeToString(newKey.getEncoded()); // Store new key version String newVersion = "v" + (keyVersions.size() + 1); keyVersions.put(newVersion, newKeyBase64); // Re-encrypt all tokens with new key (in background) reencryptAllTokens(newKeyBase64); // Update current key this.currentKey = newKeyBase64; } catch (Exception e) { throw new RuntimeException("Key rotation failed", e); } } @Async public void reencryptAllTokens(String newKey) { // Implementation for re-encrypting all tokens with new key // This should be done in batches for large datasets } }

Performance Considerations

  1. Database Indexing: Ensure proper indexes on tokenValue, tokenType, and createdAt
  2. Caching: Implement caching for frequently accessed tokens
  3. Connection Pooling: Use connection pools for database operations
  4. Batch Operations: Process token cleanup in batches
  5. Monitoring: Implement metrics and monitoring

This tokenization service provides a solid foundation for securing sensitive data in your Java applications while maintaining compliance with various regulatory requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper