17

我正在尝试在 Java 中实现 Diffie-Hellman 密钥交换,但我很难理解规范:

根据 JWA (RFC 7518) 在 Direct Key Agreement 模式下使用曲线 P-256、dT 和 QC 作为本地机制完成 Diffie-Hellman 密钥交换过程,以生成由 Transaction 标识的一对 CEK(每个方向一个) ID。此版本规范支持的参数值为:

  • “alg”:ECDH-ES
  • “apv”:SDK 参考编号
  • “epk”:QC,JSON Web Key (JWK) 格式
  • {“kty”:“EC”“crv”:“P-256”}
  • 所有其他参数:不存在
  • CEK:“kty”:oct - 256 位

创建以下数据的 JSON 对象作为要签名的 JWS 负载:

{“MyPublicKey”:“QT”,“SDKPublicKey”:“QC”}

根据 JWS (RFC 7515) 使用 JWS 紧凑序列化生成完整 JSON 对象的数字签名。此版本规范支持的参数值为:

  • “alg”:PS256 或 ES256
  • “x5c”:X.5C v3:Cert(MyPb) 加上可选的链接证书

据我了解,ECDH 会生成一个密钥。在共享我的临时公钥 (QT) 后,SDK 会生成相同的密钥,因此我们以后可以交换使用相同密钥加密的 JWE 消息。

JSON {“MyPublicKey”: “QT”, “SDKPublicKey”:” QC”} 将被签名并发送,但我不明白我将如何使用apvepk ,因为这些标头参数用于JWE而不是第一个要共享的JWS 。

在同一规范中,他们谈论这些 JWE 消息,但他们没有这些 apv 和 epk 参数。

根据 JWE (RFC 7516) 使用与 SDK 相同的“enc”算法加密 JSON 对象,获得的 CEK 由“kid”和 JWE Compact Serialization 标识。此版本规范支持的参数值为:

  • “alg”:目录
  • “enc”:A128CBC-HS256 或 A128GCM
  • “孩子”:交易 ID
  • 所有其他参数:不存在

我还阅读了RFC 7518中的示例,在那里我可以看到正在使用的标头参数 apv 和 epk,但我不确定哪些标头参数是 JWE 还是 JWS?

任何关于如何使用 nimbus-jose-jwt 或任何其他 java 库来实现的想法都会非常有帮助。谢谢

4

3 回答 3

6

apv(Agreement PartyVInfo) 和( epkEphemeral Public Key) 都是可选的,因此它们可以以多种方式使用。例如,您可以使用apv来反映 SDK 版本。它们被添加到 JWE 标头中。

你可以在这里阅读更多关于 Nimbus 中 JWE 的信息

使用 Nimbus JOSE 的示例是:

import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;

public class Security {

    public void generateJWE() throws JOSEException, URISyntaxException {
        JWEHeader jweHeader = buildJWEHeader(new Base64URL("202333517"), buildECKey());
        JWEObject jweObject = new JWEObject(jweHeader, new Payload("Hello World!"));
    }

    private JWEHeader buildJWEHeader(Base64URL apv, ECKey epk) {
        JWEHeader.Builder jweBuilder = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A128GCM);
        jweBuilder.agreementPartyVInfo(apv);
        jweBuilder.ephemeralPublicKey(epk);
        return jweBuilder.build();
    }

    private ECKey buildECKey() throws URISyntaxException {
        Set<KeyOperation> keyOperations = new HashSet<>();
        keyOperations.add(KeyOperation.ENCRYPT);
        String transactionID = "73024831";
        URI x5u = new URI("https//website.certificate");
        KeyStore keystore = null; //initialize it
        List<Base64> x5c = new ArrayList<>();
        return new ECKey(Curve.P_256, new Base64URL("x"), new Base64URL("y"), KeyUse.ENCRYPTION, keyOperations, Algorithm.NONE, transactionID, x5u, new Base64URL("x5t"), new Base64URL("x5t256"), x5c, keystore);
    }
}

