JWE:安全传输敏感数据的最佳实践 (上)
JWE:安全传输敏感数据的最佳实践 (上)
- 起源
近期工作重点涉及开发几个API,在确保信息安全的前提下(PS: 实际上这些接口已经采用了HTTPS协议加密传输方式),其中涉及的数据具有敏感性,在确保信息安全的前提下最好避免直接暴露这些数据

面对数据安全的考量时, 老板通过互联网环境下的多次尝试, 最终了解到JSON Web Encryption(JWE)这一技术, 觉得这或许正是适用之选; 研究过程中难免会有一些误操作, 常常需要反复调试才能找到问题所在
- JWE 是什么?
JWE代表"JSON Web Encryption"这一标准缩写。
该规范详细说明了如何将敏感信息安全地编码为可解密的形式。
它采用了一种称为"JWE Compact Serialization"(JSK)的技术来高效地压缩加密后的信息。
通过这种机制,在网络中传输敏感信息时能够有效保护其安全。
JWE采用加密算法以确保信息机要性,并利用消息认证码(MAC)算法以确保数据完整度;此外该技术还涵盖多种密钥管理方案例如在一种情况下将密钥嵌入编码块中或者通过秘密共享协议获取所需密钥
JWE主要用于保护敏感网络数据。
如用户凭证、支付信息等关键信息。
它属于JSON Web Token(JWT)标准的一部分。
JSON Web Token方案通过JWE实现安全功能。
好了,上面搬来了八股文,还不如直接通过代码操作
在项目中引入maven依赖项时,请注意nimbus-jose-jwt属于JWT的技术实现方案之一,并不复杂。
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.8.1</version>
</dependency>
public class JweRSADemo {
public static String encrypt(String payload, RSAPublicKey publicKey) throws Exception {
// 创建加密器
JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM).build();
JWEEncrypter jweEncrypter = new RSAEncrypter(publicKey);
// 加密JSON数据
Payload jwePayload = new Payload(payload);
JWEObject jweObject = new JWEObject(header, jwePayload);
jweObject.encrypt(jweEncrypter);
// 将JWE对象转换为JWE字符串
return jweObject.serialize();
}
public static String decrypt(String jwe, RSAPrivateKey privateKey) throws Exception {
// 创建解密器
JWEDecrypter jweDecrypter = new RSADecrypter(privateKey);
// 解密JWE字符串
JWEObject jweObject = JWEObject.parse(jwe);
jweObject.decrypt(jweDecrypter);
// 将解密后的JSON数据转换为JSONObject对象
Payload payload = jweObject.getPayload();
return payload.toString();
}
public static void main(String[] args) throws Exception {
// 创建一个JSON对象
JSONObject payload = new JSONObject();
payload.put("name", "Alice");
payload.put("age", 30);
String jsonString = payload.toJSONString();
// 生成RSA密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 加密JSON对象
String jwe = encrypt(jsonString, publicKey);
System.out.println("JWE: " + jwe);
// 解密JWE字符串
String decryptedPayload = decrypt(jwe, privateKey);
System.out.println("Decrypted payload: " + decryptedPayload);
}
}
成功进行加解密

- 接入Spring Boot
先生成一对公钥和私钥
// 生成RSA密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
System.out.println("Base64.encode(publicKey.getEncoded()) = " + Base64.encode(publicKey.getEncoded()));
System.out.println("Base64.encode(privateKey.getEncoded()) = " + Base64.encode(privateKey.getEncoded()));

以生成的公钥加密数据为基础,在线服务会将这些密钥发送给客户端。其结果随后将用于作为参数供后续处理调用。
String publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsqBTYOqBU5rdpdRX9/owxi0nHsxsyGr/8PWQZ8GtPQilXfNuQEFWO01o0ZU8Agn+wzatpGCOwIgSaEF4p6uI4tyhC8BlnkQZN/GcjbryljzIrRlu8NKsLu40SQO3CYa+p7DA36pdX/nMQ9QceSeGW9e7E5YWuiMhUDgqWVEdmRxX+bxfoNYpBtNlvyxeL0MKEIUVlikYKe+UxKhiPRcwjBIiHKfsY2PszP4h485UjcIWveyJjBu8nEpUgHUqUHdA1nm32WmUMcOhTU41tX6ZoYK6qUBeNX4xnfOG7jnE76Vz2QcsVTnnTqTQ/93nxEIrZsSfQ/vdzi8LryY2xS0EmQIDAQAB";
PublicKey publicKey = getPublicKey(publicKeyStr);
//playload 可以是json串,也可以是普通字符串
String encrypt = encrypt("jwe 测试", publicKey);
System.out.println(encrypt);

