一、简介
ECC是椭圆曲线算法,其加密算法叫ECIES,签名算法叫ECDSA。
JDK并不支持ECC算法,可以使用BouncyCastle库,下载地址:http://www.bouncycastle.org/latest_releases.html
选择最新的provider下载即可(bcprov-jdk15on-162.jar)。
通过以下代码可以输出BouncyCastle库支持的算法
private static void printProvider() {
Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
for (Provider.Service service : provider.getServices()) {
System.out.println(service.getType() + ": "
+ service.getAlgorithm());
}
}
其中包含以下信息,可以看到有加密算法ECIES,有签名算法ECDSA(并且指定了长度)
二、问题
1. Invalid KeySize 的异常
这是由于JDK对keysize有最低安全要求,不过在jdk8最新版本(本人使用的是jdk8-211)已经取消了这个限制,如果有遇到这个问题,使用最新的jdk版本即可(比如你用jdk8-66,则替换为jdk8-211),或者也可以百度一下,网上有其他解决方法。
2. Unknown KeySize 的异常
这是由于BouncyCastle库对keysize有指定要求的,可以看BouncyCastle库中的类:
org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi.class
其中大概在173行左右有这么一个静态代码块:
static {
ecParameters.put(Integers.valueOf(192), new ECGenParameterSpec("prime192v1"));
ecParameters.put(Integers.valueOf(239), new ECGenParameterSpec("prime239v1"));
ecParameters.put(Integers.valueOf(256), new ECGenParameterSpec("prime256v1"));
ecParameters.put(Integers.valueOf(224), new ECGenParameterSpec("P-224"));
ecParameters.put(Integers.valueOf(384), new ECGenParameterSpec("P-384"));
ecParameters.put(Integers.valueOf(521), new ECGenParameterSpec("P-521"));
}
从中可以看出,其支持的keysize就这么几个:192, 239, 256, 224, 384, 521
3. Cipher的使用问题
网上有些博文贴的代码是用NullCipher,但是无法加密,使用NullCipher加密得到的数据还是原文(非密文),实际没有加密,所以无效。事实上,调用Cipher.getInstance("ECIES", "BC"); 来构造的Cipher实例时是返回new Cipher(CipherSpi);这里传入的CipherSpi其实是BouncyCastle库提供的IESCipher(org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher.class),
因此要用Cipher.getInstance("ECIES", "BC"); 来获取含IESCipher实例的Cipher对象,才能正常加解密。
三、实现代码
package crypto;
import javax.crypto.Cipher;
import java.io.File;
import java.io.FileInputStream;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Base64;
public class EccUtils {
/**
* @see org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi.ecParameters (line #173)
* 192, 224, 239, 256, 384, 521
* */
private final static int KEY_SIZE = 256;//bit
private final static String SIGNATURE = "SHA256withECDSA";
static {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
private static void printProvider() {
Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
for (Provider.Service service : provider.getServices()) {
System.out.println(service.getType() + ": "
+ service.getAlgorithm());
}
}
public static void main(String[] args) {
try {
KeyPair keyPair = getKeyPair();
ECPublicKey pubKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey priKey = (ECPrivateKey) keyPair.getPrivate();
//System.out.println("[pubKey]:\n" + getPublicKey(keyPair));
//System.out.println("[priKey]:\n" + getPrivateKey(keyPair));
//测试文本
String content = "abcdefg";
//加密
byte[] cipherTxt = encrypt(content.getBytes(), pubKey);
//解密
byte[] clearTxt = decrypt(cipherTxt, priKey);
//打印
System.out.println("content:" + content);
System.out.println("cipherTxt["+cipherTxt.length+"]:" + new String(cipherTxt));
System.out.println("clearTxt:" + new String(clearTxt));
//签名
byte[] sign = sign(content, priKey);
//验签
boolean ret = verify(content, sign, pubKey);
//打印
System.out.println("content:" + content);
System.out.println("sign["+sign.length+"]:" + new String(sign));
System.out.println("verify:" + ret);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[main]-Exception:" + e.toString());
}
}
//生成秘钥对
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");//BouncyCastle
keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return keyPair;
}
//获取公钥(Base64编码)
public static String getPublicKey(KeyPair keyPair) {
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
byte[] bytes = publicKey.getEncoded();
return Base64.getEncoder().encodeToString(bytes);
}
//获取私钥(Base64编码)
public static String getPrivateKey(KeyPair keyPair) {
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
byte[] bytes = privateKey.getEncoded();
return Base64.getEncoder().encodeToString(bytes);
}
//公钥加密
public static byte[] encrypt(byte[] content, ECPublicKey pubKey) throws Exception {
Cipher cipher = Cipher.getInstance("ECIES", "BC");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(content);
}
//私钥解密
public static byte[] decrypt(byte[] content, ECPrivateKey priKey) throws Exception {
Cipher cipher = Cipher.getInstance("ECIES", "BC");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return cipher.doFinal(content);
}
//私钥签名
public static byte[] sign(String content, ECPrivateKey priKey) throws Exception {
//这里可以从证书中解析出签名算法名称
//Signature signature = Signature.getInstance(getSigAlgName(pubCert));
Signature signature = Signature.getInstance(SIGNATURE);//"SHA256withECDSA"
signature.initSign(priKey);
signature.update(content.getBytes());
return signature.sign();
}
//公钥验签
public static boolean verify(String content, byte[] sign, ECPublicKey pubKey) throws Exception {
//这里可以从证书中解析出签名算法名称
//Signature signature = Signature.getInstance(getSigAlgName(priCert));
Signature signature = Signature.getInstance(SIGNATURE);//"SHA256withECDSA"
signature.initVerify(pubKey);
signature.update(content.getBytes());
return signature.verify(sign);
}
/**
* 解析证书的签名算法,单独一本公钥或者私钥是无法解析的,证书的内容远不止公钥或者私钥
* */
private static String getSigAlgName(File certFile) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
X509Certificate x509Certificate = (X509Certificate) cf.generateCertificate(new FileInputStream(certFile));
return x509Certificate.getSigAlgName();
}
}