根据密钥类型不同将现代密码技术分为两类:对称加密算法和非对称加密算法。对称钥匙加密系统是加密和解密均采用同一把秘密钥匙,而且通信双方都必须获得这把钥匙,并保持钥匙的秘密。
非对称密钥加密系统采用的加密钥匙(公钥)和解密钥匙(私钥)是不同的。
常见的非对称加密算法如下:
RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的;
ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。
现在 SSL 证书普遍使用的是 RSA 算法,由于上述的 RSA 算法存在的缺点,使用 ECC 作为其公钥算法的数字证书近几年的发展也不容小觑:2008 年左右 CA 开始储备 ECC 根证书,2012 年左右 CA 开始对外公开销售 ECC 证书,2014 年 ECC 证书在国外被普遍开始使用,2015 年国内开始接受 ECC 证书。
ECC 和 RSA 相比,在许多方面都有对绝对的优势,主要体现在以下方面:
- 抗攻击性强
- CPU 占用少
- 内容使用少
- 网络消耗低
- 加密速度快
来个例子看看它们的“伤害”有多深:
随着安全等级的增加,当前加密法的密钥长度也会成指数增加,而 ECC 密钥长度 却只是成线性增加。例如,128 位安全加密需要 3,072 位 RSA 密钥,却只需要一 个 256 位 ECC 密钥。增加到 256 位安全加密需要一个 15,360 位 RSA 密钥,却只需要一个 512 位 ECC 密钥。ECC 具有如此卓越的按位比率加密的性能,预计其特点将成为安全系统关注的重点。
接下来重头来了,着重从性能来测试两种算法的区别,以下多图预警:
3.1 服务器性能指标
测试在两大云主机上执行。本测试案例中使用了两个不同的身份验证算法,我们看到,ECC-256 层次结构优于 RSA-2048 和 RSA-3072
3.2 响应时间与吞吐量指标之比较
下表中的条目和以下章节详细说明了我们为云主机运行的测试:测试「0K GET,0% 的重用」使用超大型 ( XLarge ) 服务器在超大型 ( XLarge ) 服桌面客户端上运行,适用于 Apache 和 IISweb 服务器,会话重用为 0%,意味着每个 SSL 握手都是一个完整的握手而不是简化版握手。同时应注意,0K 文件并不表示空负载,在过程中仍有 HTTP 抬头传输。
3.2.1 0K GET,重用为 0%
会话重用为 0%,表明每次握手是完整的握手,涉及所需 CPU 处理,我们已经发现运行 Apache 的云主机出现 CPU 饱和受限,RSA-3072 在 500 次请求 / 秒左右; RSA-2048 在 1300 次左右,而 ECC-256 经证实在达 2800 次前呈现出极强的适应性。重要的一点是,ECC-256 能够适应相当高的事务数量。虽然 Apache 和 IIS 背 后的数据点(如在吞吐量和等待时间中的数据点)是完全不同的,但得出的结论是相同的,而且有利于 ECC-256。注意,ECC-256 与 RSA-3072 同等安全,效率可相差甚远。
3.2.2 200K GET,重用为 0%
在云主机上托管 web 服务器的原因是要减少所需客户端数,以便使服务器能够最大程度地利用 CPU。结果和「0K GET,重用为 0%」趋势相同。
3.2.3 200K GET,重用为 68%
会话重用为 68% 的结果是三分之二的握手被简化。与之前测试相比,平均响应时间下降,同时吞吐量增加,三者间的饱和缺口缩小。如果重用百分比增加并绘制成图,我们便可以看到差距在缩小。
3.2.4 1200K GET,重用为 0%
一个有趣的现象是,在 Apache 一例中我们观察到 ECC-256 和 RSA-2048 的网络传输开始饱和,但 RSA-3072 却达到了 CPU 利用极限。而对于 IIS,三者都达到了 CPU 利用极限。
ECC 的这些特点使它必将取代 RSA,成为通用的公钥加密算法。比如 SET 协议的制定者已把它作为下一代 SET 协议中缺省的公钥密码算法。
ECC 证书兼容性:
ECC算法在jdk1.5后加入支持,目前仅仅只能完成密钥的生成与解析。 如果想要获得ECC算法实现,需要调用硬件完成加密/解密(ECC算法相当耗费资源,如果单纯使用CPU进行加密/解密,效率低下),涉及到Java Card领域,PKCS#11。 其实,PKCS#11配置很简单,但缺乏硬件设备,无法尝试!
尽管如此,我照旧提供相应的Java实现代码,以供大家参考。
通过java代码实现如下:
Coder类:
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldF2m;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.NullCipher;
import sun.security.ec.ECKeyFactory;
import sun.security.ec.ECPrivateKeyImpl;
import sun.security.ec.ECPublicKeyImpl;
/**
* ECC安全编码组件
*
* @author Jocerly
*
*/
public abstract class ECCCoder extends Coder {
public static final String ALGORITHM = "EC";
private static final String PUBLIC_KEY = "ECCPublicKey";
private static final String PRIVATE_KEY = "ECCPrivateKey";
/**
* 解密
* 用私钥解密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String key) throws Exception {
// 对密钥解密
byte[] keyBytes = decryptBASE64(key);
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = ECKeyFactory.INSTANCE;
ECPrivateKey priKey = (ECPrivateKey) keyFactory
.generatePrivate(pkcs8KeySpec);
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(priKey.getS(),
priKey.getParams());
// 对数据解密
// TODO Chipher不支持EC算法 未能实现
Cipher cipher = new NullCipher();
// Cipher.getInstance(ALGORITHM, keyFactory.getProvider());
cipher.init(Cipher.DECRYPT_MODE, priKey, ecPrivateKeySpec.getParams());
return cipher.doFinal(data);
}
/**
* 加密
* 用公钥加密
*
* @param data
* @param privateKey
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String privateKey)
throws Exception {
// 对公钥解密
byte[] keyBytes = decryptBASE64(privateKey);
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = ECKeyFactory.INSTANCE;
ECPublicKey pubKey = (ECPublicKey) keyFactory
.generatePublic(x509KeySpec);
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(pubKey.getW(),
pubKey.getParams());
// 对数据加密
// TODO Chipher不支持EC算法 未能实现
Cipher cipher = new NullCipher();
// Cipher.getInstance(ALGORITHM, keyFactory.getProvider());
cipher.init(Cipher.ENCRYPT_MODE, pubKey, ecPublicKeySpec.getParams());
return cipher.doFinal(data);
}
/**
* 取得私钥
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPrivateKey(Map keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 取得公钥
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPublicKey(Map keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 初始化密钥
*
* @return
* @throws Exception
*/
public static Map initKey() throws Exception {
BigInteger x1 = new BigInteger(
"2fe13c0537bbc11acaa07d793de4e6d5e5c94eee8", 16);
BigInteger x2 = new BigInteger(
"289070fb05d38ff58321f2e800536d538ccdaa3d9", 16);
ECPoint g = new ECPoint(x1, x2);
// the order of generator
BigInteger n = new BigInteger(
"5846006549323611672814741753598448348329118574063", 10);
// the cofactor
int h = 2;
int m = 163;
int[] ks = { 7, 6, 3 };
ECFieldF2m ecField = new ECFieldF2m(m, ks);
// y^2+xy=x^3+x^2+1
BigInteger a = new BigInteger("1", 2);
BigInteger b = new BigInteger("1", 2);
EllipticCurve ellipticCurve = new EllipticCurve(ecField, a, b);
ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, g,
n, h);
// 公钥
ECPublicKey publicKey = new ECPublicKeyImpl(g, ecParameterSpec);
BigInteger s = new BigInteger(
"1234006549323611672814741753598448348329118574063", 10);
// 私钥
ECPrivateKey privateKey = new ECPrivateKeyImpl(s, ecParameterSpec);
Map keyMap = new HashMap(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
}
请注意代码中的TODO内容,再次提醒注意,Chipher不支持EC算法 ,以上代码仅供参考。Chipher、Signature、KeyPairGenerator、KeyAgreement、SecretKey均不支持EC算法。为了确保程序能够正常执行,我们使用了NullCipher类,验证程序。
提供一个测试类:
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.security.spec.ECFieldF2m;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.util.Map;
import org.junit.Test;
/**
* ECC测试
* @author Jocerly
*
*/
public class ECCCoderTest {
@Test
public void test() throws Exception {
String inputStr = "abc";
byte[] data = inputStr.getBytes();
Map keyMap = ECCCoder.initKey();
String publicKey = ECCCoder.getPublicKey(keyMap);
String privateKey = ECCCoder.getPrivateKey(keyMap);
JCLoger.debug("公钥: \n" + publicKey);
JCLoger.debug("私钥: \n" + privateKey);
byte[] encodedData = ECCCoder.encrypt(data, publicKey);
byte[] decodedData = ECCCoder.decrypt(encodedData, privateKey);
String outputStr = new String(decodedData);
JCLoger.debug("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
assertEquals(inputStr, outputStr);
}
}