aliyun / gm-jsse

开源国密通信纯 Java JSSE 实现

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

关于ECDHE_SM4_CBC_SM3的实现

tommy8421 opened this issue · comments

0、ECDHE交换算法测试过的主机:https://e.tf.cn(须指定ECDHE_SM4_CBC_SM3否则服务器协商优先ECC_SM4_CBC_SM3)和https://demo.gmssl.cn:1443
1、在ServerKeyExchange阶段,按下面描述保存服务器公钥(ephemeral):
// 03 -- ECCurveType-->named_curve(3) -- 目前只有这个合法值,其他要么废弃要么是RFU的了
// 00 17 -- secp256r1 (23) sm2p256v1(41) -- 按TLCP规范,这里是不用校验的
// 41 -- length
// 04 -- SM2 public key(point = x + y)
// 8fc16899e15b5110b1b3cf45a331bd8b9c25bf2afb0f0b31faa1e7106a7f36bae75bba7938ddacbe5868fe4e64755db631199c95802c550e4342dbcbd54b937a
// 00 47 -- 下面为ASN.1签名数据signed_param(用服务器签名公钥验签,验签数据:client random+server random + ECDHE parameter)
// 30 45
// 02 20 3ed134bc05dbc842a31c83bf6b5c89ba2c504257736ab436d6e65ecbcac53dc8 -R
// 02 21 00b2541eb483f7faaa5f22e0502049104944d8a423de69a2eac757d1c6cfca5e3f -S
2、在ClientKeyExchange阶段,计算PreMasterKey,借助于 BC库的 SM2KeyExchange类;然后把Ephemeral 的PublicKey发给服务器
PrivateKey localEncPVK = session.getKeyManager().getPrivateKey(GMConstants.CERT_ENC);
KeyPair localEphKP = Crypto.generateSM2KeyPair(); // Ephemeral key pair
PublicKey peerEphPUK = Crypto.loadPublicKey(session.getServerKEParam()); // 这里就是之前SKE的时候服务器的ECPoint为PublicKey
byte[] plainSecret = Crypto.generateECDHESecret(localEncPVK, localEphKP.getPrivate(), peerEncPUK, peerEphPUK);

核心代码如下:

public static KeyPair generateSM2KeyPair()
        throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
    KeyPairGenerator generator = KeyPairGenerator.getInstance(“EC”, GMProvider.BC_PROVIDER); // new BouncyCastleProvider()
    generator.initialize(new ECGenParameterSpec(GM_NAME));  // GM_NAME="sm2p256v1"

    return generator.generateKeyPair();
}

public static PublicKey loadPublicKey(byte[] pointBytes)
        throws InvalidKeySpecException, NoSuchAlgorithmException {
    ECParameterSpec ecParamSpec = org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(GM_NAME);
    ECPoint ecPoint = ecParamSpec.getCurve().decodePoint(pointBytes);
    ECPublicKeySpec ecPUKSpec = new ECPublicKeySpec(ecPoint, ecParamSpec);

    KeyFactory keyFactory = KeyFactory.getInstance("EC", GMProvider.BC_PROVIDER);
    return keyFactory.generatePublic(ecPUKSpec);
}

public static byte[] generateECDHESecret(PrivateKey localEncPVK, PrivateKey localEphPVK, PublicKey peerEncPUK, PublicKey peerEphPUK) {
    CipherParameters sm2kePvkParam = new SM2KeyExchangePrivateParameters(
            false,  // for client
            ((BCECPrivateKey) localEncPVK).engineGetKeyParameters(),
            ((BCECPrivateKey) localEphPVK).engineGetKeyParameters());
    CipherParameters pvkParamWithID = new ParametersWithID(sm2kePvkParam, GM_USERID);

    // 因为 BCECPublicKey.engineGetKeyParameters 没有公开,也可以用反射调用
    ECPublicKeyParameters peerPUKParam = new ECPublicKeyParameters(((BCECPublicKey) peerEncPUK).getQ(), ecDomainParameters);
    ECPublicKeyParameters peerEphPUKParam = new ECPublicKeyParameters(((BCECPublicKey) peerEphPUK).getQ(), ecDomainParameters);
    CipherParameters sm2kePukParam = new SM2KeyExchangePublicParameters(peerPUKParam, peerEphPUKParam);
    CipherParameters pukParamWithID = new ParametersWithID(sm2kePukParam, GM_USERID);// GM_USERID="1234567812345678".getBytes()

    SM2KeyExchange keyExchange = new SM2KeyExchange();
    keyExchange.init(pvkParamWithID);
    return keyExchange.calculateKey(GMConstants.ECDHE_KEY_BITS, pukParamWithID);   // pre-master key bits number, ECDHE_KEY_BITS=48*8
}

可以看我的仓库,AndroidGMSSE。实现了E011/E051/E013/E053.