Java SunEC实现JWT JWK ES256 导入导出PEM

1 篇文章 0 订阅

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-demoicon-default.png?t=N7T8https://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());
    }
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

车联网服务端架构

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值