我们再写一个Controller来接受参数并用私钥解密
@RestController
public class JWEController {
private static final Logger LOGGER = LoggerFactory.getLogger(JWEController.class);
private final PrivateKey privateKey;
/** * 在构造方法里初始化刚刚生成的私钥
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public JWEController() throws NoSuchAlgorithmException, InvalidKeySpecException {
String privateKeyBase64 = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCyoFNg6oFTmt2l1Ff3+jDGLScezGzIav/w9ZBnwa09CKVd825AQVY7TWjRlTwCCf7DNq2kYI7AiBJoQXinq4ji3KELwGWeRBk38ZyNuvKWPMitGW7w0qwu7jRJA7cJhr6nsMDfql1f+cxD1Bx5J4Zb17sTlha6IyFQOCpZUR2ZHFf5vF+g1ikG02W/LF4vQwoQhRWWKRgp75TEqGI9FzCMEiIcp+xjY+zM/iHjzlSNwha97ImMG7ycSlSAdSpQd0DWebfZaZQxw6FNTjW1fpmhgrqpQF41fjGd84buOcTvpXPZByxVOedOpND/3efEQitmxJ9D+93OLwuvJjbFLQSZAgMBAAECggEAJHg4XcazPeMWEufuR/pkX+nTHWYmZar283bnk0+HM7lirfJoFaVhWj09Q+EgvdfVlHzC6hcuvh9qBrArVqxeh9b86H3RIYWM0o+5Y3SCV+s0G6dgL7oLno9SzH9+LOs+XNVpI6FQbCp/qm+RmqjXtUOv9dlEbZ+DizHUb6TwkpRPMo3BHPUKGpajmP0pgSkQz8x4MWJ0a6SVST+/E7ZbwF8PobzOQCxND2yZQah/TY1EvCCyOM6chGm0loCyC4HGTBmDm/0LcWRqgiI8GiglEijGu2Iha6UR11JaEStHoc1Sy38Orj377bhndm2l19HNOQIslp9m0VP4WUSG3GJDKwKBgQC55+HY29F0H2jy7p94+snPCKVG0uJHu0IKhFN09fChP/1q0PLJuz3Vj07YUjGneSL3RHM1D+HbMYAGnZ+7GFmwIi8FLKBVZDx4L7ENhcPMUM2q0rsBphZuHfD14Hf9I0xUiNrpyTLuVPJfK3kzx2NYQmRGo6tY+INDuI5L2HEELwKBgQD1+c4ilEQtTRvoIa+BfZ/oOBeFaYsQGlLL+8yVbsPsfH+0MMoiIY++my5rDhT//8QXNhnOI1cs7CFKELA/99Mk7y9G/4o+cMb1kAyZJU6zNoSe9Eitdyo0qQQ20NVAW+YWX0zAuAm5bttk1RvVGz/wiuOotVw6oCSR0uUYUWCptwKBgB2psy6f/G6z6FIC4y0xjuva7Ew9r99UMLhu3sYly+xewne9uU+Y8cfWovT/QG8BdCPSJzPLQfVwk4X6tpbqzry855XCxh557PAcY/rNYi2Cox5jm3Uq5B9T5bPFyj9412ARqiRtdxPyN+4ZiLBLWz2k8k0XJmr+1CsFEqdldLr/AoGAEjyqLuAlSeKMriJJO+WPhI0cGVUg7Vm2R89sdKvYtODqKvbvFaa9XJlu0JsjrXNOG5Z0RVdTcE41jaM9HhEGw5dEPxRVMJn19mDuvjAI7LqfDJX6CXprU6owWMwU84ecwI3iR+udNPVmKMywGpXBoNj7VhfUNbiH3ZPwTmRCMXMCgYBi5I1KJ7kyaHKTilJEAhiYv6XBwsJScJkdAXWuA/SdG3aWQAEc4SOrRwqmqbHWYOm827tb20kG09rHnVS+tVSShvCkv4OcAr2X0L04IX9OfvUqI5pPWiQd/VzCQrTPcelixgkUkG4Sc2dRr6gvvFOKFAAVTQrePh9clk8kd3bg+g==";
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBase64.getBytes(StandardCharsets.UTF_8));
KeyFactory keyFactory;
keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec);
}
@GetMapping("/jwe")
public void jwe(@RequestParam String encrypt) throws Exception {
LOGGER.info("接受到的密文:{}", encrypt);
String decrypt = decrypt(encrypt, (RSAPrivateKey) privateKey);
LOGGER.info("解密后的内容:{}", decrypt);
}
public String decrypt(String jwe, RSAPrivateKey privateKey) throws Exception {
// 创建解密器
JWEDecrypter jweDecrypter = new RSADecrypter(privateKey);
// 解密JWE字符串
JWEObject jweObject = JWEObject.parse(jwe);
jweObject.decrypt(jweDecrypter);
// 将解密后的JSON数据转换为JSONObject对象
Payload payload = jweObject.getPayload();
return payload.toString();
}
}
接下来用刚刚公钥生成的加密数据作为参数,调用接口

我们看控制台输出,接口调用成功,并成功解密


