Securing Your Spring APIs: A Guide to AES, RSA, SHA, and HMAC
Introduction
In the world of Spring Boot development, security is often treated like an onion - multiple layers protecting the core data. But with acronyms like AES, RSA, SHA, and HMAC, it's easy to get confused.
Are they all "encryption"? (No). Can they be used interchangeably? (Absolute not).
1. AES (Advanced Encryption Standard)
Type: Symmetric Encryption
AES is a symmetric encryption algorithm standardized by the National Institute of Standards and Technology (NIST) in 2001. It is based on the Rijndael cipher developed by Belgian cryptographers. You use the same key to both encrypt and decrypt data. Think of it as a high-tech physical safe.
- Best for: Encrypting large amounts of data, such as sensetive fields in your database (e.g., Credit Card numbers,...)
- Pros:
- Very high security - no known practical attacks exist
- Performance - efficient in software and hardware implementations
- Flexible key size - 128, 192 or 256 bit keys
- Widely adopted and implemented in protocols and standards like TLS, SSH, IPsec, WiFi security
- Cons: Key management is tricky. If a hacker steals he key, they can unlock everything.
Example:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AesUtil {
private static final String KEY = "1234567890123456"; // 16 bytes for AES-128
public static String encrypt(String data) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secret_key);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
}
public static String decrypt(String encryptedData) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decoded = Base64.getDecoder().decode(encryptedData);
return new String(cipher.doFinal(decoded));
}
public static void main(String[] args) throws Exception {
String sensitiveData = "012000215";
String encrypted = encrypt(sensitiveData);
System.out.println("Encrypted data: " + encrypted);
System.out.println("Decrypted data: " + decrypt(encrypted));
}
}
2. RSA (Rivest-Shamir-Adleman)
Type: Asymmetric Encryption
RSA uses a Key Pair: a Public Key (which you give to the world) and a Private Key (wich you guard with your life). It was invented by Ron Rivest, Adi Shamir and Leonard Adleman in 1977.
- Best for: Digital signatures and secure key exchange. In Spring Security, RSA is the "heavy lifter" used to sign JWTs (JSON Web Tokens) in Microservices.
- Pros:
- Very secure if large enough keys are used (e.g. 2048 bits or more)
- Scalable and flexible to use with different key sizes
- Widely used for secure data transmission and digital signatures
- Supported in standards like PKCS, TLS, SSH, S/MIME, PGP
- Cons: Much slower than AES, not suitable for large files.
Example:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
public class RsaExample {
public static void main(String[] args) throws Exception {
// Create Public/Private key pair
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
PrivateKey privKey = pair.getPrivate();
PublicKey pubKey = pair.getPublic();
System.out.println("Private Key (used to sign Token): " +
Base64.getEncoder().encodeToString(privKey.getEncoded()));
System.out.println("Public Key (used to authenticate Token): " +
Base64.getEncoder().encodeToString(pubKey.getEncoded()));
}
}
3. SHA (Secure Hash Algorithm)
Type: Hashing (One-way)
Hash functions like SHA are used to generate fixed-length message digests from input data. These digests protect integrity as any change to the input will affect the hash value.
Hashing is NOT encryption. You cannot "decrypt" a SHA-256 hash. It turns any input into a unique, fixed-lengh string.
- Best fore: Integrity checks and password storage (when combined with BCrypt that is a password hashing in Spring Security).
- Pros:
- Widely used to verify data integrity and for digital signatures.
- Resistant to preimage and second preimage attacks.
- Standardized and commonly implemented in software and hardware.
- Allows use of higher security variants like SHA-2 and SHA-3 as they emerge.
- Cons: Raw SHA-256 is vulnerable to "Rainbow Table" attacks (pre-computed lists of hashes), which is why we use Salting
Example:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class HashExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "mySecretPassword123";
// Băm mật khẩu để lưu vào Database
String encodedPassword = encoder.encode(rawPassword);
System.out.println("Chuỗi băm lưu trong DB: " + encodedPassword);
// Khi người dùng đăng nhập, kiểm tra khớp hay không
boolean isMatch = encoder.matches("mySecretPassword123", encodedPassword);
System.out.println("Mật khẩu khớp: " + isMatch);
}
}
4. HMAC (Hash-based Message Authentication Code)
Type: Keyed Hashing
HMAC is a specific construction that combines a Hash function like (SHA-256) with Secret Key. It proves both Integrity (data wasn't changed) and Authenticity (the sender is who they say they are).
- Best For: Signing API requests and JWTs (the HS256 algorithm).
- Why use it? Plain SHA tells you if the data changed. HMAC tells you if the data changed and if the person who sent it knows your secret key.
Example:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
public class JwtHmacExample {
public static void main(String[] args) {
// 1. Create a Secret Key for HMAC-SHA256
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 2. Create JWT (Signing)
String jws = Jwts.builder()
.setSubject("User123")
.signWith(key) // use HMAC + SHA
.compact();
System.out.println("JWT Token: " + jws);
// 3. Verify JWT (Verification)
try {
String user = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jws)
.getBody()
.getSubject();
System.out.println("Successful authenticate for user: " + user);
} catch (Exception e) {
System.out.println("Token invalid!");
}
}
}
Conclusion
If you are building a modern Spring Boot API:
- Use BCrypt (which uses hashing) for passwords.
- Use RSA (RS256) or HMAC (HS256) for JWTs.
- Use AES if you need to store data in a database that you eventually need to read back in plain text.
