在Java中实现Diffie-Hellman密钥交换

2022-09-03 02:50:34

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

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

  • “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 紧凑型序列化根据 JWS (RFC 7515) 生成完整 JSON 对象的数字签名。此版本的规范中支持的参数值为:

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

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

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

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

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

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

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

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


答案 1

(协议方VInfo)和(临时公钥)都是可选的,因此它们可以以多种方式使用。例如,您可以使用来反映 SDK 版本。它们将添加到 JWE 标头中。apvepkapv

您可以在此处阅读有关尼姆布斯的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);
    }
}

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


答案 2

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

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

我发现Apache CXF Jose库作为JWE / JWS / JWK实现做得很好。除了生成临时密钥对。但它可以很容易地用充气城堡来完成:

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();

推荐