Hybrid RSA‑AES Encryption for Secure API Parameter Transmission in Java
This article recounts a leaderboard data breach caused by tampered Base64 request parameters, then details a hybrid RSA‑AES encryption strategy—using RSA to protect the AES key, IV and timestamp while AES encrypts the actual payload—along with Java implementations, key‑generation utilities, and a Spring AOP aspect for automatic decryption, providing a comprehensive guide to securing API communications.
The author describes an incident where a game’s leaderboard scores were corrupted because a user altered the Base64‑encoded request parameters, prompting a redesign of the API security strategy.
He explains the fundamentals of asymmetric (RSA) and symmetric (AES) encryption, their advantages, padding modes, key sizes, and formats (DER/PEM).
He proposes a hybrid encryption scheme: the client encrypts the actual request data with AES, then encrypts the AES key, IV and timestamp with RSA; both ciphertexts (asy and sym) are sent to the server.
The server decrypts the RSA part to obtain the AES key and IV, validates the timestamp, and then decrypts the AES payload to retrieve the original parameters.
Implementation details include utility classes (ActivityRSAUtil, AES256Util, RequestDecryptionUtil) and a custom annotation @RequestRSA with an AOP aspect that intercepts controller methods, extracts and decrypts the encrypted body, and injects the plain object into the method arguments.
{
"integral": "MTExMTM0NzY5NQ==",
}Key‑pair generation example:
public static KeyPair getKeyPair(int keyLength) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keyLength);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("生成密钥对时遇到异常" + e.getMessage());
}
}Custom annotation definition:
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestRSA {}AOP aspect for automatic decryption:
@Aspect
@Component
@Order(2)
public class RequestRSAAspect {
@Pointcut("execution(public * app.activity.controller.*.*(..))")
public void requestRAS() {}
@Around("requestRAS()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
RequestRSA annotation = method.getAnnotation(RequestRSA.class);
if (annotation != null) {
Object data = getParameter(method, joinPoint.getArgs());
String body = JSONObject.toJSONString(data);
JSONObject jsonObject = JSONObject.parseObject(body);
String asy = jsonObject.get("asy").toString();
String sym = jsonObject.get("sym").toString();
JSONObject decryption = RequestDecryptionUtil.getRequestDecryption(sym, asy);
Class
paramClass = joinPoint.getArgs()[0].getClass();
Object paramObj = JSONObject.parseObject(decryption.toJSONString(), paramClass);
return joinPoint.proceed(new Object[]{paramObj});
}
return joinPoint.proceed();
}
// ... (parameter extraction method omitted for brevity)
}RSA decryption utility excerpt:
public static
Object getRequestDecryption(String sym, String asy, Class
clazz) {
try {
RSAPrivateKey privateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
String rsaJson = ActivityRSAUtil.privateDecrypt(sym, privateKey);
RSADecodeData decodeData = JSONObject.parseObject(rsaJson, RSADecodeData.class);
if (System.currentTimeMillis() - decodeData.getTime() > timeout) {
throw new ServiceException("Request timed out, please try again.");
}
String aesJson = AES256Util.decode(decodeData.getKey(), asy, decodeData.getKeyVI());
return JSONObject.parseObject(aesJson, clazz);
} catch (Exception e) {
throw new RuntimeException("RSA decryption Exception: " + e.getMessage());
}
}AES utility example (CBC mode with PKCS7 padding):
public static String encode(String key, String content, String keyVI) {
try {
SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(keyVI.getBytes()));
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}Finally, the author emphasizes using appropriate key lengths (e.g., 2048‑bit RSA), proper IV handling, and invites readers to join a backend‑focused technical community.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.