Symmetric and Asymmetric Encryption, Digital Signatures, HTTPS, and Secure URL Handling with Spring Cloud Gateway
This article explains the fundamentals of symmetric and asymmetric encryption, how digital signatures and HTTPS work with certificate authorities, and demonstrates a practical implementation of secure URL encryption, key exchange, and request validation using custom Spring Cloud Gateway filters written in Java.
Symmetric Encryption
Symmetric encryption uses the same key for both encryption and decryption. The basic workflow includes key generation, encrypting the original data with the shared key, transmitting the ciphertext, and finally decrypting it with the same key.
Asymmetric Encryption
Asymmetric encryption employs a public‑key/private‑key pair. The steps are: generate a key pair, distribute the public key, encrypt data with the public key, transmit the ciphertext, and decrypt it with the private key.
Combined Use
In real‑world applications, asymmetric encryption is used to securely exchange a symmetric key, after which symmetric encryption (e.g., AES) protects the bulk data for efficiency and security.
Digital Signature
A digital signature is created by hashing the original data, encrypting the hash with the sender’s private key, and sending both the data and the encrypted hash. The receiver uses the sender’s public key to decrypt the hash and compare it with a locally computed hash to verify integrity and authenticity.
HTTPS and Certificate Authority (CA)
HTTPS is not a separate protocol; it secures HTTP traffic using TLS/SSL. A CA issues a digital certificate containing the server’s public key and identity information. During the TLS handshake, the client validates the certificate, negotiates a session key (often a symmetric key), and uses it to encrypt all subsequent traffic, ensuring confidentiality and integrity.
Gateway Filter Chain
Custom global filters in Spring Cloud Gateway implement the method public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) . By implementing Ordered , the execution order can be controlled. Filters can mutate the request/response, for example using exchange.mutate() , and can access attributes such as DefaultServerWebExchange and ForwardRoutingFilter to influence routing.
Key Exchange and Symmetric Key Storage
The following filter decrypts an RSA‑encrypted symmetric key from the request header, converts it to a UTF‑8 string, and stores it in Redis for later use.
public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String encryptedSymmetricKey = exchange.getRequest().getHeaders().getFirst("X-Encrypted-Symmetric-Key");
if (encryptedSymmetricKey != null) {
try {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, securityConfig.getKeyPair().getPrivate());
byte[] decryptedKeyBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedSymmetricKey));
String symmetricKey = new String(decryptedKeyBytes, StandardCharsets.UTF_8);
String redisSymmetricKey = "symmetric:key:" + 1;
stringRedisTemplate.opsForValue().set(redisSymmetricKey, symmetricKey);
} catch (Exception e) {
e.printStackTrace();
String responseBody = "there are something wrong occurs when decrypt your key!!!";
GatewayUtil.responseMessage(exchange, responseBody);
}
}
return chain.filter(exchange);
}RSA Public‑Key Endpoint (SecurityConfig)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import javax.annotation.PostConstruct;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* @author: 张锦标
* @date: 2023/10/2 15:13
* SecurityConfig provides the RSA public key.
*/
@Configuration
public class SecurityConfig {
private KeyPair keyPair;
@PostConstruct
public void init() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
keyPair = keyGen.genKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to generate RSA key pair", e);
}
}
@Bean
public RouterFunction
publicKeyEndpoint() {
return RouterFunctions.route()
.GET("/public-key", req -> {
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
return ServerResponse.ok().bodyValue(publicKey);
})
.build();
}
public KeyPair getKeyPair() {
return keyPair;
}
}RSA Encryption Example (Client Side)
package blossom.star.project.product;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RSA {
public static void main(String[] args) throws Exception {
String publicKeyPEM = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXBSqSyOPb01/...";
String symmetricKey = "zhangjinbiao6666";
byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedSymmetricKey = cipher.doFinal(symmetricKey.getBytes());
String encryptedSymmetricKeyBase64 = Base64.getEncoder().encodeToString(encryptedSymmetricKey);
System.out.println(encryptedSymmetricKeyBase64);
}
}URL Dynamic Encryption (AES Helper)
public class AES {
public static void main(String[] args) throws Exception {
String plaintext = "productId=1";
String symmetricKey = "zhangjinbiao6666"; // 16 bytes
String encryptedText = encryptUrl(plaintext, symmetricKey);
System.out.println(encryptedText);
}
public static String encryptUrl(String url, String symmetricKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(url.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
}CryptoFilter – Decrypt URL and Verify Signature
@Component
public class CryptoFilter implements GlobalFilter, Ordered {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private CryptoHelper cryptoHelper;
@Override
public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String redisSymmetricKey = "symmetric:key:" + 1;
String symmetricKey = stringRedisTemplate.opsForValue().get(redisSymmetricKey);
if (symmetricKey == null) {
return GatewayUtil.responseMessage(exchange, "this session has not symmetricKey!!!");
}
try {
String encryptedUrl = exchange.getRequest().getURI().toString();
String path = exchange.getRequest().getURI().getPath();
String encryptPathParam = path.substring(path.indexOf("/encrypt/") + 9);
String decryptedPathParam = cryptoHelper.decryptUrl(encryptPathParam, symmetricKey);
String decryptedUri = encryptedUrl.substring(0, encryptedUrl.indexOf("/encrypt/"))
.concat("?").concat(decryptedPathParam);
exchange = exchange.mutate().request(build -> {
try {
build.uri(new URI(decryptedUri));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}).build();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(decryptedUri).build();
MultiValueMap
decryptedQueryParams = uriComponents.getQueryParams();
String signature = decryptedQueryParams.getFirst("signature");
if (!cryptoHelper.verifySignature(decryptedQueryParams, signature, symmetricKey)) {
return GatewayUtil.responseMessage(exchange, "the param has something wrong!!!");
}
} catch (Exception e) {
return GatewayUtil.responseMessage(exchange, "the internal server occurs an error!!!");
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -200;
}
}CryptoHelper – Decrypt and Verify
@Configuration
public class CryptoHelper {
public String decryptUrl(String encryptedUrl, String symmetricKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedUrl));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public boolean verifySignature(MultiValueMap
queryParams, String signature, String symmetricKey) throws Exception {
StringBuilder sb = new StringBuilder();
for (Map.Entry
> entry : queryParams.entrySet()) {
if (!"signature".equals(entry.getKey())) {
sb.append(entry.getKey()).append("=").append(String.join(",", entry.getValue())).append("&");
}
}
sb.setLength(sb.length() - 1);
String computedSignature = encryptRequestParam(sb.toString(), symmetricKey);
return computedSignature.equals(signature);
}
public static String encryptRequestParam(String requestParam, String symmetricKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(requestParam.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
}The article concludes with a reminder that the content originates from a Juejin post and includes community promotion messages, which are not part of the technical explanation.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.