package com.alipay.easysdk.kms.aliyun; import com.alipay.easysdk.kernel.AlipayConstants; import com.alipay.easysdk.kms.aliyun.models.AsymmetricSignRequest; import com.alipay.easysdk.kms.aliyun.models.AsymmetricSignResponse; import com.alipay.easysdk.kms.aliyun.models.GetPublicKeyRequest; import com.alipay.easysdk.kms.aliyun.models.GetPublicKeyResponse; import com.alipay.easysdk.kms.aliyun.models.RuntimeOptions; import com.aliyun.tea.TeaConverter; import com.aliyun.tea.TeaModel; import com.aliyun.tea.TeaPair; import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.math.ec.ECFieldElement; import org.bouncycastle.util.encoders.Base64; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.PublicKey; import java.security.Security; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; /** * 实现KMS的Client * * @author aliyunkms * @version $Id: AliyunKMSClient.java, v 0.1 2020年05月08日 10:53 PM aliyunkms Exp $ */ public class AliyunKMSClient extends AliyunRpcClient { //支付宝signType与KMS签名算法映射表 private static final Map signAlgs = new HashMap<>(); private static final Map digestAlgs = new HashMap<>(); private static final Map namedCurves = new HashMap<>(); static { digestAlgs.put("RSA_PKCS1_SHA_256", "SHA-256"); digestAlgs.put("RSA_PSS_SHA_256", "SHA-256"); digestAlgs.put("ECDSA_SHA_256", "SHA-256"); namedCurves.put("SM2DSA", "sm2p256v1"); signAlgs.put("RSA2", "RSA_PKCS1_SHA_256"); } private String keyId; private String keyVersionId; private String algorithm; private PublicKey publicKey; private String protocol; private String method; private String version; private Integer connectTimeout; private Integer readTimeout; private Integer maxAttempts; private boolean ignoreSSL; public AliyunKMSClient(Map config) throws Exception { super(config); this.keyId = (String) config.get("kmsKeyId"); this.keyVersionId = (String) config.get("kmsKeyVersionId"); this.algorithm = signAlgs.get((String) config.get(AlipayConstants.SIGN_TYPE_CONFIG_KEY)); this.publicKey = null; this.protocol = "HTTPS"; this.method = "POST"; this.version = "2016-01-20"; this.connectTimeout = 15000; this.readTimeout = 15000; this.maxAttempts = 3; this.ignoreSSL = false; } private GetPublicKeyResponse _getPublicKey(GetPublicKeyRequest request) throws Exception { validateModel(request); RuntimeOptions runtime = RuntimeOptions.build(TeaConverter.buildMap( new TeaPair("connectTimeout", this.connectTimeout), new TeaPair("readTimeout", this.readTimeout), new TeaPair("maxAttempts", this.maxAttempts), new TeaPair("ignoreSSL", this.ignoreSSL) )); return TeaModel.toModel( this.doRequest("GetPublicKey", this.protocol, this.method, this.version, TeaModel.buildMap(request), null, runtime), new GetPublicKeyResponse()); } private PublicKey getPublicKey(String keyId, String keyVersionId) throws Exception { GetPublicKeyRequest request = GetPublicKeyRequest.build(TeaConverter.buildMap( new TeaPair("KeyId", keyId), new TeaPair("KeyVersionId", keyVersionId) )); GetPublicKeyResponse response = _getPublicKey(request); String pemKey = response.publicKey; pemKey = pemKey.replaceFirst("-----BEGIN PUBLIC KEY-----", ""); pemKey = pemKey.replaceFirst("-----END PUBLIC KEY-----", ""); pemKey = pemKey.replaceAll("\\s", ""); byte[] derKey = Base64.decode(pemKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey); Security.addProvider(new BouncyCastleProvider()); return KeyFactory.getInstance("EC", "BC").generatePublic(keySpec); } private byte[] getZ(ECPublicKeyParameters ecPublicKeyParameters, ECDomainParameters ecDomainParameters) { Digest digest = new SM3Digest(); digest.reset(); String userID = "1234567812345678"; addUserID(digest, userID.getBytes()); addFieldElement(digest, ecDomainParameters.getCurve().getA()); addFieldElement(digest, ecDomainParameters.getCurve().getB()); addFieldElement(digest, ecDomainParameters.getG().getAffineXCoord()); addFieldElement(digest, ecDomainParameters.getG().getAffineYCoord()); addFieldElement(digest, ecPublicKeyParameters.getQ().getAffineXCoord()); addFieldElement(digest, ecPublicKeyParameters.getQ().getAffineYCoord()); byte[] result = new byte[digest.getDigestSize()]; digest.doFinal(result, 0); return result; } private void addUserID(Digest digest, byte[] userID) { int len = userID.length * 8; digest.update((byte) (len >> 8 & 0xFF)); digest.update((byte) (len & 0xFF)); digest.update(userID, 0, userID.length); } private void addFieldElement(Digest digest, ECFieldElement v) { byte[] p = v.getEncoded(); digest.update(p, 0, p.length); } private byte[] calcSM3Digest(PublicKey pubKey, byte[] message) { X9ECParameters x9ECParameters = GMNamedCurves.getByName(namedCurves.get(this.algorithm)); ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN()); BCECPublicKey localECPublicKey = (BCECPublicKey) pubKey; ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters); byte[] z = getZ(ecPublicKeyParameters, ecDomainParameters); Digest digest = new SM3Digest(); digest.update(z, 0, z.length); digest.update(message, 0, message.length); byte[] result = new byte[digest.getDigestSize()]; digest.doFinal(result, 0); return result; } private AsymmetricSignResponse _asymmetricSign(AsymmetricSignRequest request) throws Exception { validateModel(request); RuntimeOptions runtime = RuntimeOptions.build(TeaConverter.buildMap( new TeaPair("connectTimeout", this.connectTimeout), new TeaPair("readTimeout", this.readTimeout), new TeaPair("maxAttempts", this.maxAttempts), new TeaPair("ignoreSSL", this.ignoreSSL) )); return TeaModel.toModel( this.doRequest("AsymmetricSign", this.protocol, this.method, this.version, TeaModel.buildMap(request), null, runtime), new AsymmetricSignResponse()); } private String asymmetricSign(String keyId, String keyVersionId, String algorithm, byte[] message) throws Exception { byte[] digest; if (algorithm.equals("SM2DSA")) { if (this.publicKey == null) { this.publicKey = getPublicKey(keyId, keyVersionId); } digest = calcSM3Digest(this.publicKey, message); } else { digest = MessageDigest.getInstance(digestAlgs.get(algorithm)).digest(message); } AsymmetricSignRequest request = AsymmetricSignRequest.build(TeaConverter.buildMap( new TeaPair("keyId", keyId), new TeaPair("keyVersionId", keyVersionId), new TeaPair("algorithm", algorithm), new TeaPair("digest", Base64.toBase64String(digest)) )); AsymmetricSignResponse response = _asymmetricSign(request); return response.value; } /** * 计算签名 * * @param content 待签名的内容 * @return 签名值的Base64串 */ public String sign(String content) throws Exception { return asymmetricSign(this.keyId, this.keyVersionId, this.algorithm, content.getBytes(AlipayConstants.DEFAULT_CHARSET)); } public String getAlgorithm() { return this.algorithm; } public void setAlgorithm(String algorithm) { this.algorithm = algorithm; } public String getKeyId() { return this.keyId; } public void setKeyId(String keyId) { this.keyId = keyId; } public String getKeyVersionId() { return this.keyVersionId; } public void setKeyVersionId(String keyVersionId) { this.keyVersionId = keyVersionId; } public PublicKey getPublicKey() { return this.publicKey; } public void setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; } public String getProtocol() { return this.protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public String getMethod() { return this.method; } public void setMethod(String method) { this.method = method; } public String getVersion() { return this.version; } public void setVersion(String version) { this.version = version; } public Integer getConnectTimeout() { return this.connectTimeout; } public void setConnectTimeout(Integer connectTimeout) { this.connectTimeout = connectTimeout; } public Integer getReadTimeout() { return this.readTimeout; } public void setReadTimeout(Integer readTimeout) { this.readTimeout = readTimeout; } public Integer getMaxAttempts() { return this.maxAttempts; } public void setMaxAttempts(Integer maxAttempts) { this.maxAttempts = maxAttempts; } public boolean getIgnoreSSL() { return this.ignoreSSL; } public void setIgnoreSSL(boolean ignoreSSL) { this.ignoreSSL = ignoreSSL; } }