我正在尝试在 Android (Java) 中与 JWE 一起实现 ECDH 加密/解密。
我发现jose4j和Nimbus JOSE库旨在满足我的所有需求,但似乎比我想象的更具挑战性。
如果有人熟悉,那就是 3D Secure 2.0 ......
在下面的规范中:
- SDK =本地端
- DS = 目录服务器(另一端)
接下来是规格:
- 给定:P(DS) - EC 公钥(以 PEM 格式提供,可以转换为 PublicKey 或 JWK)
- 生成一个新的临时密钥对 (Q(SDK), d(SDK))
- 根据 JWA (RFC7518) 在 Direct Key Agreement 模式下使用曲线 P-256、d(SDK) 和 P(DS) 进行 Diffie-Hellman 密钥交换过程以生成 CEK。此版本规范支持的参数值为:
- “alg”:ECDH-ES
- “apv”:目录服务器 ID
- “epk”:P(DS),inJSONWebKey(JWK)格式{“kty”:“EC”,“crv”:“P-256”}
- 所有其他参数:不存在
- CEK:"kty":oct-256bits
- 生成 128 位随机数据作为 IV
- 使用 CEK 和 JWE 紧凑序列化根据 JWE (RFC7516) 加密 JSON 对象。此版本规范支持的参数值为:
- “alg”:目录
- “epk”:Q(SDK)作为{“kty”:“EC”,“crv”:“P-256”}
- “enc”:“A128CBC-HS256”或“A128GCM”
- 所有其他参数:不存在
- 如果算法是 A128CBC-HS256,则使用完整的 CEK,或者如果算法是 A128GCM,则使用 CEK 的最左边 128 位。
- 删除临时密钥对 (Q(SDK),d(SDK))
- 将生成的 JWE 作为 SDK 加密数据提供给 3DS 服务器
如果有人已经实现了这个确切的规范并且可以分享代码,那就太棒了!!
在 jose4j 的示例中有一个使用 ECDH 创建 JWT 的示例:
https ://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples (最后一个示例,标题为“生产和使用嵌套的(签名和加密的)JWT ”)。
但是这个例子并不是我所需要的。当我需要加密文本时,它会创建一个令牌。
从上面规范中的 "CEK:"kty":oct-256bits" 开始,我不明白该怎么做。
这是我使用 Nimbus lib 的代码(到目前为止):
public String nimbus_encrypt(String plainJson, ECPublicKey otherPublicKey, String directoryServerId) throws JOSEException {
JWEHeader jweHeader = new JWEHeader(
JWEAlgorithm.ECDH_ES,
EncryptionMethod.A128CBC_HS256,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Base64URL.encode(directoryServerId),
null,
0,
null,
null,
null,
null);
JWEObject jwe = new JWEObject(jweHeader, new Payload(plainJson));
jwe.encrypt(new ECDHEncrypter(otherPublicKey));
String serializedJwe = jwe.serialize();
Log.d("[ENCRYPTION]", "nimbus_encrypt: jwe = " + jwe.getHeader());
Log.d("[ENCRYPTION]", "nimbus_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
这是雨云输出:
nimbus_encrypt: jwe = {"epk":{"kty":"EC","crv":"P-256","x":"AS0GRfAOWIDONXxaPR_4IuNHcDIUJPHbACjG5L7x-nQ","y":"xonFn1vRASKUTdCkFTwsl16LRmSe-bAF8EO4-mh1NYw"} ,"apv":"RjAwMDAwMDAwMQ","enc":"A128CBC-HS256","alg":"ECDH-ES"}
nimbus_encrypt: serializedJwe = eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJBUzBHUmZBT1dJRE9OWHhhUFJfNEl1TkhjRElVSlBIYkFDakc1TDd4LW5RIiwieSI6InhvbkZuMXZSQVNLVVRkQ2tGVHdzbDE2TFJtU2UtYkFGOEVPNC1taDFOWXcifSwiYXB2IjoiUmpBd01EQXdNREF3TVEiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiRUNESC1FUyJ9..Pi48b7uj3UilvVXKewFacg.0sx9OkHxxtZvkVm-IENRFw.bu5GvOAwcZxdxaDKWIBqwA
这是我的代码(到目前为止,使用@Brian-Campbell 的答案)使用 jose4j lib:
public String jose4j_encrypt(String plainJson, PublicKey otherPublicKey, String directoryServerId) throws JoseException {
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation(directoryServerId));
jwe.setKey(otherPublicKey);
jwe.setPayload(plainJson);
String serializedJwe = jwe.getCompactSerialization();
Log.d("[ENCRYPTION]", "jose4j_encrypt: jwe = " + jwe);
Log.d("[ENCRYPTION]", "jose4j_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
这是 jose4j 输出:
jose4j_encrypt: jwe = JsonWebEncryption{"alg":"ECDH-ES","enc":"A128CBC-HS256","apv":"RjAwMDAwMDAwMQ","epk":{"kty":"EC","x" :"prvyhexJXDWvPQmPA1xBjY8mkHEbrEiJ4Dr-7_5YfdQ","y":"fPjw8UdfzgkVTppPSN5o_wprItKLwecoia9yrWi38yo","crv":"P-256"}}
jose4j_encrypt: serializedJwe = eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImFwdiI6IlJqQXdNREF3TURBd01RIiwiZXBrIjp7Imt0eSI6IkVDIiwieCI6InBydnloZXhKWERXdlBRbVBBMXhCalk4bWtIRWJyRWlKNERyLTdfNVlmZFEiLCJ5IjoiZlBqdzhVZGZ6Z2tWVHBwUFNONW9fd3BySXRLTHdlY29pYTl5cldpMzh5byIsImNydiI6IlAtMjU2In19..gxWYwFQSOqLk5HAgs7acdA.mUIHBiWpWSlQaEOJ_EZGYA.eiTe-88fw-Jfuhji_W0rtg
可以看出,最终结果中的“alg”标头是“ECDH-ES”,而不是“dir”。
如果我要实现通信的双方就足够了,但是有了这个规范,这里似乎缺少许多配置......
使用 jose4j 的代码更长,似乎更可配置,但我无法构建足够有价值的东西来发布在这里。
对我来说主要缺少的部分是如何从上面的规范生成 CEK。
谢谢你。
编辑
在上面添加了 jose4j 代码并添加了输出...