项目中刚好用到了ecdh进行密钥协商. java系列的文章比较少.记录下
首先密钥协商的大致过程:
- 双方A,B都使用EC算法(椭圆曲线算法)生成对应的公私钥.即A_PUB,A_PRI,B_PUB,B_PRI
- A把自己的公钥A_PUB传递给B, B把公钥B_PUB传递给A
- A用A_PRI和B_PUB 可以通过ecdh算法算出来一个密钥secret;
- B同样可以使用B_PRI和A_PUB 算出来同一个密钥secret;
- 这样AB之间就可以用这个secret 进行愉快的加解密了;
先声明一下, ec算法的密钥可以用XY坐标点表示, 也可以用Key.getEncoded()的字符串表示, 本文采用的更复杂的XY坐标点表示法, 因为是java语言对接的C语言,传过来就是XY坐标点表示法; 想用getEncoded的话 需要你自行百度; 应该也不难
//参考公私钥
public static final String A_PUBLIC_KEY_HEX =
"04939859a19e9bdc2989cae7915857f8186a72ff03563b463ec1f81fd7064ea1b9694f56c10e76a583a1d6a78226ad005872c0ef2279bd8b84a0c9e6072aad12d9";
public static final String A_PRIVATE_KEY_HEX =
"38ca6cb7c6bbdc43e9f1acd53d17da4d797a3881c0d047038d2c49eec75b3b49";
public static final String B_PUBLIC_KEY_HEX =
"046c6a8a366e15db90c91c37fdcf43808f79d110c9a8b3a4e2657343a7b904c3b26d4019f8af58d12e4dba331a786e61a7b76d2a99448bdf309cfbc7fe1eab1e32";
public static final String B_PRIVATE_KEY_HEX =
"db4f1b7c14c84fe5c654b889fb657576aebbd4cbfa4b2098bacf14e3eab86285";
废话不多说,开始撸代码
//椭圆曲线名称, 标准的曲线有很多种, 这里选用secp256k1这个曲线. 两方要提前约定好
public static final String ecCurveName = "secp256k1";
/**
* 通过ecdh密钥协商, 自身的私钥+对方的公钥,生成共同的密钥,用于aes加密
* <p>
* 密钥格式,base64/16进制都支持
*
* @param myPriHex
* @param othPubHex
* @return
*/
public static String getCommonSecretFromMyPriAndOthPub(String myPriHex, String othPubHex) throws Exception {
PrivateKey myPrivateKey = getPrivateKeyFromS(myPriHex);
//分解, 获取坐标
Map<String, String> XYMap = getHexXY(othPubHex);
PublicKey othPublicKey = getPublicKeyFromXY(XYMap.get("X"), XYMap.get("Y"));
KeyAgreement ecdhA = KeyAgreement.getInstance("ECDH");
ecdhA.init(myPrivateKey);
ecdhA.doPhase(othPublicKey, true);
String commonSecret = HexUtil.encodeHexStr(ecdhA.generateSecret());
log.info("ecdh协商后的密钥16进制:[{}]", commonSecret);
//System.out.println("ecdh协商后的密钥16进制:"+ commonSecret);
String substring1 = commonSecret.substring(commonSecret.length() / 2);
log.info("截取后的密钥:[{}]", substring1);
return substring1;
}
/**
* ec 椭圆曲线算法中
* 从私钥的坐标获取 ECPublicKey对象
*
* @param hexPrivateS 16进制的坐标点
* @return
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidParameterSpecException
* @throws InvalidKeySpecException
*/
public static ECPrivateKey getPrivateKeyFromS(String hexPrivateS) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidParameterSpecException,
InvalidKeySpecException {
byte[] privateKeyS = HexUtil.decodeHex(hexPrivateS);
AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
parameters.init(new ECGenParameterSpec(ecCurveName));
ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyS), ecParameters);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey privateKey = (ECPrivateKey) kf.generatePrivate(privateSpec);
return privateKey;
}
/**
* 解析公钥坐标, 04开头, 剩余应该是128长度, 切割前后64个,获取X和Y
*
* @param othPubHex
* @return
*/
private static Map<String, String> getHexXY(String othPubHex) {
Map<String, String> hashMap = MapUtil.newHashMap();
if (!(othPubHex.length() == 130 && othPubHex.startsWith("04"))) {
log.error("othPubHex格式异常,需要04开头,长度为130,othPubHex:[{}]", othPubHex);
throw new RuntimeException("othPubHex格式异常,需要04开头,长度为130");
}
String sub = StrUtil.sub(othPubHex, 2, othPubHex.length());
String subX = sub.substring(0, sub.length() / 2);
String subY = sub.substring(sub.length() / 2);
hashMap.put("X", subX);
hashMap.put("Y", subY);
return hashMap;
}
/**
* ec 椭圆曲线算法中
* 从公钥的xy坐标获取 ECPublicKey对象
*
* @param hexPublicKeyX 16进制的x坐标点
* @param hexPublicKeyY 16进制的y坐标点
* @return
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidParameterSpecException
* @throws InvalidKeySpecException
*/
public static ECPublicKey getPublicKeyFromXY(String hexPublicKeyX, String hexPublicKeyY) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidParameterSpecException,
InvalidKeySpecException {
byte[] publicKeyX = HexUtil.decodeHex(hexPublicKeyX);
byte[] publicKeyY = HexUtil.decodeHex(hexPublicKeyY);
ECPoint pubPoint = new ECPoint(new BigInteger(1, publicKeyX), new BigInteger(1, publicKeyY));
AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
parameters.init(new ECGenParameterSpec(ecCurveName));
ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
ECPublicKeySpec pubSpec = new ECPublicKeySpec(pubPoint, ecParameters);
//ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyS), ecParameters);
KeyFactory kf = KeyFactory.getInstance("EC");
//ECPrivateKey privateKey = (ECPrivateKey) kf.generatePrivate(privateSpec);
ECPublicKey publicKey = (ECPublicKey) kf.generatePublic(pubSpec);
return publicKey;
}
```java
//如果需要自己生成密钥字符串
/**
* 生成EC密钥对
*
* @param ecCurveName 椭圆曲线 可选为:secp256k1
* type:hex/base64
* @return
*/
public static KeyPairStrDto generatorEcKeyPair(String ecCurveName, String type) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
if (StrUtil.isNotBlank(ecCurveName)) {
ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(ecCurveName);
keyPairGenerator.initialize(ecGenParameterSpec);
}
KeyPair ecKeyPair = keyPairGenerator.generateKeyPair();
ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic();
String s1 = ecPublicKey.getW().getAffineX().toString(16);
String s2 = ecPublicKey.getW().getAffineY().toString(16);
System.out.println("公钥:04" + s1 + s2);
ECPrivateKey ecPrivateKey = (ECPrivateKey) ecKeyPair.getPrivate();
String s = ecPrivateKey.getS().toString(16);
System.out.println("私钥:" + s);
String publicKeyStr = new String(Base64.getEncoder().encode(ecPublicKey.getEncoded()));
String privateKeyStr = new String(Base64.getEncoder().encode(ecPrivateKey.getEncoded()));
//使用16进制的
if ("hex".equals(type)) {
publicKeyStr = HexUtil.encodeHexStr(ecPublicKey.getEncoded());
privateKeyStr = HexUtil.encodeHexStr(ecPrivateKey.getEncoded());
}
return new KeyPairStrDto(publicKeyStr, privateKeyStr);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}
里面用到了hutool工具类
```java
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.1</version>
</dependency>
</dependencies>
ok
打完收工