Introduction
In today’s digital age, data security has become one of the most severe challenges faced by enterprises and developers. Imagine your bank account information and personal privacy data as precious items placed in a transparent glass house, easily visible to anyone. This is why data encryption technology is so important—it acts like an invisibility cloak for your data.
However, many Java developers often encounter the following issues in practical projects:
- Confusion in Algorithm Selection: Faced with numerous encryption algorithms such as AES, RSA, and DES, they are unsure which to use in which scenarios.
- Balancing Performance and Security: The higher the encryption strength, the greater the performance loss; how to find the optimal balance.
- High Implementation Complexity: Correct implementation of encryption algorithms involves multiple technical details such as key management, padding modes, and initialization vectors.
- Frequent Security Vulnerabilities: Incorrect implementation may lead to seemingly secure encryption being effectively useless.
This article will unveil the mysteries of Java data encryption in an easy-to-understand manner, providing directly applicable solutions.
Comparison of Similar Knowledge Points
Core Comparison of Symmetric vs Asymmetric Encryption
| Comparison Dimension | Symmetric Encryption (AES) | Asymmetric Encryption (RSA) |
|---|---|---|
| Key Characteristics | Same key for encryption and decryption | Uses a pair of public and private keys |
| Performance | Fast speed, suitable for large data volumes | Slow speed, suitable for small data volumes |
| Security Strength | Key lengths of 128/192/256 bits | Key lengths of 1024/2048/4096 bits |
| Key Distribution | Difficult key distribution | Public key can be distributed openly |
| Typical Applications | File encryption, data transmission | Digital signatures, key exchange |
| Analogous Understanding | Like a safe, one key opens and closes | Like a mailbox, the delivery slot and retrieval key are separate |
Common Encryption Algorithms Security Comparison
| Algorithm Name | Key Length | Security Level | Recommended Use | Remarks |
|---|---|---|---|---|
| DES | 56 bits | Low | ❌ Not recommended | Has been cracked, for learning purposes only |
| 3DES | 168 bits | Medium | ⚠️ Use with caution | Poor performance, gradually phased out |
| AES-128 | 128 bits | High | ✅ Recommended | Balance of performance and security |
| AES-256 | 256 bits | Very high | ✅ Recommended | Government-level security standard |
| RSA-1024 | 1024 bits | Medium | ⚠️ Use with caution | May be cracked by quantum computing |
| RSA-2048 | 2048 bits | High | ✅ Recommended | Current mainstream standard |
| RSA-4096 | 4096 bits | Very high | ✅ Recommended | Secure for the next 10 years |
Related Knowledge and Code Introduction
Symmetric Encryption Algorithm – AES
AES (Advanced Encryption Standard) is currently the most widely used symmetric encryption algorithm, akin to a super safe that can be locked and unlocked with the same key. It replaced the easily cracked DES algorithm and has become the preferred encryption solution for governments and enterprises.
Core Concept Analysis
- Block Cipher: AES encrypts data in 128-bit blocks, similar to processing a long article in fixed-length paragraphs.
- Key Length: Supports three key lengths of 128, 192, and 256 bits; the larger the number, the higher the security.
- Encryption Modes: Common modes include CBC, ECB, GCM, etc., which affect the security and performance of encryption.
- Padding Method: PKCS5Padding is the most commonly used padding method, ensuring consistent block length.
Complete Code Implementation
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
/**
* AES Encryption Utility Class - Production-ready version
* Supports AES-256-CBC mode, providing complete encryption and decryption functionality
*/
public class AESUtil {
// Algorithm name
private static final String ALGORITHM = "AES";
// Encryption mode and padding method
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
// Key length (256 bits)
private static final int KEY_LENGTH = 256;
// Initialization vector length
private static final int IV_LENGTH = 16;
/**
* Generate AES key
* @return Base64 encoded key string
*/
public static String generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
keyGenerator.init(KEY_LENGTH); // Use 256-bit key
SecretKey secretKey = keyGenerator.generateKey();
return Base64.getEncoder().encodeToString(secretKey.getEncoded());
}
/**
* Generate random initialization vector
* @return 16-byte random IV
*/
private static byte[] generateIV() {
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv); // Use secure random number generator
return iv;
}
/**
* AES Encryption
* @param plainText Plain text
* @param keyStr Base64 encoded key
* @return Base64 encoded cipher text (including IV)
*/
public static String encrypt(String plainText, String keyStr) throws Exception {
// Decode key
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
// Generate random IV
byte[] iv = generateIV();
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Initialize encryptor
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
// Perform encryption
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// Combine IV and cipher text (IV in the first 16 bytes)
byte[] encryptedWithIV = new byte[IV_LENGTH + encryptedBytes.length];
System.arraycopy(iv, 0, encryptedWithIV, 0, IV_LENGTH);
System.arraycopy(encryptedBytes, 0, encryptedWithIV, IV_LENGTH, encryptedBytes.length);
return Base64.getEncoder().encodeToString(encryptedWithIV);
}
/**
* AES Decryption
* @param encryptedText Base64 encoded cipher text (including IV)
* @param keyStr Base64 encoded key
* @return Decrypted plain text
*/
public static String decrypt(String encryptedText, String keyStr) throws Exception {
// Decode key and cipher text
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
byte[] encryptedWithIV = Base64.getDecoder().decode(encryptedText);
// Extract IV (first 16 bytes)
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(encryptedWithIV, 0, iv, 0, IV_LENGTH);
// Extract cipher text (remaining bytes)
byte[] encryptedBytes = new byte[encryptedWithIV.length - IV_LENGTH];
System.arraycopy(encryptedWithIV, IV_LENGTH, encryptedBytes, 0, encryptedBytes.length);
// Create key and IV specifications
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Initialize decryptor
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
// Perform decryption
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
/**
* Test method
*/
public static void main(String[] args) {
try {
// Generate key
String key = generateKey();
System.out.println("Generated Key: " + key);
// Test data
String originalText = "This is the sensitive data to be encrypted: UserID=12345, Balance=10000.00";
System.out.println("Original: " + originalText);
// Encrypt
String encryptedText = encrypt(originalText, key);
System.out.println("Cipher Text: " + encryptedText);
// Decrypt
String decryptedText = decrypt(encryptedText, key);
System.out.println("Decrypted: " + decryptedText);
// Verify result
System.out.println("Encryption and Decryption Successful: " + originalText.equals(decryptedText));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Key Technical Points
- Secure Key Generation: Use
<span>KeyGenerator</span>to generate a truly random 256-bit key. - Random IV Usage: Generate a new initialization vector for each encryption to prevent the same plain text from producing the same cipher text.
- CBC Mode Selection: More secure than ECB mode, can hide data patterns.
- Key Management: In actual projects, keys should be stored in a secure key management system.
- Exception Handling: A complete exception handling mechanism to avoid information leakage.
Asymmetric Encryption Algorithm – RSA
RSA (Rivest-Shamir-Adleman) is the most famous asymmetric encryption algorithm, akin to a magical mailbox system: anyone can send letters to the mailbox (encrypting with the public key), but only the mailbox owner can retrieve the letters (decrypting with the private key). This feature makes RSA irreplaceable in scenarios such as digital signatures and key exchanges.
Core Concept Analysis
- Key Pair: Consists of a public key and a private key, mathematically related but cannot derive one from the other.
- Encryption and Decryption: Data encrypted with the public key can only be decrypted with the corresponding private key, and vice versa.
- Digital Signature: Sign data with the private key, anyone can verify the authenticity of the signature with the public key.
- Key Length: Recommended to use 2048 or 4096 bits; 1024 bits is no longer secure.
Complete Code Implementation
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* RSA Encryption Utility Class - Production-ready version
* Supports RSA-2048 key pair generation, encryption, decryption, and digital signature verification
*/
public class RSAUtil {
// Algorithm name
private static final String ALGORITHM = "RSA";
// Signature algorithm
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
// Key length (2048 bits)
private static final int KEY_SIZE = 2048;
// RSA maximum plaintext size for encryption
private static final int MAX_ENCRYPT_BLOCK = 245; // 2048-bit key corresponds to 245 bytes
// RSA maximum ciphertext size for decryption
private static final int MAX_DECRYPT_BLOCK = 256; // 2048-bit key corresponds to 256 bytes
/**
* Key pair wrapper class
*/
public static class KeyPairInfo {
private String publicKey;
private String privateKey;
public KeyPairInfo(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() { return publicKey; }
public String getPrivateKey() { return privateKey; }
}
/**
* Generate RSA key pair
* @return KeyPairInfo object containing public and private keys
*/
public static KeyPairInfo generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE); // Use 2048-bit key
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// Get public and private keys
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// Convert to Base64 string
String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
return new KeyPairInfo(publicKeyStr, privateKeyStr);
}
/**
* Restore public key from string
*/
private static PublicKey getPublicKey(String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(spec);
}
/**
* Restore private key from string
*/
private static PrivateKey getPrivateKey(String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(spec);
}
/**
* Public key encryption
* @param plainText Plain text
* @param publicKeyStr Public key string
* @return Base64 encoded cipher text
*/
public static String encryptByPublicKey(String plainText, String publicKeyStr) throws Exception {
PublicKey publicKey = getPublicKey(publicKeyStr);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] data = plainText.getBytes(StandardCharsets.UTF_8);
// Block encryption (RSA has length limits)
if (data.length <= MAX_ENCRYPT_BLOCK) {
// Data is small, encrypt directly
byte[] encryptedData = cipher.doFinal(data);
return Base64.getEncoder().encodeToString(encryptedData);
} else {
// Data is large, block encryption
StringBuilder result = new StringBuilder();
int offset = 0;
while (offset < data.length) {
int blockSize = Math.min(MAX_ENCRYPT_BLOCK, data.length - offset);
byte[] block = new byte[blockSize];
System.arraycopy(data, offset, block, 0, blockSize);
byte[] encryptedBlock = cipher.doFinal(block);
result.append(Base64.getEncoder().encodeToString(encryptedBlock)).append("|");
offset += blockSize;
}
// Remove the last separator
return result.substring(0, result.length() - 1);
}
}
/**
* Private key decryption
* @param encryptedText Base64 encoded cipher text
* @param privateKeyStr Private key string
* @return Decrypted plain text
*/
public static String decryptByPrivateKey(String encryptedText, String privateKeyStr) throws Exception {
PrivateKey privateKey = getPrivateKey(privateKeyStr);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// Check if it is block encrypted data
if (encryptedText.contains("|")) {
// Block decryption
String[] blocks = encryptedText.split("\\|");
StringBuilder result = new StringBuilder();
for (String block : blocks) {
byte[] encryptedBlock = Base64.getDecoder().decode(block);
byte[] decryptedBlock = cipher.doFinal(encryptedBlock);
result.append(new String(decryptedBlock, StandardCharsets.UTF_8));
}
return result.toString();
} else {
// Single block decryption
byte[] encryptedData = Base64.getDecoder().decode(encryptedText);
byte[] decryptedData = cipher.doFinal(encryptedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
}
/**
* Private key signing
* @param data Data to be signed
* @param privateKeyStr Private key string
* @return Base64 encoded signature
*/
public static String sign(String data, String privateKeyStr) throws Exception {
PrivateKey privateKey = getPrivateKey(privateKeyStr);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = signature.sign();
return Base64.getEncoder().encodeToString(signatureBytes);
}
/**
* Public key signature verification
* @param data Original data
* @param signatureStr Base64 encoded signature
* @param publicKeyStr Public key string
* @return Signature verification result
*/
public static boolean verify(String data, String signatureStr, String publicKeyStr) throws Exception {
PublicKey publicKey = getPublicKey(publicKeyStr);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = Base64.getDecoder().decode(signatureStr);
return signature.verify(signatureBytes);
}
/**
* Test method
*/
public static void main(String[] args) {
try {
// Generate key pair
KeyPairInfo keyPair = generateKeyPair();
System.out.println("Public Key: " + keyPair.getPublicKey());
System.out.println("Private Key: " + keyPair.getPrivateKey());
System.out.println();
// Test encryption and decryption
String originalText = "This is the important information to be encrypted: Transaction Amount=50000.00";
System.out.println("Original: " + originalText);
// Public key encryption
String encryptedText = encryptByPublicKey(originalText, keyPair.getPublicKey());
System.out.println("Cipher Text: " + encryptedText);
// Private key decryption
String decryptedText = decryptByPrivateKey(encryptedText, keyPair.getPrivateKey());
System.out.println("Decrypted: " + decryptedText);
System.out.println("Encryption and Decryption Successful: " + originalText.equals(decryptedText));
System.out.println();
// Test digital signature
String dataToSign = "Important contract content: Party A pays Party B 1 million yuan";
System.out.println("Data to be signed: " + dataToSign);
// Private key signing
String signatureStr = sign(dataToSign, keyPair.getPrivateKey());
System.out.println("Digital Signature: " + signatureStr);
// Public key verification
boolean isValid = verify(dataToSign, signatureStr, keyPair.getPublicKey());
System.out.println("Signature Verification Result: " + isValid);
// Test tampering detection
String tamperedData = "Important contract content: Party A pays Party B 2 million yuan"; // Data tampered
boolean isTamperedValid = verify(tamperedData, signatureStr, keyPair.getPublicKey());
System.out.println("Tampered Data Verification Result: " + isTamperedValid);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Key Technical Points
- Key Length Selection: Use a 2048-bit key to balance security and performance.
- Block Processing: RSA has length limits; large data needs to be encrypted in blocks.
- Padding Method: Default use of PKCS1Padding to ensure security.
- Digital Signature: Use SHA256withRSA algorithm to provide data integrity and identity authentication.
- Key Format: Public key uses X.509 format, private key uses PKCS#8 format.
Digital Signature and Verification
Digital Signature is like a combination of a handwritten signature and a seal in reality, but more secure and reliable. It acts like a tamper-proof label for data, where anyone can verify the authenticity of the label, but only the signer can create it.
Working Principle of Digital Signature
Digital signatures combine hash algorithms and asymmetric encryption algorithms:
- Generate Hash: Use a hash algorithm (e.g., SHA-256) on the original data to generate a fixed-length hash.
- Private Key Signing: Encrypt the hash with the private key to generate the digital signature.
- Attach Signature: Attach the digital signature to the original data for transmission.
- Verify Signature: The receiver uses the sender’s public key to decrypt the signature and obtain the hash.
- Compare Verification: Recalculate the hash of the received data and compare it with the decrypted hash.
Hybrid Encryption Practical Case
In practical applications, we often combine the advantages of AES and RSA to achieve a hybrid encryption scheme that is both secure and efficient:
import java.security.SecureRandom;
import java.util.Base64;
/**
* Hybrid Encryption Utility Class - Combining the advantages of AES and RSA
* AES is responsible for data encryption (fast), RSA is responsible for key encryption (high security)
*/
public class HybridCryptoUtil {
/**
* Hybrid encryption result wrapper class
*/
public static class EncryptResult {
private String encryptedData; // AES encrypted data
private String encryptedKey; // RSA encrypted AES key
private String signature; // Digital signature
public EncryptResult(String encryptedData, String encryptedKey, String signature) {
this.encryptedData = encryptedData;
this.encryptedKey = encryptedKey;
this.signature = signature;
}
// Getters
public String getEncryptedData() { return encryptedData; }
public String getEncryptedKey() { return encryptedKey; }
public String getSignature() { return signature; }
}
/**
* Hybrid encryption: Use AES to encrypt data, RSA to encrypt AES key
* @param plainText Plain text data
* @param receiverPublicKey Receiver's RSA public key
* @param senderPrivateKey Sender's RSA private key (for signing)
* @return Encryption result
*/
public static EncryptResult hybridEncrypt(String plainText,
String receiverPublicKey,
String senderPrivateKey) throws Exception {
// 1. Generate random AES key
String aesKey = AESUtil.generateKey();
// 2. Use AES to encrypt data (fast, suitable for large data)
String encryptedData = AESUtil.encrypt(plainText, aesKey);
// 3. Use receiver's public key to encrypt AES key (secure key transmission)
String encryptedKey = RSAUtil.encryptByPublicKey(aesKey, receiverPublicKey);
// 4. Use sender's private key to digitally sign the original data (identity authentication)
String signature = RSAUtil.sign(plainText, senderPrivateKey);
return new EncryptResult(encryptedData, encryptedKey, signature);
}
/**
* Hybrid decryption: Use RSA to decrypt AES key, then use AES to decrypt data
* @param encryptResult Encryption result
* @param receiverPrivateKey Receiver's RSA private key
* @param senderPublicKey Sender's RSA public key (for signature verification)
* @return Decrypted plain text
*/
public static String hybridDecrypt(EncryptResult encryptResult,
String receiverPrivateKey,
String senderPublicKey) throws Exception {
// 1. Use receiver's private key to decrypt AES key
String aesKey = RSAUtil.decryptByPrivateKey(encryptResult.getEncryptedKey(), receiverPrivateKey);
// 2. Use AES key to decrypt data
String decryptedData = AESUtil.decrypt(encryptResult.getEncryptedData(), aesKey);
// 3. Use sender's public key to verify digital signature
boolean isSignatureValid = RSAUtil.verify(decryptedData, encryptResult.getSignature(), senderPublicKey);
if (!isSignatureValid) {
throw new SecurityException("Digital signature verification failed, data may have been tampered with or the source is untrusted!");
}
return decryptedData;
}
/**
* Complete secure communication example
*/
public static void main(String[] args) {
try {
System.out.println("=== Hybrid Encryption Secure Communication Demonstration ===");
// Simulate both parties generating key pairs
RSAUtil.KeyPairInfo aliceKeyPair = RSAUtil.generateKeyPair(); // Alice's key pair
RSAUtil.KeyPairInfo bobKeyPair = RSAUtil.generateKeyPair(); // Bob's key pair
System.out.println("Alice and Bob have generated their respective RSA key pairs.");
// Sensitive data Alice wants to send to Bob
String sensitiveData = "Confidential document: New product release plan, expected investment of 5 million, expected return of 10 million, release date June 2024";
System.out.println("\nAlice wants to send the original data:");
System.out.println(sensitiveData);
// Alice uses hybrid encryption to send data to Bob
System.out.println("\n=== Alice Encrypts Data ===");
EncryptResult encryptResult = hybridEncrypt(
sensitiveData, // Data to encrypt
bobKeyPair.getPublicKey(), // Bob's public key (for encrypting AES key)
aliceKeyPair.getPrivateKey() // Alice's private key (for digital signature)
);
System.out.println("AES Encrypted Data: " + encryptResult.getEncryptedData().substring(0, 50) + "...");
System.out.println("RSA Encrypted AES Key: " + encryptResult.getEncryptedKey().substring(0, 50) + "...");
System.out.println("Digital Signature: " + encryptResult.getSignature().substring(0, 50) + "...");
// Bob receives and decrypts data
System.out.println("\n=== Bob Decrypts Data ===");
String decryptedData = hybridDecrypt(
encryptResult, // Encryption result
bobKeyPair.getPrivateKey(), // Bob's private key (for decrypting AES key)
aliceKeyPair.getPublicKey() // Alice's public key (for signature verification)
);
System.out.println("Decrypted Data:");
System.out.println(decryptedData);
// Verify data integrity
boolean isDataIntact = sensitiveData.equals(decryptedData);
System.out.println("\nData Integrity Verification: " + (isDataIntact ? "✅ Passed" : "❌ Failed"));
System.out.println("Digital Signature Verification: ✅ Passed (data source is trusted, not tampered)");
// Simulate data tampering attack
System.out.println("\n=== Simulating Data Tampering Attack ===");
try {
// Tampered encrypted data
EncryptResult tamperedResult = new EncryptResult(
encryptResult.getEncryptedData(),
encryptResult.getEncryptedKey(),
"fake_signature_by_attacker"// Forged signature
);
hybridDecrypt(tamperedResult, bobKeyPair.getPrivateKey(), aliceKeyPair.getPublicKey());
} catch (SecurityException e) {
System.out.println("🛡️ Security Protection Activated: " + e.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Applications of Digital Signatures
- Software Distribution: Ensures that software packages have not been maliciously modified.
- Electronic Contracts: Proves the integrity of contract content and the identity of the signer.
- Blockchain Transactions: Verifies the legality of transactions and prevents double-spending attacks.
- API Interfaces: Ensures that request parameters have not been tampered with.
- Document Authentication: Proves the originality and integrity of documents.
Specific Applicable Scenarios
In actual development, different business scenarios require the selection of appropriate encryption schemes. Just like choosing a mode of transportation, a bicycle for short distances and a high-speed train for long distances, each encryption algorithm has its best application scenario.
1. E-commerce Payment System
Scenario Description: When users make online payments, sensitive data such as bank card information and transaction amounts need to be protected.
Encryption Scheme:
- AES Encryption: Encrypt sensitive information such as user bank card numbers and CVV codes.
- RSA Encryption: Protect the transmission of the AES key.
- Digital Signature: Ensure the integrity and non-repudiation of transaction requests.
// E-commerce payment encryption example
public class PaymentCryptoService {
public static class PaymentInfo {
private String cardNumber; // Bank card number
private String cvv; // CVV code
private String amount; // Transaction amount
private String merchantId; // Merchant ID
// Constructor and getter/setter omitted
}
/**
* Encrypt payment information
*/
public static String encryptPaymentInfo(PaymentInfo paymentInfo, String publicKey) throws Exception {
// Convert payment information to JSON string
String paymentJson = convertToJson(paymentInfo);
// Generate random AES key
String aesKey = AESUtil.generateKey();
// Use AES to encrypt payment information
String encryptedPayment = AESUtil.encrypt(paymentJson, aesKey);
// Use RSA public key to encrypt AES key
String encryptedKey = RSAUtil.encryptByPublicKey(aesKey, publicKey);
// Combine encryption results
return encryptedKey + "|" + encryptedPayment;
}
private static String convertToJson(PaymentInfo info) {
// Simplified JSON conversion, use Jackson or Gson in actual projects
return String.format(
"{\"cardNumber\":\"%s\",\"cvv\":\"%s\",\"amount\":\"%s\",\"merchantId\":\"%s\"}",
info.cardNumber, info.cvv, info.amount, info.merchantId
);
}
}
2. Enterprise Internal Document Management
Scenario Description: Enterprises need to protect confidential documents, ensuring that only authorized personnel can access them.
Encryption Scheme:
- AES-256 Encryption: Encrypt document content.
- Role-based Key Management: Different levels of employees use different keys.
- Access Log Signing: Record document access history.
// Enterprise document encryption management
public class DocumentCryptoManager {
public enum AccessLevel {
PUBLIC("public_key"),
INTERNAL("internal_key"),
CONFIDENTIAL("confidential_key"),
TOP_SECRET("top_secret_key");
private String keyId;
AccessLevel(String keyId) {
this.keyId = keyId;
}
public String getKeyId() { return keyId; }
}
/**
* Encrypt document based on confidentiality level
*/
public static String encryptDocument(String content, AccessLevel level, String employeeId) throws Exception {
// Get corresponding encryption key based on confidentiality level
String encryptionKey = getKeyByLevel(level);
// Encrypt document content
String encryptedContent = AESUtil.encrypt(content, encryptionKey);
// Record access log (including timestamp, employee ID, operation type)
String accessLog = String.format("ENCRYPT|%s|%s|%d", employeeId, level.name(), System.currentTimeMillis());
// Sign access log (to prevent tampering)
String logSignature = RSAUtil.sign(accessLog, getSystemPrivateKey());
// Return encryption result (including encrypted content and signed log)
return encryptedContent + "|" + Base64.getEncoder().encodeToString(accessLog.getBytes()) + "|" + logSignature;
}
private static String getKeyByLevel(AccessLevel level) {
// In actual projects, get from a secure key management system
switch (level) {
case PUBLIC: return "public_aes_key_256bit";
case INTERNAL: return "internal_aes_key_256bit";
case CONFIDENTIAL: return "confidential_aes_key_256bit";
case TOP_SECRET: return "top_secret_aes_key_256bit";
default: throw new IllegalArgumentException("Unknown access level");
}
}
private static String getSystemPrivateKey() {
// Get system private key from secure storage
return "system_rsa_private_key";
}
}
3. API Interface Secure Communication
Scenario Description: API calls between microservices need to ensure data transmission security.
Encryption Scheme:
- Request Parameter Encryption: Prevent sensitive parameters from leaking.
- Timestamp Anti-Replay: Prevent replay attacks.
- Signature Verification: Ensure that the request source is trusted.
// API secure communication utility
public class ApiSecurityUtil {
public static class SecureApiRequest {
private String encryptedParams; // Encrypted request parameters
private String timestamp; // Timestamp
private String nonce; // Random number
private String signature; // Signature
// Constructor and getter/setter omitted
}
/**
* Create a secure API request
*/
public static SecureApiRequest createSecureRequest(String apiParams, String appId, String appSecret) throws Exception {
// Generate timestamp and random number
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = generateNonce();
// Generate AES key (based on appSecret)
String aesKey = generateAESKeyFromSecret(appSecret);
// Encrypt API parameters
String encryptedParams = AESUtil.encrypt(apiParams, aesKey);
// Generate signature string
String signString = String.format("appId=%s×tamp=%s&nonce=%s¶ms=%s",
appId, timestamp, nonce, encryptedParams);
// Generate signature using HMAC-SHA256
String signature = generateHMACSignature(signString, appSecret);
SecureApiRequest request = new SecureApiRequest();
request.encryptedParams = encryptedParams;
request.timestamp = timestamp;
request.nonce = nonce;
request.signature = signature;
return request;
}
/**
* Validate the security of the API request
*/
public static boolean validateRequest(SecureApiRequest request, String appId, String appSecret) throws Exception {
// 1. Validate timestamp (to prevent replay attacks)
long requestTime = Long.parseLong(request.timestamp);
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - requestTime) > 300000) { // 5 minutes validity
System.out.println("Request has expired");
return false;
}
// 2. Validate signature
String signString = String.format("appId=%s×tamp=%s&nonce=%s¶ms=%s",
appId, request.timestamp, request.nonce, request.encryptedParams);
String expectedSignature = generateHMACSignature(signString, appSecret);
if (!expectedSignature.equals(request.signature)) {
System.out.println("Signature verification failed");
return false;
}
return true;
}
private static String generateNonce() {
return UUID.randomUUID().toString().replace("-", "");
}
private static String generateAESKeyFromSecret(String appSecret) throws Exception {
// Use SHA-256 to convert appSecret to a 32-byte AES key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(appSecret.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(hash);
}
private static String generateHMACSignature(String data, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(data.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(hash);
}
}
4. Database Sensitive Field Encryption
Scenario Description: Sensitive information such as user phone numbers and ID card numbers stored in the database needs encryption protection.
Encryption Scheme:
- Field-level Encryption: Only encrypt sensitive fields.
- Reversible Encryption: Support decryption for business queries.
- Key Rotation: Regularly change encryption keys.
// Database field encryption utility
public class DatabaseFieldCrypto {
// Sensitive field enumeration
public enum SensitiveField {
PHONE("phone_key"),
ID_CARD("id_card_key"),
EMAIL("email_key"),
BANK_CARD("bank_card_key");
private String keyId;
SensitiveField(String keyId) {
this.keyId = keyId;
}
public String getKeyId() { return keyId; }
}
/**
* Encrypt sensitive field
*/
public static String encryptField(String plainValue, SensitiveField fieldType) throws Exception {
if (plainValue == null || plainValue.trim().isEmpty()) {
return plainValue;
}
// Get the corresponding encryption key for the field
String encryptionKey = getFieldEncryptionKey(fieldType);
// Use AES to encrypt
String encryptedValue = AESUtil.encrypt(plainValue, encryptionKey);
// Add field type prefix for easy identification during decryption
return fieldType.name() + ":" + encryptedValue;
}
/**
* Decrypt sensitive field
*/
public static String decryptField(String encryptedValue) throws Exception {
if (encryptedValue == null || !encryptedValue.contains(":")) {
return encryptedValue;
}
// Parse field type and encrypted value
String[] parts = encryptedValue.split(":", 2);
SensitiveField fieldType = SensitiveField.valueOf(parts[0]);
String encryptedData = parts[1];
// Get the corresponding decryption key
String decryptionKey = getFieldEncryptionKey(fieldType);
// Decrypt
return AESUtil.decrypt(encryptedData, decryptionKey);
}
/**
* User information encryption example
*/
public static class EncryptedUser {
private Long id;
private String username;
private String encryptedPhone; // Encrypted phone number
private String encryptedIdCard; // Encrypted ID card number
private String encryptedEmail; // Encrypted email
// Set phone number (automatically encrypt)
public void setPhone(String phone) throws Exception {
this.encryptedPhone = encryptField(phone, SensitiveField.PHONE);
}
// Get phone number (automatically decrypt)
public String getPhone() throws Exception {
return decryptField(this.encryptedPhone);
}
// Similar methods for other fields...
}
private static String getFieldEncryptionKey(SensitiveField fieldType) {
// In actual projects, get from key management system
// Different fields use different keys to enhance security
switch (fieldType) {
case PHONE: return "phone_encryption_key_256bit";
case ID_CARD: return "id_card_encryption_key_256bit";
case EMAIL: return "email_encryption_key_256bit";
case BANK_CARD: return "bank_card_encryption_key_256bit";
default: throw new IllegalArgumentException("Unknown field type");
}
}
}
Scenario Selection Guide
| Scenario Type | Recommended Algorithm | Key Considerations | Performance Requirements |
|---|---|---|---|
| Large File Transfer | AES + RSA Hybrid | Transmission efficiency, security | High performance |
| API Interface | AES + HMAC Signature | Anti-replay, identity authentication | Medium performance |
| Database Storage | AES Field Encryption | Query convenience, compliance | Low latency |
| Identity Authentication | RSA Digital Signature | Non-repudiation, integrity | Security first |
| Real-time Communication | Symmetric Encryption | Low latency, high concurrency | Extremely high performance |
Mock Interview Q&A
Below are common interview questions related to data encryption; mastering the answers to these questions can help you stand out in technical interviews.
Q1: What is the difference between symmetric and asymmetric encryption? What are their respective advantages and disadvantages?
Standard Answer:
Symmetric Encryption is like a universal key, using the same key for both encryption and decryption:
- Advantages: Fast speed, suitable for large data encryption, simple algorithms.
- Disadvantages: Difficult key distribution, high risk of key leakage.
- Typical Algorithms: AES, DES, 3DES.
- Application Scenarios: File encryption, database field encryption.
Asymmetric Encryption is like a mailbox system, with a public key (mailbox address) and a private key (mailbox password):
- Advantages: Secure key distribution, supports digital signatures, identity authentication.
- Disadvantages: Slow speed, not suitable for large data, complex algorithms.
- Typical Algorithms: RSA, ECC, DSA.
- Application Scenarios: Key exchange, digital signatures, identity authentication.
Interview Bonus Point: In actual projects, hybrid encryption is often used, where RSA is used to transmit the AES key, and AES is used to encrypt the actual data.
Q2: What is the difference between CBC and ECB modes in AES encryption? Why is ECB mode not recommended?
Standard Answer:
ECB Mode (Electronic Codebook Mode):
- Each plaintext block is encrypted independently, resulting in the same ciphertext block for the same plaintext block.
- Security Risks: Easily exposes data patterns, potentially leaking information structure.
- Analogy: Like using the same seal to stamp, the same content will leave the same mark.
CBC Mode (Cipher Block Chaining Mode):
- Uses an initialization vector (IV), where the encryption of each block depends on the previous block.
- Security Advantages: Produces different ciphertext for the same plaintext, hiding data patterns.
- Analogy: Like a game of solitaire, each step depends on the result of the previous step.
// Incorrect Example: ECB Mode
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // Unsafe
// Correct Example: CBC Mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Safe
Interview Bonus Point: Mention the importance of IV (initialization vector), which should be random for each encryption.
Q3: How to securely store and manage encryption keys?
Standard Answer:
Core Principles of Key Management:
- Separate Storage: Store keys and data separately, like keeping the key and safe apart.
- Access Control: Strictly control key access permissions.
- Regular Rotation: Regularly change keys to reduce leakage risks.
- Backup and Recovery: Securely back up keys to ensure business continuity.
Practical Solutions:
// 1. Environment variable storage (development environment)
String encryptionKey = System.getenv("ENCRYPTION_KEY");
// 2. Configuration file encrypted storage
@Value("${app.encryption.key}")
private String encryptionKey;
// 3. Professional key management system (production environment)
// AWS KMS, Azure Key Vault, HashiCorp Vault
public class KeyManagementService {
public String getEncryptionKey(String keyId) {
// Get key from key management system
return kmsClient.getKey(keyId);
}
}
Interview Bonus Point: Mention key lifecycle management, including generation, distribution, usage, rotation, and destruction.
Q4: What is a digital signature? How does it ensure data integrity and identity authentication?
Standard Answer:
Digital Signature is like a combination of a handwritten signature and a seal:
Working Principle:
- Generate Hash: Calculate the hash value (e.g., SHA-256) of the original data.
- Private Key Signing: Encrypt the hash with the private key to generate the digital signature.
- Verify Signature: The receiver decrypts the signature with the public key and compares the hash.
Guaranteed Security Features:
- Integrity: If the data is tampered with, the hash will change, and signature verification will fail.
- Identity Authentication: Only the private key holder can generate a valid signature.
- Non-repudiation: The signer cannot deny the signed data.
// Digital signature example
public class DigitalSignatureDemo {
public static void signAndVerify() throws Exception {
String data = "Important contract content";
// 1. Generate signature
String signature = RSAUtil.sign(data, privateKey);
// 2. Verify signature
boolean isValid = RSAUtil.verify(data, signature, publicKey);
System.out.println("Signature Verification Result: " + isValid);
}
}
Interview Bonus Point: Explain the difference between digital signatures and encryption; signatures are for verification, while encryption is for confidentiality.
Q5: How to optimize encryption and decryption performance in high-concurrency scenarios?
Standard Answer:
Performance Optimization Strategies:
- Algorithm Selection Optimization:
// Choose high-performance encryption algorithms
// AES > 3DES > DES (performance and security)
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // GCM mode has better performance
- Object Pooling:
// Cipher object pool to avoid repeated creation
public class CipherPool {
private static final ObjectPool<Cipher> cipherPool =
new GenericObjectPool<>(new CipherFactory());
public static String encrypt(String data, String key) throws Exception {
Cipher cipher = cipherPool.borrowObject();
try {
// Perform encryption operation
return doEncrypt(cipher, data, key);
} finally {
cipherPool.returnObject(cipher);
}
}
}
- Asynchronous Processing:
// Asynchronous encryption, does not block the main thread
@Async
public CompletableFuture<String> encryptAsync(String data) {
return CompletableFuture.supplyAsync(() -> {
try {
return AESUtil.encrypt(data, encryptionKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
- Cache Strategy:
// Cache encryption results to avoid repeated calculations
@Cacheable(value = "encryptedData", key = "#data.hashCode()")
public String encryptWithCache(String data) throws Exception {
return AESUtil.encrypt(data, encryptionKey);
}
Interview Bonus Point: Mention hardware acceleration (AES-NI instruction set) and distributed encryption services.
Q6: How to prevent common encryption attacks, such as replay attacks and man-in-the-middle attacks?
Standard Answer:
1. Prevent Replay Attacks:
// Use timestamp + random number
public class AntiReplayUtil {
private static final Set<String> usedNonces = new ConcurrentHashMap<>();
public static boolean validateRequest(String timestamp, String nonce) {
// Check if the timestamp is within the valid period (e.g., 5 minutes)
long requestTime = Long.parseLong(timestamp);
if (System.currentTimeMillis() - requestTime > 300000) {
return false;
}
// Check if the nonce has been used
String key = timestamp + ":" + nonce;
return usedNonces.add(key); // Returns false if it already exists
}
}
2. Prevent Man-in-the-Middle Attacks:
// Use certificate verification and HTTPS
public class SecureHttpClient {
public static void setupSSL() {
// Verify server certificate
System.setProperty("javax.net.ssl.trustStore", "truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
// Enable certificate chain verification
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> {
// Verify hostname
return hostname.equals("trusted-server.com");
}
);
}
}
3. Prevent Brute Force Attacks:
// Use strong keys and salt
public class SecureKeyGeneration {
public static String generateSecureKey(String password, String salt) throws Exception {
// Use PBKDF2 to increase cracking difficulty
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(),
salt.getBytes(),
100000, // Iteration count
256); // Key length
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey key = factory.generateSecret(spec);
return Base64.getEncoder().encodeToString(key.getEncoded());
}
}
Interview Bonus Point: Mention advanced attack prevention methods such as side-channel attacks and timing attacks.
Q7: In a microservices architecture, how to design secure inter-service communication?
Standard Answer:
Microservices Secure Communication Scheme:
- JWT Token Authentication:
// JWT generation and verification
public class JWTUtil {
private static final String SECRET = "your-secret-key";
public static String generateToken(String userId, String role) {
return Jwts.builder()
.setSubject(userId)
.claim("role", role)
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public static Claims validateToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
- API Gateway Unified Authentication:
// Security filtering at the gateway level
@Component
public class SecurityGatewayFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// Verify JWT Token
String token = request.getHeaders().getFirst("Authorization");
if (!JWTUtil.isValidToken(token)) {
return unauthorized(exchange);
}
// Verify API signature
if (!ApiSecurityUtil.validateSignature(request)) {
return forbidden(exchange);
}
return chain.filter(exchange);
}
}
- Service Mesh Security:
# Istio security policy example
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT # Enforce mTLS
Interview Bonus Point: Mention modern microservices security concepts such as zero trust architecture, service mesh (Istio), and mTLS.
Conclusion
Data security is not achieved overnight; it requires continuous attention and improvement. Just like building a sturdy castle, every aspect from the foundation to the walls, from the moat to the sentinels, must not be neglected.
I hope this article can serve as a helpful assistant on your journey in data encryption, helping you build more secure and reliable application systems. Remember, security is no small matter; details determine success or failure. In this data-driven era, mastering data encryption technology is not only a reflection of technical capability but also a safeguard of user trust.
Let us work together in the world of code to build an unbreakable line of defense for data security!