而不是您可以在您的规范EncryptionMethod.A128GCM中使用as 。并添加到构建器内的 JWEHeader 中。其他参数可以在 JWEHeader.Builder 和 ECKey 的构造函数中选择。我使用了ECDH-ES算法,A128GCM加密方式,P-256曲线(ECKey生成默认椭圆曲线),交易ID是字符串。我选择了其他没有任何明确模式的参数。KeyStore 的初始化对于该示例来说过于宽泛。加密只是你可以用 JWE 做的一件事,在签名和其他方面。EncryptionMethod.A128CBC-HS256apvepk

于 2018-06-04T13:43:20.933 回答
2

Nimbus(以及 Jose4j)不是实现规范的最佳选择(我猜它是 3D 安全 2.xx)。

这些库不返回内容加密密钥,而是根据 JWE 规范使用它来加密或解密消息。

我发现Apache CXF Jose 库作为 JWE/JWS/JWK 实现做得很好。除了生成临时密钥对。但这可以通过 Bouncy Castle 轻松完成:

Security.addProvider(new BouncyCastleProvider());
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec(JsonWebKey.EC_CURVE_P256);
KeyPairGenerator g = KeyPairGenerator.getInstance(ALGORITHM_SIGNATURE_EC, "BC");
g.initialize(ecGenSpec, new SecureRandom());
KeyPair keyPair = g.generateKeyPair();

可以使用以下代码生成内容加密密钥:

byte[] cek = JweUtils.getECDHKey(privateKey, publicKey, null, SDKReferenceNumber, "", 256);

并用于加密获得 JWE 紧凑表示的消息:

JweEncryption jweEncryption = JweUtils.getDirectKeyJweEncryption(cek, contentAlgorithm);
JweHeaders head = new JweHeaders();
head.setHeader(JoseConstants.HEADER_KEY_ID, keyId);
String encrypted = jweEncryption.encrypt(contentToEncrypt.getBytes(StandardCharsets.UTF_8), head);

或解密:

JweCompactConsumer compactConsumer = new JweCompactConsumer(encrypted);
JweHeaders head = compactConsumer.getJweHeaders();
JweDecryption jweDecryption = JweUtils.getDirectKeyJweDecryption(cek, head.getContentEncryptionAlgorithm());
JweDecryptionOutput out = jweDecryption.decrypt(encrypted);
String decrypted = out.getContentText();
于 2018-09-19T15:49:21.287 回答
0

我能够为 A128CBC-HS256 生成密钥。但是在 A128GCM 中仍然没有成功。使用帮助 nimbus-jose 库添加 A128CBC-HS256 的工作示例。下面的代码仅用于密钥生成。

另请注意,我覆盖了 nimbus-jose 类以供我使用。希望能帮助到你。

private void generateSHA256SecretKey(AREQ areq, ECKey sdkPubKey, KeyPair acsKeyPair) throws Exception {

        // Step 4 - Perform KeyAgreement and derive SecretKey
        SecretKey Z = CustomECDH.deriveSharedSecret(sdkPubKey.toECPublicKey(), (ECPrivateKey)acsKeyPair.getPrivate(), null);

        CustomConcatKDF concatKDF = new CustomConcatKDF("SHA-256");

        String algIdString = "";
        String partyVInfoString = areq.getSdkReferenceNumber();
        int keylength = 256; //A128CBC-HS256            

        byte[] algID = CustomConcatKDF.encodeDataWithLength(algIdString.getBytes(StandardCharsets.UTF_8));
        byte[] partyUInfo = CustomConcatKDF.encodeDataWithLength(new byte[0]);          
        byte[] partyVInfo = CustomConcatKDF.encodeDataWithLength(partyVInfoString.getBytes(StandardCharsets.UTF_8));
        byte[] suppPubInfo = CustomConcatKDF.encodeIntData(keylength);
        byte[] suppPrivInfo = CustomConcatKDF.encodeNoData();

        SecretKey derivedKey = concatKDF.deriveKey(
            Z,
            keylength,
            algID,
            partyUInfo,
            partyVInfo,
            suppPubInfo,
            suppPrivInfo);

        System.out.println("Generated SHA256 DerivedKey : "+SecureUtils.bytesToHex(derivedKey.getEncoded()));       
    }
于 2018-07-11T09:42:52.733 回答