使用jose4j的ECDH + JWE加密/解密

时间:2019-05-12 06:58:03

标签: java 3d-secure jwe jose4j ecdh

我正在尝试在Android(Java)中与JWE一起实现ECDH加密/解密。
我发现jose4jNimbus JOSE库旨在满足我的所有需求,但似乎比我想象的更具挑战性。

如果有人熟悉,那么它适用于3D Secure 2.0 ...

在以下规范中:

  • SDK = local
  • 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”:DirectoryServerID
    •   
    • “ epk”:P(DS),inJSONWebKey(JWK)format {“ kty”:“ EC”,“ crv”:“ P-256”}
    •   
    • 所有其他参数:不存在
    •   
  •   
  • CEK:“ kty”:oct-256位
  •   
  • 生成IV的128位随机数据​​
  •   
  • 使用CEK和JWE Compact序列化根据JWE(RFC7516)加密JSON对象。此版本的规范中支持的参数值为:      
        
    • “ alg”:dir
    •   
    • “ epk”:Q(SDK)as {“ 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的输出:

  

nimbus_encrypt:jwe = {“ epk”:{“ kty”:“ EC”,“ crv”:“ P-256”,“ x”:“ AS0GRfAOWIDONXxaPR_4IuNHcDIUJPHbACjG5L7x-nQ”,“ y”:“ xonFn1vRASKUTbLR4-E mh1NYw“},” apv“:” RjAwMDAwMDAwMQ“,” enc“:” A128CBC-HS256“,” alg“:” ECDH-ES“}

     

nimbus_encrypt:serializedJwe = eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJBUzBHUmZBT1dJRE9OWHhhUFJfNEl1TkhjRElVSlBIYkFDakc1TDd4LW5RIiwieSI6InhvbkZuMXZSQVNLVVRkQ2tGVHdzbDE2TFJtU2UtYkFGOEVPNC1taDFOWXcifSwiYXB2IjoiUmpBd01EQXdNREF3TVEiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiRUNESC1FUyJ9..Pi48b7uj3UilvVXKewFacg.0sx9OkHxxtZvkVm-IENRFw.bu5GvOAwcZxdxaDKWIBqwA

这是我使用jose4j lib的代码(到目前为止,使用@ Brian-Campbell的答案):

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代码并添加了输出...

1 个答案:

答案 0 :(得分:2)

下面是一些使用jose4j的示例代码,我认为它可以满足您的需求。您所指向的示例与JWE的纯文本(即JWS / JWT)相似,但可以是任意内容。基本的JWE功能处理了CEK生成/派生的细节。请注意,这仅加密内容,不提供完整性保护或发件人身份验证。

    String encodedCert = "MIIBRjCB7KADAgECAgYBaqxRCjswDAYIKoZIzj0EAwIFADApMQswCQYDVQQGEwJDQTEMMAoGA1UE\n" +
            "ChMDbWVoMQwwCgYDVQQDEwNtZWgwHhcNMTkwNTEyMTM1MjMzWhcNMjAwNTExMTM1MjMzWjApMQsw\n" +
            "CQYDVQQGEwJDQTEMMAoGA1UEChMDbWVoMQwwCgYDVQQDEwNtZWgwWTATBgcqhkjOPQIBBggqhkjO\n" +
            "PQMBBwNCAAQH83AhYHCehKj7M5+UTNshwLFqqqJWGrJPNj9Kr7xvxtcZnyjq+AKLGMLfdk/G7yb8\n" +
            "4vIh0cJwtVs70WgIXT8xMAwGCCqGSM49BAMCBQADRwAwRAIgO0PJRzan2msHpcvcqhybzeualDea\n" +
            "/X2QGAWCYT+sNiwCIDMrfhrzUQ6uIX4vnB8AYqb85Ssl7Qcl9nYtjHb08NR8";

    X509Util x509Util = new X509Util();
    X509Certificate x509Certificate = x509Util.fromBase64Der(encodedCert);

    // the JWE object
    JsonWebEncryption jwe = new JsonWebEncryption();

    // The output of the ECDH-ES key agreement will be used as the content encryption key
    jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);

    // The content encryption key is used to encrypt the payload
    // with a composite AES-CBC / HMAC SHA2 encryption algorithm
    jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);

    // don't think you really need this but you had ""apv":DirectoryServerID" in the question so...
    jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation("<<DirectoryServerID>>"));

    // We encrypt to the receiver using their public key
    jwe.setKey(x509Certificate.getPublicKey());

    // and maybe put x5t to help the receiver know which key to use in decryption
    jwe.setX509CertSha1ThumbprintHeaderValue(x509Certificate);

    // What is going to be encrypted
    jwe.setPayload("Your text here. It can be JSON or whatever.");

    // Produce the JWE compact serialization, which is a string consisting of five dot ('.') separated
    // base64url-encoded parts in the form Header..IV.Ciphertext.AuthenticationTag
    String serializedJwe = jwe.getCompactSerialization();

    System.out.println(serializedJwe);