一、ECC算法介绍

      ECC全称为“Ellipse Curve Ctyptography”,是一种基于椭圆曲线数学的公开密钥加密算法。说到非对称加密算法,大家一般印象是RSA算法,ECC算法的主要优势是可以使用较小的密钥病提供相当高等级的安全,ECC164位密钥提供的安全级可以与RSA 1024位密钥相当。ECC算法相比RSA、DSA算法有以下优势:

  • 同等安全级别要求下,ECC算法计算量小,处理速度快,虽然同等秘钥长度下,ECC的计算更为复杂,但是ECC算法只要较小长度的秘钥就可以达到RSA、DSA算法长秘钥才能达到的安全等级,所以总体上ECC算法性能上更优;
  • 存储空间小,因为同等级安全要求,ECC的秘钥长度要小很多,所以占用的存储空间小;
  • 带宽要求低,虽然长消息场景ECC算法并没有优势,但是短消息场景,ECC要求的带宽是较低的;
  • ECC算法不仅可以用于加解密(但ECIES当前还未被NIST认可)、签名验签,还可以与DH结合使用,用于密钥磋商,这个密钥交换算法称为ECDH,交换双方可以在不共享任何秘密的情况下协商出一个共享密钥(交互算法可以直接根据自己的私钥和对方的公钥支持生产共享秘钥);

二、ECC算法实现

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * ECC算法工具类
 */
public class ECCUtil {
    /**
     * 算法名称
     */
    private static final String ALGORITHM = "ECDSA";

    /**
     * BouncyCastle provider
     */
    private static final String PROVIDER = "BC";

    private static final String ENCRYPT_ALGORITHM = "ECIES";

    /**
     * 密钥交换算法
     */
    private static final String SHARE_KEY_ALGORITHM = "ECDH";

    /**
     * 签名算法
     */
    private static final String SIGN_PROVIDER = "SHA256withECDSA";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生产公私钥对
     *
     * @return ECC公私钥
     * @throws Exception
     */
    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
        // 指定EC曲线类型生成密钥对
        ECGenParameterSpec spec = new ECGenParameterSpec("curve25519");
        keyPairGenerator.initialize(spec, SecureRandom.getInstance("SHA1PRNG"));
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * 读取公钥(base64编码)
     * @param keyPair
     * @return
     */
    public static String getPublicKey(KeyPair keyPair) {
        return Base64.toBase64String(keyPair.getPublic().getEncoded());
    }

    /**
     * 读取私钥(base64编码)
     * @param keyPair
     * @return
     */
    public static String getPrivateKey(KeyPair keyPair) {
        return Base64.toBase64String(keyPair.getPrivate().getEncoded());
    }

    /**
     * 构建公钥对象(输入为base64编码字符串)
     * @param keyStr
     * @return
     * @throws Exception
     */
    public static PublicKey str2PublicKey(String keyStr) throws Exception {
        byte[] publicKey = Base64.decode(keyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 构建私钥对象(输入为base64编码字符串)
     * @param keyStr
     * @return
     * @throws Exception
     */
    public static PrivateKey str2PrivateKey(String keyStr) throws Exception {
        byte[] privateKey = Base64.decode(keyStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 加密数据
     * @param publicKey 公钥
     * @param data 数据
     * @return
     * @throws Exception
     */
    public static byte[] encrypt(PublicKey publicKey, byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGORITHM, PROVIDER);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 解密数据
     * @param privateKey 私钥
     * @param data 数据
     * @return
     * @throws Exception
     */
    public static byte[] decrypt(PrivateKey privateKey, byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGORITHM, PROVIDER);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 数据签名
     * @param privateKey 私钥
     * @param data 数据
     * @return
     * @throws Exception
     */
    public static byte[] sign(PrivateKey privateKey, byte[] data) throws Exception {
        Signature signature = Signature.getInstance(SIGN_PROVIDER);
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    /**
     * 数据验签
     * @param publicKey 公钥
     * @param data 数据
     * @param sign 数据签名
     * @return
     * @throws Exception
     */
    public static boolean verify(PublicKey publicKey, byte[] data, byte[] sign) throws Exception {
        Signature signature = Signature.getInstance(SIGN_PROVIDER);
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(sign);
    }

    /**
     * 生成共享密钥:用于密钥交换,客户端和服务器端双方各自生成一对公私钥,然后客户端使用自己的私钥和服务器的公钥生成一个共享秘钥,
     * 服务端使用自己的私钥和客户端的公钥生成一个共享秘钥,这两个秘钥结果相同,可用于对称加密
     * @param privateKey
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static byte[] genShareKey(PublicKey publicKey, PrivateKey privateKey) throws Exception {
        KeyAgreement keyAgreement = KeyAgreement.getInstance(SHARE_KEY_ALGORITHM, PROVIDER);
        keyAgreement.init(privateKey);
        keyAgreement.doPhase(publicKey, true);
        return keyAgreement.generateSecret();
    }
}

三、ECC算法应用场景

 当前在比特币中ECC算法用的比较多,但是随着计算能力的增强,安全等级要求越来越高,RSA算法的秘钥长度已经太长,弊端比较大,可以预见ECC算法应用会越来越广,比如:

  • 性能要求不高的单向数据加密传输场景(非对称加密相比对称加密算法AES性能差别巨大),例如用于长期自动登录身份的校验等;
  • 双方预制校验身份,比如物理网场景平台一份公私钥,设备一份公私钥,平台发放设备时把设备id和设备公钥预制到进去,这样设备上线后就可以与平台认证通信等;