Java实现基于EC非对称密钥生成、签名算法及实现ES256/ES512等JWT/JWK签名算法的大多使用BC(BouncyCastle)来实现。闲着无聊就用JDK自带的SunEC来实现了这些,请忽略英文注释。这文章主要用来记一下ES/PEM用java SunEC怎么写的,免的以后找不到。
不过JDK没有提供ECC的加密算法,这个需要自己写代码。这篇文章是没有的,如果要加密还得用BC。
同时支持PKCS8格式的openssl pem的导入导出,工程是基于Spring Boot的,主要是想用它的一些工具类,当然可以不用spring boot,将其中的工具类换成其他的,也可以自己写。
java-security-demohttps://gitee.com/junjunzhu/java-security-demo
不废话,代码如下:
首先给出一个ECUtil工具类,用来做ES签名和DER格式转换的工具类,实现ES签名的必备工具类,SunEC是有这个工具类的,当然自己也写了个,可以通过环境变量控制使用自定义的还是SunEC的。
package org.junyee.demo.security.ec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import com.sun.org.apache.xml.internal.security.algorithms.implementations.ECDSAUtils;
import org.junyee.demo.security.exception.KeyException;
import org.springframework.util.Assert;
/**
* Refrence JOSE BigIntegerUtil.java
*/
@SuppressWarnings("restriction")
public class EcUtils {
static final KeyFactory KEY_FACTORY;
public static final String SECURITY_SUN_EC_DSA_UTIL_DISABLE = "security.sun.ec.dsa.util.disable";
private static boolean disableSecuritySunEcDsaUtil() {
return Boolean.parseBoolean(System.getProperty(SECURITY_SUN_EC_DSA_UTIL_DISABLE, "false"));
}
static {
try {
KEY_FACTORY = KeyFactory.getInstance("EC");
} catch (NoSuchAlgorithmException e) {
throw new KeyException(e);
}
}
public static byte[] toBytesUnsigned(final BigInteger bigInt) {
Assert.notNull(bigInt, "Args Required");
// Copied from Apache Commons Codec 1.8
int bitlen = bigInt.bitLength();
// round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
final byte[] bigBytes = bigInt.toByteArray();
if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if ((bigInt.bitLength() % 8) == 0) {
startSrc = 1;
len--;
}
final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
final byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
return resizedBytes;
}
/**
*
* @param derSignature
* @param outputLength
* @return
*/
public static byte[] transcodeSignatureToConcat(final byte[] derSignature) {
Assert.notNull(derSignature, "Der Signature Required ");
if (!disableSecuritySunEcDsaUtil()) {
try {
// return ECDSAUtils.convertASN1toXMLDSIG(derSignature); // invoke this method if it is exists in the current jdk version.
return ECDSAUtils.convertASN1toXMLDSIG(derSignature,-1); // else invoke this method.
} catch (IOException e) {
throw new KeyException(e);
}
}
if (derSignature.length < 8 || derSignature[0] != 48) {
throw new KeyException("Invalid ASN.1 format of ECDSA signature");
}
int offset;
if (derSignature[1] > 0) {
offset = 2;
} else if (derSignature[1] == (byte) 0x81) {
offset = 3;
} else {
throw new KeyException("Invalid ASN.1 format of ECDSA signature");
}
byte rLength = derSignature[offset + 1];
int i = rLength;
while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) {
i--;
}
byte sLength = derSignature[offset + 2 + rLength + 1];
int j = sLength;
while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) {
j--;
}
int rawLen = Math.max(i, j);
if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
|| (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength || derSignature[offset] != 2
|| derSignature[offset + 2 + rLength] != 2) {
throw new KeyException("Invalid ASN.1 format of ECDSA signature");
}
final byte[] concatSignature = new byte[2 * rawLen];
System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);
return concatSignature;
}
/**
*
* @param esSignature
* @return
*/
public static byte[] transcodeSignatureToDER(byte[] esSignature) {
Assert.notNull(esSignature, "ES Signature Required ");
if (!disableSecuritySunEcDsaUtil()) {
try {
return ECDSAUtils.convertXMLDSIGtoASN1(esSignature);
} catch (IOException e) {
throw new KeyException(e);
}
}
int rawLen = esSignature.length / 2;
int i = rawLen;
while ((i > 0) && (esSignature[rawLen - i] == 0)) {
i--;
}
int j = i;
if (esSignature[rawLen - i] < 0) {
j += 1;
}
int k = rawLen;
while ((k > 0) && (esSignature[2 * rawLen - k] == 0)) {
k--;
}
int l = k;
if (esSignature[2 * rawLen - k] < 0) {
l += 1;
}
int len = 2 + j + 2 + l;
if (len > 255) {
throw new KeyException("Invalid XMLDSIG format of ECDSA signature");
}
int offset;
final byte derSignature[];
if (len < 128) {
derSignature = new byte[2 + 2 + j + 2 + l];
offset = 1;
} else {
derSignature = new byte[3 + 2 + j + 2 + l];
derSignature[1] = (byte) 0x81;
offset = 2;
}
derSignature[0] = 48;
derSignature[offset++] = (byte) len;
derSignature[offset++] = 2;
derSignature[offset++] = (byte) j;
System.arraycopy(esSignature, rawLen - i, derSignature, (offset + j) - i, i);
offset += j;
derSignature[offset++] = 2;
derSignature[offset++] = (byte) l;
System.arraycopy(esSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);
return derSignature;
}
private EcUtils() {
}
}
再给一个PemUtil工具类,主要用来做PEM格式的导入和导出的
package org.junyee.demo.security.ec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.junyee.demo.security.exception.KeyException;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import sun.security.util.*;
import sun.security.x509.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SuppressWarnings("restriction")
public class PemUtils {
private static final String FORMAT_PUBLIC_PEM_HEAD = "-----BEGIN PUBLIC KEY-----";
private static final String FORMAT_PUBLIC_PEM_FOOT = "-----END PUBLIC KEY-----";
private static final String FORMAT_PRIVATE_PEM_HEAD = "-----BEGIN PRIVATE KEY-----";
private static final String FORMAT_PRIVATE_PEM_FOOT = "-----END PRIVATE KEY-----";
public static String enhance(String encodeText) {
Assert.hasText(encodeText, "encode text not be empty");
StringBuilder text = new StringBuilder();
int len = encodeText.length();
for (int i = 0; i < len;) {
int end = i + 64;
if (i > 0) {
text.append("\r\n");
}
text.append(encodeText.substring(i, i = end > len ? len : end));
}
return text.toString();
}
public static boolean isPkcs8PublicKeyFormat(String pem) {
Assert.hasText(pem, "PEM Requried");
pem = StringUtils.trimWhitespace(pem);
return pem.startsWith(FORMAT_PUBLIC_PEM_HEAD) && pem.endsWith(FORMAT_PUBLIC_PEM_FOOT);
}
public static boolean isPkcs8PrivateKeyFormat(String pem) {
Assert.hasText(pem, "PEM Requried");
pem = StringUtils.trimWhitespace(pem);
return pem.startsWith(FORMAT_PRIVATE_PEM_HEAD) && pem.endsWith(FORMAT_PRIVATE_PEM_FOOT);
}
public static byte[] getBytes(String pem) {
Assert.hasText(pem, "PEM Requried");
pem = StringUtils.trimWhitespace(pem);
boolean isPublicKey = isPkcs8PublicKeyFormat(pem);
boolean isPrivatekey = isPkcs8PrivateKeyFormat(pem);
if (!isPublicKey && !isPrivatekey) {
throw new KeyException("Not pkcs8 format PEM");
}
int start = isPublicKey ? 26 : 27;
int end = isPublicKey ? 24 : 25;
pem = StringUtils.trimAllWhitespace(pem.substring(start, pem.length() - end));
return Base64Utils.decodeFromString(pem);
}
public static PemEncodedKeySpec getKeySpec(String pem) {
byte[] bytes = getBytes(pem);
boolean isPublicKey = isPkcs8PublicKeyFormat(pem);
X509EncodedKeySpec x509EncodedKeySpec = isPublicKey ? new X509EncodedKeySpec(bytes)
: getPublicKeyFromPrivateKey(bytes);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = isPublicKey ? null : new PKCS8EncodedKeySpec(bytes);
return new PemEncodedKeySpec(x509EncodedKeySpec, pkcs8EncodedKeySpec);
}
/**
* Get public key in the private Key if contain a public key. It will be
* resolved by ASN.1 PKCS8 Format
*
* @param bytes The private key bytes
* @return
*/
public static X509EncodedKeySpec getPublicKeyFromPrivateKey(byte[] bytes) {
Assert.isTrue(!ObjectUtils.isEmpty(bytes), "encodeBytes must be not empty");
try {
// get private key
DerValue derValue = new DerValue(new ByteArrayInputStream(bytes));
// ASN.1 PKCS8 defined start with sequence
if (derValue.tag != DerValue.tag_Sequence) {
throw new KeyException("invalid key format");
}
// ASN.1 pkcs8 defined version is 0.
BigInteger version = derValue.data.getBigInteger();
if (!version.equals(BigInteger.ZERO)) {
throw new KeyException("pkcs8 version mismatch: (supported:0)");
}
// ASN.1 pkcs8 defined OID and built it to be algorithmId
AlgorithmId algid = AlgorithmId.parse(derValue.data.getDerValue());
// ASN.1 pkcs8 defined ECC Private Key bytes.
bytes = derValue.data.getOctetString();
derValue = new DerValue(new ByteArrayInputStream(bytes));
// ASN.1 pkcs8 defined ECC Private Key start with sequence
if (derValue.tag != DerValue.tag_Sequence) {
throw new KeyException("invalid key format");
}
// ASN.1 pkcs8 Sec1 defined ECC Private Key version 1
int version1 = derValue.data.getInteger();
if (version1 != 1) {
throw new KeyException("ecc private key version mismatch: (supported:1)");
}
// ASN.1 pkcs8 Sec1 defined ECC Private Key property d.
bytes = derValue.data.getOctetString();
// ASN.1 pkcs8 Sec1 defined ecc optional parameters if contain some optional
if (derValue.data.available() != 0) {
DerOutputStream out = new DerOutputStream();
DerValue derValue3 = derValue.data.getDerValue();
// 0xA0 : ASN.1 pkcs8 [0] optional which is OID
if (derValue3.tag == (byte) 0xA0) {
ObjectIdentifier oid = derValue3.data.getOID();
if (log.isDebugEnabled()) {
log.debug("[0] OID: {} ", oid);
}
DerValue params = null;
if (derValue3.data.available() == 0) {
params = null;
} else {
params = derValue3.data.getDerValue();
if (params.tag == DerValue.tag_Null) {
if (params.length() != 0) {
throw new IOException("invalid NULL");
}
params = null;
}
if (derValue3.data.available() != 0) {
throw new IOException("Invalid AlgorithmIdentifier: extra data");
}
}
derValue3 = derValue.data.available() != 0 ? derValue.data.getDerValue() : null;
}
// 0xA1 : ASN.1 pkcs8 Sec1 [1] optional which is public key
if (derValue3 != null && derValue3.tag == (byte) 0xA1) {
// convert to ASN.1 pkcs8 Sec1 defined public key format
encodePublicKey(out, algid, derValue3.data.getUnalignedBitString());
return new X509EncodedKeySpec(out.toByteArray());
}
}
} catch (IOException e) {
throw new KeyException(e);
}
if (log.isWarnEnabled()) {
log.warn("Can not found public key!");
}
return null;
}
private static void encodePublicKey(DerOutputStream out, AlgorithmId algid, BitArray key) {
DerOutputStream tmp = new DerOutputStream();
try {
algid.encode(tmp);
tmp.putUnalignedBitString(key);
out.write(DerValue.tag_Sequence, tmp);
} catch (IOException e) {
throw new KeyException(e);
}
}
@AllArgsConstructor
@Getter
public static class PemEncodedKeySpec implements AlgorithmParameterSpec {
private X509EncodedKeySpec publicKey;
private PKCS8EncodedKeySpec privateKey;
}
}
给出一个ECC和ES签名的封装类,支持ES128/256/384/512。其中ECParameterSpec对象是固定的参数值,是用默认SunEC支持的curve name跑代码跑出来的。
package org.junyee.demo.security.ec;
import java.security.spec.ECParameterSpec;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import org.junyee.demo.security.exception.KeyException;
import org.springframework.util.Assert;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.math.BigInteger;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.ECGenParameterSpec;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public final class EcEs {
/**
* KeyPairGenerator need a random seed to create key. the seed is a constant
* value and make refresh every time.
*
* @return
*/
public KeyPair generateKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_NAME);
SecureRandom secureRandom = new SecureRandom();
keyPairGenerator.initialize(eCGenParameterSpec, secureRandom);
return keyPairGenerator.genKeyPair();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new KeyException(e);
}
}
public Signature generateSignature() {
try {
return Signature.getInstance(digiestName);
} catch (NoSuchAlgorithmException e) {
throw new KeyException(e);
}
}
public ECPrivateKey createPrivateKey(BigInteger d) {
Assert.notNull(d, "Required D");
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, switchEcParameterSpec());
try {
return (ECPrivateKey) EcUtils.KEY_FACTORY.generatePrivate(ecPrivateKeySpec);
} catch (InvalidKeySpecException e) {
throw new KeyException(e);
}
}
public ECPublicKey createPublicKey(BigInteger x, BigInteger y) {
Assert.notNull(x, "Required X");
Assert.notNull(y, "Required Y");
ECPoint ecPoint = new ECPoint(x, y);
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(ecPoint, switchEcParameterSpec());
try {
return (ECPublicKey) EcUtils.KEY_FACTORY.generatePublic(ecPublicKeySpec);
} catch (InvalidKeySpecException e) {
throw new KeyException(e);
}
}
private ECParameterSpec switchEcParameterSpec() {
return disableSunEcParamSpec() ? ecParameterSpec : sourceEcParameterSpec;
}
private void generateSourceKey() {
if (!disableSunEcParamSpec()) {
sourceKeyPair = generateKeyPair();
sourceEcPrivateKey = (ECPrivateKey) sourceKeyPair.getPrivate();
sourceEcPublicKey = (ECPublicKey) sourceKeyPair.getPublic();
sourceEcParameterSpec = sourceEcPrivateKey.getParams();
}
}
private boolean disableSunEcParamSpec() {
return Boolean.parseBoolean(System.getProperty(SECURITY_SUN_EC_PARAM_SPEC_DISABLE, "true"));
}
private EcEs(String alg, String curve, String digiest, int byteLength, ECParameterSpec ecParameterSpec) {
this.algorithm = alg;
this.curveName = curve;
this.digiestName = digiest;
// removed
this.byteLength = byteLength;
this.ecParameterSpec = ecParameterSpec;
this.eCGenParameterSpec = new ECGenParameterSpec(curveName);
generateSourceKey();
}
public static final String SECURITY_SUN_EC_PARAM_SPEC_DISABLE = "security.sun.ec.paramspec.disable";
private static final String EC_NAME = "EC";
public static final EcEs ES128 = new EcEs("ES128", "secp128r1", "SHA1withECDSA", 32,
new ECParameterSpec(
new EllipticCurve(new ECFieldFp(new BigInteger("340282366762482138434845932244680310783")),
new BigInteger("340282366762482138434845932244680310780"),
new BigInteger("308990863222245658030922601041482374867")),
new ECPoint(new BigInteger("29408993404948928992877151431649155974"),
new BigInteger("275621562871047521857442314737465260675")),
new BigInteger("340282366762482138443322565580356624661"), 1));
public static final EcEs ES256 = new EcEs("ES256", "secp256r1", "SHA256withECDSA", 64, new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(new BigInteger(
"115792089210356248762697446949407573530086143415290314195533631308867097853951")),
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291")),
new ECPoint(new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109")),
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), 1));
public static final EcEs ES384 = new EcEs("ES384", "secp384r1", "SHA384withECDSA", 96, new ECParameterSpec(
new EllipticCurve(new ECFieldFp(new BigInteger(
"39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319")),
new BigInteger(
"39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112316"),
new BigInteger(
"27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575")),
new ECPoint(new BigInteger(
"26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087"),
new BigInteger(
"8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871")),
new BigInteger(
"39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643"),
1));
public static final EcEs ES512 = new EcEs("ES512", "secp521r1", "SHA512withECDSA", 132, new ECParameterSpec(
new EllipticCurve(new ECFieldFp(new BigInteger(
"6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151")),
new BigInteger(
"6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057148"),
new BigInteger(
"1093849038073734274511112390766805569936207598951683748994586394495953116150735016013708737573759623248592132296706313309438452531591012912142327488478985984")),
new ECPoint(new BigInteger(
"2661740802050217063228768716723360960729859168756973147706671368418802944996427808491545080627771902352094241225065558662157113545570916814161637315895999846"),
new BigInteger(
"3757180025770020463545507224491183603594455134769762486694567779615544477440556316691234405012945539562144444537289428522585666729196580810124344277578376784")),
new BigInteger(
"6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"),
1));
public static EcEs getByBigInteger(BigInteger bigInteger) {
Assert.notNull(bigInteger, "Args Required");
byte[] cb = EcUtils.toBytesUnsigned(bigInteger);
switch (cb.length) {
// case 15:
case 16: // st
case 17:
return ES128;
// case 31:
case 32: // st
case 33:
return ES256;
// case 47:
case 48: // st
case 49:
return ES384;
// case 64:
case 65: // st
case 66:
return ES512;
default:
throw new KeyException("Not found EC ES [field Size] " + cb.length);
}
}
private String algorithm;
private String curveName;
private String digiestName;
private int byteLength;
private ECParameterSpec ecParameterSpec;
private ECGenParameterSpec eCGenParameterSpec;
private KeyPair sourceKeyPair;
private ECParameterSpec sourceEcParameterSpec;
private ECPrivateKey sourceEcPrivateKey;
private ECPublicKey sourceEcPublicKey;
}
ES转完了,转PEM格式,用如下的封装类
package org.junyee.demo.security.ec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.MessageFormat;
import org.junyee.demo.security.ec.PemUtils.PemEncodedKeySpec;
import org.junyee.demo.security.exception.KeyException;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.ReflectionUtils;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import sun.security.x509.AlgorithmId;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.ECUtil;
/**
* link https://lapo.it/asn1js ASN.1 DER PKCS8 Format
*/
@Getter
@ToString
@Slf4j
@SuppressWarnings("restriction")
public class EcPem {
public static final String SECURITY_SUN_EC_PARAM_OPTIONAL_DISABLE = "security.sun.ec.params.optional.disable";
private static final String FORMAT_PUBLIC_PEM = "-----BEGIN PUBLIC KEY-----\r\n{0}\r\n-----END PUBLIC KEY-----\r\n";
private static final String FORMAT_PRIVATE_PEM = "-----BEGIN PRIVATE KEY-----\r\n{0}\r\n-----END PRIVATE KEY-----\r\n";
private ECPrivateKey privateKey;
private ECPublicKey publicKey;
private String publicPem;
private String privatePem;
private EcPem() {
}
private int getSHALengthNum() {
ECKey ecKey = privateKey == null ? publicKey : privateKey;
int num = ((ecKey.getParams().getOrder().bitLength() + 7) / 8 * 8);
num = num <= 128 ? 1 : num > 512 ? 512 : num;
return num;
}
public Signature generateSignature() {
try {
String algname = "SHA" + getSHALengthNum() + "withECDSA";
if (log.isDebugEnabled()) {
log.debug("The algorithm is [{}] from PEM.", algname);
}
return Signature.getInstance(algname);
} catch (NoSuchAlgorithmException e) {
throw new KeyException(e);
}
}
public static EcPem buildFromPrivateKeyPem(String pem) {
if (!PemUtils.isPkcs8PrivateKeyFormat(pem)) {
throw new KeyException("Not PKCS8 Private Key PEM");
}
return buildFromPem(pem);
}
public static EcPem buildFromPem(String pem) {
try {
PemEncodedKeySpec keySpec = PemUtils.getKeySpec(pem);
PKCS8EncodedKeySpec privateKey2 = keySpec.getPrivateKey();
ECPrivateKey ecPrivateKey = null;
ECPublicKey ecPublicKey = null;
KeyFactory keyFactory = EcUtils.KEY_FACTORY;
if (privateKey2 != null) {
ecPrivateKey = (ECPrivateKey) keyFactory.generatePrivate(privateKey2);
}
X509EncodedKeySpec publicKey2 = keySpec.getPublicKey();
if (publicKey2 != null) {
ecPublicKey = (ECPublicKey) keyFactory.generatePublic(publicKey2);
}
return build(ecPrivateKey, ecPublicKey);
} catch (Exception e) {
throw new KeyException(e);
}
}
public static EcPem build(ECPrivateKey privateKey, ECPublicKey publicKey) {
Assert.isTrue(privateKey != null || publicKey != null, "key requried");
EcPem ecPem = new EcPem();
ecPem.privateKey = privateKey;
ecPem.publicKey = publicKey;
if (publicKey != null) {
ecPem.publicPem = ecPem.exportPkcs8PublickeyPem();
}
if (privateKey != null) {
ecPem.privatePem = ecPem.exportPkcs8PrivatekeyPem();
}
return ecPem;
}
private String exportPkcs8PublickeyPem() {
String pemSource = Base64Utils.encodeToString(publicKey.getEncoded());
return MessageFormat.format(FORMAT_PUBLIC_PEM, PemUtils.enhance(pemSource));
}
private String exportPkcs8PrivatekeyPem() {
DerOutputStream out = new DerOutputStream();
try {
out.write(DerValue.tag_Sequence, buidPrivateKeyAsn1());
byte[] bytes = out.toByteArray();
String pemSource = Base64Utils.encodeToString(bytes);
return MessageFormat.format(FORMAT_PRIVATE_PEM, PemUtils.enhance(pemSource));
} catch (IOException e) {
throw new KeyException(e);
}
}
private DerOutputStream buidPrivateKeyAsn1() {
DerOutputStream privateKeyAsn1 = new DerOutputStream();
try {
// 0: DER Version
privateKeyAsn1.putInteger(0);
DerOutputStream algDerStream = buidAlgorithmIdAsn1();
byte[] algorithmIdBytes = algDerStream.toByteArray();
privateKeyAsn1.write(algorithmIdBytes);
privateKeyAsn1.write(DerValue.tag_OctetString, buidSec1(algorithmIdBytes));
return privateKeyAsn1;
} catch (IOException e) {
throw new KeyException(e);
}
}
private DerOutputStream buidAlgorithmIdAsn1() {
DerOutputStream derOutputStream = new DerOutputStream();
try {
getAlgorithmId().encode(derOutputStream);
return derOutputStream;
} catch (IOException e) {
throw new KeyException(e);
}
}
private boolean enableEcParamOptional() {
return Boolean.parseBoolean(System.getProperty(SECURITY_SUN_EC_PARAM_OPTIONAL_DISABLE, "true"));
}
private AlgorithmId getAlgorithmId() {
Field field = ReflectionUtils.findField(privateKey.getClass(), "algid");
ReflectionUtils.makeAccessible(field);
AlgorithmId algid = (AlgorithmId) ReflectionUtils.getField(field, privateKey);
return algid;
}
private byte[] getAlgorithmIdDataBytes(byte[] algorithmIdBytes) {
try {
DerValue derValue = new DerValue(new ByteArrayInputStream(algorithmIdBytes));
if (derValue.tag != DerValue.tag_Sequence) {
throw new KeyException("invalid algorithmId bytes");
}
return derValue.getDataBytes();
} catch (IOException e) {
throw new KeyException(e);
}
}
private byte[] buidSec1(byte[] algorithmIdBytes) {
try {
byte[] sArr = privateKey.getS().toByteArray();
int numOctets = (privateKey.getParams().getOrder().bitLength() + 7) / 8;
byte[] sOctets = new byte[numOctets];
int inPos = Math.max(sArr.length - sOctets.length, 0);
int outPos = Math.max(sOctets.length - sArr.length, 0);
int length = Math.min(sArr.length, sOctets.length);
System.arraycopy(sArr, inPos, sOctets, outPos, length);
DerOutputStream out = new DerOutputStream();
out.putInteger(1); // version 1
out.putOctetString(sOctets);
if (!enableEcParamOptional()) {
// 0xA0: DER [0] ec params Optional
out.putDerValue(new DerValue((byte) 0xA0, getAlgorithmIdDataBytes(algorithmIdBytes)));
}
// 0xA1: DER [1] public key Optional
out.putDerValue(new DerValue((byte) 0xA1, buildPublicKeyOptional()));
DerOutputStream seq = new DerOutputStream();
seq.write(DerValue.tag_Sequence, out);
return seq.toByteArray();
} catch (IOException exc) {
throw new KeyException(exc);
}
}
private byte[] buildPublicKeyOptional() {
byte[] key = ECUtil.encodePoint(publicKey.getW(), publicKey.getParams().getCurve());
DerOutputStream out = new DerOutputStream();
try {
out.putBitString(key);
} catch (IOException e) {
throw new KeyException(e);
}
return out.toByteArray();
}
}
最后,再提供一个直接可以使用的EcKey的封装类,可以直接使用完成ES/Pem操作
package org.junyee.demo.security.ec;
import java.security.InvalidKeyException;
import org.junyee.demo.security.exception.KeyException;
import java.security.KeyPair;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.util.Arrays;
import java.math.BigInteger;
import org.apache.tomcat.util.buf.HexUtils;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/**
* JDK > 1.8 and SecurityProvider is SunEC The SunEC is simple provider. Such as
* create ECC Key, Signature. But openssl pem and chiper is not supported. Or
* You can use BC(BouncyCastle) to develop ECC, support openssl pem , chiper and
* other advance function. If you want to develop JWK/JWS/JWT, you can use java
* JOSE (Jose4J, you can get it through link: https://jwt.io). JOSE implement
* OIDC support ECC/RSA. When I was develop this ECC demo, the spring boot has
* not integrate ES JWKS. you can develop an ES JWKS starter based the Jose4J by
* yourself.
*/
@Getter
@ToString
@Slf4j
public class EcKey {
private final String type;
private BigInteger d;
private BigInteger x;
private BigInteger y;
private ECPrivateKey ecPrivateKey;
private ECPublicKey ecPublicKey;
private EcEs ecEs;
private EcPem ecPem;
private EcKey(String type) {
this.type = type;
}
private EcKey(BigInteger d, BigInteger x, BigInteger y, String type) {
this(type);
this.d = d;
this.x = x;
this.y = y;
}
public static EcKey generate(EcEs ecEs) {
Assert.notNull(ecEs, "ecEs Required");
KeyPair genKeyPair = ecEs.generateKeyPair();
ECPrivateKey private1 = (ECPrivateKey) genKeyPair.getPrivate();
ECPublicKey public1 = (ECPublicKey) genKeyPair.getPublic();
if (log.isDebugEnabled()) {
// print these to assemble custom EcParameterSpec
log.debug("p: {}", ((ECFieldFp) (private1.getParams().getCurve().getField())).getP()); // print P
log.debug("a: {}", private1.getParams().getCurve().getA()); // ec param spec curve a
log.debug("b: {}", private1.getParams().getCurve().getB()); // ec param spec curve b
log.debug("x: {}", private1.getParams().getGenerator().getAffineX()); // public key ec point x big num
log.debug("y: {}", private1.getParams().getGenerator().getAffineY()); // public key ec point y big num
log.debug("n: {}", private1.getParams().getOrder()); // ec param spec order n
log.debug("h: {}", private1.getParams().getCofactor());// ec param spec cofactor h
}
EcKey ecFactor = new EcKey(private1.getS(), public1.getW().getAffineX(), public1.getW().getAffineY(), "ES");
ecFactor.ecPrivateKey = private1;
ecFactor.ecPublicKey = public1;
ecFactor.ecEs = ecEs;
ecFactor.ecPem = EcPem.build(private1, public1);
return ecFactor;
}
public static EcKey createKeyFromPkcs8Pem(String pem) {
EcPem buildFromPrivateKeyPem = EcPem.buildFromPem(pem);
EcKey ecFactor = new EcKey("PEM");
ecFactor.d = buildFromPrivateKeyPem.getPrivateKey() != null ? buildFromPrivateKeyPem.getPrivateKey().getS()
: null;
ecFactor.x = buildFromPrivateKeyPem.getPublicKey() != null
? buildFromPrivateKeyPem.getPublicKey().getW().getAffineX()
: null;
ecFactor.y = buildFromPrivateKeyPem.getPublicKey() != null
? buildFromPrivateKeyPem.getPublicKey().getW().getAffineY()
: null;
ecFactor.ecPrivateKey = buildFromPrivateKeyPem.getPrivateKey();
ecFactor.ecPublicKey = buildFromPrivateKeyPem.getPublicKey();
ecFactor.ecPem = buildFromPrivateKeyPem;
return ecFactor;
}
public static EcKey createKey(byte[] db, byte[] xb, byte[] yb, EcEs ecEs) {
Assert.notNull(ecEs, "EC ES Required");
boolean notPublic = ObjectUtils.isEmpty(xb) || ObjectUtils.isEmpty(yb);
boolean notPrivate = ObjectUtils.isEmpty(db);
if (notPublic && notPrivate) {
throw new KeyException("Can not create any key.");
}
BigInteger x = notPublic ? null : new BigInteger(1, xb), y = notPublic ? null : new BigInteger(1, yb),
d = notPrivate ? null : new BigInteger(1, db);
EcKey ecFactor = new EcKey(d, x, y, "ES");
if (!notPrivate) {
ecFactor.ecPrivateKey = ecEs.createPrivateKey(d);
}
if (!notPublic) {
ecFactor.ecPublicKey = ecEs.createPublicKey(x, y);
}
ecFactor.ecEs = ecEs;
ecFactor.ecPem = EcPem.build(ecFactor.ecPrivateKey, ecFactor.ecPublicKey);
return ecFactor;
}
public boolean hasPublicKey() {
return x != null && y != null && ecPublicKey != null;
}
public boolean hasPrivateKey() {
return d != null && ecPrivateKey != null;
}
public Signature createSignature() {
if (ecEs == null && ecPem == null) {
throw new KeyException("Can not create Signature");
}
return ecEs == null ? ecPem.generateSignature() : ecEs.generateSignature();
}
public byte[] sign(byte[] content) {
Assert.notNull(content, "Content Required");
if (!hasPrivateKey()) {
throw new KeyException("Has not private key to sign.");
}
Signature signatureSign = createSignature();
try {
signatureSign.initSign(ecPrivateKey);
signatureSign.update(content);
return signatureSign.sign();
} catch (InvalidKeyException | SignatureException e) {
throw new KeyException(e);
}
}
public boolean verify(byte[] signatureContent, byte[] content) {
Assert.notNull(signatureContent, "SignatureContent Required");
Assert.notNull(content, "Content Required");
if (!hasPublicKey()) {
throw new KeyException("Has not public key to verification.");
}
Signature signatureVerify = createSignature();
try {
signatureVerify.initVerify(ecPublicKey);
signatureVerify.update(content);
return signatureVerify.verify(signatureContent);
} catch (InvalidKeyException | SignatureException e) {
throw new KeyException(e);
}
}
public byte[] signES(byte[] content) {
byte[] derSignature = sign(content);
if (log.isDebugEnabled()) {
log.debug("Signature: {}", StringUtils.arrayToDelimitedString(Arrays.asList(derSignature).toArray(), ","));
}
return EcUtils.transcodeSignatureToConcat(derSignature);
}
public boolean verifyES(byte[] esSignature, byte[] content) {
byte[] transcodeSignatureToDER = EcUtils.transcodeSignatureToDER(esSignature);
return verify(transcodeSignatureToDER, content);
}
public String getBase64URLSafeX() {
return x == null ? null : encodeToUrlSafeString(x.toByteArray());
}
public String getBase64URLSafeY() {
return y == null ? null : encodeToUrlSafeString(y.toByteArray());
}
public String getBase64URLSafeD() {
return d == null ? null : encodeToUrlSafeString(d.toByteArray());
}
public static EcKey createPrivateKeyFromUrlSafeBase64(String dDecoder, String xDecoder, String yDecoder,
EcEs ecEs) {
Assert.hasText(dDecoder, "dDecoder must be has text!");
Assert.hasText(xDecoder, "xDecoder must be has text!");
Assert.hasText(yDecoder, "yDecoder must be has text!");
byte[] d = Base64Utils.decodeFromUrlSafeString(dDecoder);
byte[] x = Base64Utils.decodeFromUrlSafeString(xDecoder);
byte[] y = Base64Utils.decodeFromUrlSafeString(yDecoder);
return createKey(d, x, y, ecEs);
}
public static EcKey createPublicKeyFromUrlSafeBase64(String xDecoder, String yDecoder, EcEs ecEs) {
Assert.hasText(xDecoder, "XDecoder must be has text!");
Assert.hasText(yDecoder, "YDecoder must be has text!");
byte[] x = Base64Utils.decodeFromUrlSafeString(xDecoder);
byte[] y = Base64Utils.decodeFromUrlSafeString(yDecoder);
return createKey(null, x, y, ecEs);
}
/**
* Caution: automation recognition ECParameterSpec maybe hide bug if you test a
* changeless jwk successfully , you can call the method.
*
* @param db
* @param xb
* @param yb
* @return
*/
public static EcKey createKey(byte[] db, byte[] xb, byte[] yb) {
byte[] bytes = ObjectUtils.isEmpty(db) ? ObjectUtils.isEmpty(xb) ? yb : xb : db;
if (ObjectUtils.isEmpty(bytes)) {
throw new KeyException("Can not create any key.");
}
return createKey(db, xb, yb, EcEs.getByBigInteger(new BigInteger(1, bytes)));
}
public static EcKey createPrivateKeyFromHex(String dHex, String xHex, String yHex, EcEs ecEs) {
Assert.hasText(dHex, "DHex must be has text!");
Assert.hasText(xHex, "XHex must be has text!");
Assert.hasText(yHex, "YHex must be has text!");
byte[] d = HexUtils.fromHexString(dHex);
byte[] x = HexUtils.fromHexString(xHex);
byte[] y = HexUtils.fromHexString(yHex);
return createKey(d, x, y, ecEs);
}
public static EcKey createPublicKeyFromHex(String xHex, String yHex, EcEs ecEs) {
Assert.hasText(xHex, "XHex must be has text!");
Assert.hasText(yHex, "YHex must be has text!");
byte[] x = HexUtils.fromHexString(xHex);
byte[] y = HexUtils.fromHexString(yHex);
return createKey(null, x, y, ecEs);
}
private String encodeToUrlSafeString(byte[] bytes) {
String text;
int index;
return bytes == null ? null
: (index = (text = Base64Utils.encodeToUrlSafeString(bytes)).indexOf('=')) > 0
? text.substring(0, index)
: text;
}
/*
* public static EcFactor createPrivateKeyFromBase64(String dDecoder, String
* xDecoder, String yDecoder, EcEs ecEs) { Assert.hasText(dDecoder,
* "dDecoder must be has text!"); Assert.hasText(xDecoder,
* "xDecoder must be has text!"); Assert.hasText(yDecoder,
* "yDecoder must be has text!");
*
* byte[] d = Base64Utils.decodeFromString(dDecoder); byte[] x =
* Base64Utils.decodeFromString(xDecoder); byte[] y =
* Base64Utils.decodeFromString(yDecoder); return createKey(d, x, y, ecEs); }
*
* public static EcFactor createPublicKeyFromBase64(String xDecoder, String
* yDecoder, EcEs ecEs) { Assert.hasText(xDecoder,
* "XDecoder must be has text!"); Assert.hasText(yDecoder,
* "YDecoder must be has text!");
*
* byte[] x = Base64Utils.decodeFromString(xDecoder); byte[] y =
* Base64Utils.decodeFromString(yDecoder); return createKey(null, x, y, ecEs); }
*
* public static EcFactor createPrivateKeyFromUrlSafeBase64(String dDecoder,
* String xDecoder, String yDecoder) { Assert.hasText(dDecoder,
* "dDecoder must be has text!"); Assert.hasText(xDecoder,
* "xDecoder must be has text!"); Assert.hasText(yDecoder,
* "yDecoder must be has text!");
*
* byte[] d = Base64Utils.decodeFromUrlSafeString(dDecoder); byte[] x =
* Base64Utils.decodeFromUrlSafeString(xDecoder); byte[] y =
* Base64Utils.decodeFromUrlSafeString(yDecoder); return createKey(d, x, y); }
*
* public static EcFactor createPublicKeyFromUrlSafeBase64(String xDecoder,
* String yDecoder) { Assert.hasText(xDecoder, "XDecoder must be has text!");
* Assert.hasText(yDecoder, "YDecoder must be has text!");
*
* byte[] x = Base64Utils.decodeFromUrlSafeString(xDecoder); byte[] y =
* Base64Utils.decodeFromUrlSafeString(yDecoder); return createKey(null, x, y);
* }
*/
}
最后的最后,再提供一个main方法去跑一下这些代码:
package org.junyee.demo.security.ec;
import java.nio.charset.StandardCharsets;
import org.springframework.util.Base64Utils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class TestEc {
public static void main(String... a) {
// disable pkcs8 ASN.1 put optional [0]
System.setProperty(EcPem.SECURITY_SUN_EC_PARAM_OPTIONAL_DISABLE, "true");
// disable SunEC EcParameterSpec provided
System.setProperty(EcEs.SECURITY_SUN_EC_PARAM_SPEC_DISABLE, "true");
// enable SunEC DsaUtil.java to convert ES signature
System.setProperty(EcUtils.SECURITY_SUN_EC_DSA_UTIL_DISABLE, "false");
log.info("-----------------------------JWK-----------------------------");
EcKey ecKey = EcKey.createPrivateKeyFromUrlSafeBase64("Jg_XvZqKhb2tf8g7HZnN45UYYdp7jgesFS0YJAqcyEg",
"AKK-yxZabtVhWO99dw0v2gCpxAMyY2HCGjjOfuSO7YRX", "QZoeD-gpDLvF01-jB4cN878TvTytxoHZwqyQwb74yZo",
EcEs.ES256);
byte[] signs = ecKey.signES("123123".getBytes(StandardCharsets.UTF_8));
log.info(Base64Utils.encodeToString(signs));
boolean rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info(ecKey.toString());
log.info("-----------------------------ES128-----------------------------");
ecKey = EcKey.generate(EcEs.ES128);
signs = ecKey.signES("123123".getBytes(StandardCharsets.UTF_8));
log.info(Base64Utils.encodeToString(signs));
rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info(ecKey.toString());
log.info("d: {}", ecKey.getBase64URLSafeD());
log.info("x: {}", ecKey.getBase64URLSafeX());
log.info("y: {}", ecKey.getBase64URLSafeY());
log.info("-----------------------------ES256-----------------------------");
ecKey = EcKey.generate(EcEs.ES256);
signs = ecKey.signES("123123".getBytes(StandardCharsets.UTF_8));
log.info(Base64Utils.encodeToString(signs));
rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info(ecKey.toString());
log.info("d: {}", ecKey.getBase64URLSafeD());
log.info("x: {}", ecKey.getBase64URLSafeX());
log.info("y: {}", ecKey.getBase64URLSafeY());
log.info("-----------------------------ES384-----------------------------");
ecKey = EcKey.generate(EcEs.ES384);
signs = ecKey.signES("123123".getBytes(StandardCharsets.UTF_8));
log.info(Base64Utils.encodeToString(signs));
rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info(ecKey.toString());
log.info("d: {}", ecKey.getBase64URLSafeD());
log.info("x: {}", ecKey.getBase64URLSafeX());
log.info("y: {}", ecKey.getBase64URLSafeY());
log.info("-----------------------------ES512-----------------------------");
ecKey = EcKey.generate(EcEs.ES512);
signs = ecKey.signES("123123".getBytes(StandardCharsets.UTF_8));
log.info(Base64Utils.encodeToString(signs));
rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info(ecKey.toString());
log.info("d: {}", ecKey.getBase64URLSafeD());
log.info("x: {}", ecKey.getBase64URLSafeX());
log.info("y: {}", ecKey.getBase64URLSafeY());
// create EcKey from openssl pkcs8 private key pem file
ecKey = EcKey.createKeyFromPkcs8Pem(ecKey.getEcPem().getPrivatePem());
signs = ecKey.signES("123123".getBytes(StandardCharsets.UTF_8));
log.info(Base64Utils.encodeToString(signs));
rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info("d: {}", ecKey.getBase64URLSafeD());
log.info("x: {}", ecKey.getBase64URLSafeX());
log.info("y: {}", ecKey.getBase64URLSafeY());
// create EcKey from openssl pkcs8 public key pem file
ecKey = EcKey.createKeyFromPkcs8Pem(ecKey.getEcPem().getPublicPem());
rs = ecKey.verifyES(signs, "123123".getBytes(StandardCharsets.UTF_8));
log.info(String.valueOf(rs));
log.info("d: {}", ecKey.getBase64URLSafeD());
log.info("x: {}", ecKey.getBase64URLSafeX());
log.info("y: {}", ecKey.getBase64URLSafeY());
}
}