非对称加密算法
常见算法
RSA、ECC、ElGamal。
非对称加密算法比较
| 名称 | 成熟度 | 安全性 | 运算速度 | 资源消耗 |
|---|---|---|---|---|
| RSA | 高 | 高 | 中 | 中 |
| ECC | 高 | 高 | 慢 |
| 名称 | 优点 | 缺点 | 破解方式 | 使用场景 |
|---|---|---|---|---|
| RSA | 原理简单 | 产生密钥麻烦,受到素数产生技术的限制,因此难做到一次一密;分组长度太大,不利于数据格式标准化; | 难度大 | 1,数字签名;2,公钥加密;3,防止数据篡改;4,通讯领域较多; |
特点
- 加密和解密使用不同的密钥。它需要两个密钥,一个是公钥,另一个是私钥,一个用于加密,另一个用于解密。
- 如果使用私钥加密, 只能使用公钥解密
- 如果使用公钥加密, 只能使用私钥解密
- 处理数据的速度较慢, 因为安全级别高
PS:通常情况下,公钥的长度一般比较短,所以我们会将公钥对外公开,让别人使用公钥来加密数据。 而我们则使用私钥解密数据,但是切记基于非对称的特性。 我们也是可以使用公开私钥加密,而使用公钥解密。不这样做的原因是因为私钥通常比公钥要长,传输起来比较费资源。
RSA
Public-Key Encryption, 公钥加密算法。 根据秘钥长度的不同,有RSA(512、1024、2048、3072、4096)等。
毫不夸张地说,只要有计算机网络的地方,就有 RSA 算法。
当前最著名、应用最广泛的公钥系统RSA是在1978年,由美国麻省理工学院(MIT)的Rivest、Shamir和Adleman在题为《获得数字签名和公开钥密码系统的方法》的论文中提出的。 它是一个基于数论的非对称(公开钥)密码体制,是一种分组密码体制。其名称来自于三个发明者的姓名首字母。 它的安全性是基于大整数素因子分解的困难性,而大整数因子分解问题是数学上的著名难题,至今没有有效的方法予以解决,因此可以确保RSA算法的安全性。 RSA系统是公钥系统的最具有典型意义的方法,大多数使用公钥密码进行加密和数字签名的产品和标准使用的都是RSA算法。
RSA算法是一种非对称加密算法,所谓非对称就是该算法需要一对密钥, 若使用其中一个加密,则需要用另一个才能解密。
目前它是最有影响力和最常用的公钥加密算法,能够抵抗已知的绝大多数密码攻击。
从提出到现今的三十多年里,经历了各种攻击的考验,逐渐为人们接受, 普遍认为是目前最优秀的公钥方案之一。
该算法基于一个的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难, 因此可以将乘积公开作为加密密钥。
由于进行的都是大数计算,RSA 最快的情况也比 DES 慢上好几倍,比对应同样安全级别的对称密码算法要慢 1000 倍左右。 所以 RSA 一般只用于少量数据加密,比如说交换对称加密的密钥。
RSA算法是第一个既能用于数据加密也能用于数字签名的算法,因此它为公用网络上信息的加密和鉴别提供了一种基本的方法。 它通常是先生成一对RSA密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册,人们用公钥加密文件发送给个人,个人就可以用私钥解密接受。为提高保密强度,RSA密钥至少为500位长,一般推荐使用1024位。
元素
RSA 算法里面一共有 8 个元素。
- p,一个素数。
- q,另一个素数
- n,根据 p 和 q 形成的数,一般为 n = p X q
- e,一个被称为「公钥因子」的数
- d,一个被称为「私钥因子」的数。下面的公式会具体展现e和d的转化
- ϕ,一个与 n 有关,由 p 和 q 计算而得到的数。具体就是 n 的欧拉函数 ϕ(n)
- m,明文,就是要保护的信息。
- c,密文,就是要破解的信息。
代码样例
Java
public static final String AES = "AES";
public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
/**
* 随机生成 RSA 密钥对
*
* @param keyLength 密钥长度, 范围: 512~2048, 一般 1024
* @return 密钥对
*/
public static KeyPair generateRSAKeyPair(int keyLength) {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
kpg.initialize(keyLength);
return kpg.genKeyPair();
} catch (NoSuchAlgorithmException e) {
logger.error(e);
}
return null;
}
/**
* 公钥加密
*
* @param data 原文
* @param publicKey 公钥
* @return 加密后的数据
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPublic);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* 私钥加密
*
* @param data 待加密数据
* @param privateKey 密钥
* @return 加密后的数据
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* 公钥解密
*
* @param data 待解密数据
* @param publicKey 密钥
* @return 解密后的数据
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPublic);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* 私钥解密
*
* @param data 待解密的数据
* @param privateKey 私钥
* @return 解密后的数据
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] privateKey) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
当然,实际项目中私钥一般是从文件中进行的加载。也有从字符串中加载的。此处举一个实现的代码Demo。
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.apache.commons.io.FileUtils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RsaXXX {
public static void main(String[] args) {
String algorithm = "RSA";
String input = "非对称加密与对称加密相比,其安全性更好:对称加密的通信双方使用相同的秘钥,如果一方的秘钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对秘钥,一个用来加密,一个用来解密,而且公钥是公开的,秘钥是自己保存的,不需要像对称加密那样在通信之前要先同步秘钥";
try {
generateKeyToFile(algorithm, "a.pub", "a.pri");
PublicKey publicKey = loadPublicKeyFromFile(algorithm, "a.pub");
PrivateKey privateKey = loadPrivateKeyFromFile(algorithm, "a.pri");
String encrypt = encrypt(algorithm, input, privateKey, 245);
String decrypt = decrypt(algorithm, encrypt, publicKey, 256);
System.out.println(decrypt);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成密钥对并保存在本地文件中
*
* @param algorithm : 算法
* @param pubPath : 公钥保存路径
* @param priPath : 私钥保存路径
* @throws Exception
*/
private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {
// 获取密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
// 获取密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥
PublicKey publicKey = keyPair.getPublic();
// 获取私钥
PrivateKey privateKey = keyPair.getPrivate();
// 获取byte数组
byte[] publicKeyEncoded = publicKey.getEncoded();
byte[] privateKeyEncoded = privateKey.getEncoded();
// 进行Base64编码
String publicKeyString = Base64.encode(publicKeyEncoded);
String privateKeyString = Base64.encode(privateKeyEncoded);
// 保存文件
FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));
FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));
}
/**
* 从文件中加载公钥
*
* @param algorithm : 算法
* @param filePath : 文件路径
* @return : 公钥
* @throws Exception
*/
private static PublicKey loadPublicKeyFromFile(String algorithm, String filePath) throws Exception {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));
return loadPublicKeyFromString(algorithm, keyString);
}
/**
* 从字符串中加载公钥
*
* @param algorithm : 算法
* @param keyString : 公钥字符串
* @return : 公钥
* @throws Exception
*/
private static PublicKey loadPublicKeyFromString(String algorithm, String keyString) throws Exception {
// 进行Base64解码
byte[] decode = Base64.decode(keyString);
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范
X509EncodedKeySpec keyspec = new X509EncodedKeySpec(decode);
// 获取公钥
return keyFactory.generatePublic(keyspec);
}
/**
* 从文件中加载私钥
*
* @param algorithm : 算法
* @param filePath : 文件路径
* @return : 私钥
* @throws Exception
*/
private static PrivateKey loadPrivateKeyFromFile(String algorithm, String filePath) throws Exception {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));
return loadPrivateKeyFromString(algorithm, keyString);
}
/**
* 从字符串中加载私钥
*
* @param algorithm : 算法
* @param keyString : 私钥字符串
* @return : 私钥
* @throws Exception
*/
private static PrivateKey loadPrivateKeyFromString(String algorithm, String keyString) throws Exception {
// 进行Base64解码
byte[] decode = Base64.decode(keyString);
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范
PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(decode);
// 生成私钥
return keyFactory.generatePrivate(keyspec);
}
/**
* 使用密钥加密数据
*
* @param algorithm : 算法
* @param input : 原文
* @param key : 密钥
* @param maxEncryptSize : 最大加密长度(需要根据实际情况进行调整)
* @return : 密文
* @throws Exception
*/
private static String encrypt(String algorithm, String input, Key key, int maxEncryptSize) throws Exception {
// 获取Cipher对象
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化模式(加密)和密钥
cipher.init(Cipher.ENCRYPT_MODE, key);
// 将原文转为byte数组
byte[] data = input.getBytes();
// 总数据长度
int total = data.length;
// 输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
decodeByte(maxEncryptSize, cipher, data, total, baos);
// 对密文进行Base64编码
return Base64.encode(baos.toByteArray());
}
/**
* 解密数据
*
* @param algorithm : 算法
* @param encrypted : 密文
* @param key : 密钥
* @param maxDecryptSize : 最大解密长度(需要根据实际情况进行调整)
* @return : 原文
* @throws Exception
*/
private static String decrypt(String algorithm, String encrypted, Key key, int maxDecryptSize) throws Exception {
// 获取Cipher对象
Cipher cipher = Cipher.getInstance(algorithm);
// 初始化模式(解密)和密钥
cipher.init(Cipher.DECRYPT_MODE, key);
// 由于密文进行了Base64编码, 在这里需要进行解码
byte[] data = Base64.decode(encrypted);
// 总数据长度
int total = data.length;
// 输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
decodeByte(maxDecryptSize, cipher, data, total, baos);
// 输出原文
return baos.toString();
}
/**
* 分段处理数据
*
* @param maxSize : 最大处理能力
* @param cipher : Cipher对象
* @param data : 要处理的byte数组
* @param total : 总数据长度
* @param baos : 输出流
* @throws Exception
*/
private static void decodeByte(int maxSize, Cipher cipher, byte[] data, int total, ByteArrayOutputStream baos) throws Exception {
// 偏移量
int offset = 0;
// 缓冲区
byte[] buffer;
// 如果数据没有处理完, 就一直继续
while (total - offset > 0) {
// 如果剩余的数据 >= 最大处理能力, 就按照最大处理能力来加密数据
if (total - offset >= maxSize) {
// 加密数据
buffer = cipher.doFinal(data, offset, maxSize);
// 偏移量向右侧偏移最大数据能力个
offset += maxSize;
} else {
// 如果剩余的数据 < 最大处理能力, 就按照剩余的个数来加密数据
buffer = cipher.doFinal(data, offset, total - offset);
// 偏移量设置为总数据长度, 这样可以跳出循环
offset = total;
}
// 向输出流写入数据
baos.write(buffer);
}
}
}
SM2
SM2 为椭圆曲线(ECC)公钥加密算法, 非对称加密,在我们国家商用密码体系中被用来替换 RSA 算法。
SM2 算法和 RSA 算法都是公钥加密算法,SM2 算法是一种更先进安全的算法。
目前支持SM2算法的产品已达1000余款,广泛应用于电子政务、移动办公、电子商务、移动支付、电子证书等基础设施、云服务等领域。
SM2推荐了一条256位的曲线作为标准曲线。

SM2 VS RSA
| SM2 | RSA | |
|---|---|---|
| 安全性 | 256 位 SM2 强度已超过 RSA-2048 | 一般 |
| 算法结构 | 基本椭圆曲线(ECC) | 基于特殊的可逆模幂运算 |
| 计算复杂度 | 完全指数级 | 亚指数级 |
| 存储空间(密钥长度) | 192-256 bit | 2048-4096 bit |
| 秘钥生成速度 | 较 RSA 算法快百倍以上 | 慢 |
| 解密加密速度 | 较快 | 一般 |
代码样例
Python
注意,在 Python 里面并没有比较官方的库来实现国密算法。
在第三方库中可以使用如下:
其中 gmssl-python 是 gmssl 的改进版,gmssl-python 新增支持了 SM9 算法。
加密(encrypt)和解密(decrypt)
from gmssl import sm2
# 16 进制的公钥和私钥
private_key = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
public_key = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'
sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)
# 待加密数据和加密后数据为 bytes 类型
data = b"this is the data to be encrypted"
enc_data = sm2_crypt.encrypt(data)
dec_data = sm2_crypt.decrypt(enc_data)
print('enc_data: ', enc_data.hex())
print('dec_data: ', dec_data)
# enc_data: 3cb96dd2e0b6c24df8e22a5da3951d061a6ee6ce99f46a446426feca83e501073288b1553ca8d91fad79054e26696a27c982492466dafb5ed06a573fb09947f2aed8dfae243b095ab88115c584bb6f0814efe2f338a00de42b244c99698e81c7913c1d82b7609557677a36681dd10b646229350ad0261b51ca5ed6030d660947
# dec_data: b'this is the data to be encrypted'
SM2 签名(sign)和校验(verify)
from gmssl import sm2, func
# 16 进制的公钥和私钥
private_key = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
public_key = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'
sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)
# 待签名数据为 bytes 类型
data = b"this is the data to be signed"
random_hex_str = func.random_hex(sm2_crypt.para_len)
# 16 进制
sign = sm2_crypt.sign(data, random_hex_str)
verify = sm2_crypt.verify(sign, data)
print('sign: ', sign)
print('verify: ', verify)
# sign: 45cfe5306b1a87cf5d0034ef6712babdd1d98547e75bcf89a17f3bcb617150a3f111ab05597601bab8c41e2b980754b74ebe9a169a59db37d549569910ae273a
# verify: True
JavaScript
在 JavaScript 中已有比较成熟的实现库,这里推荐 sm-crypto,目前支持 SM2、SM3 和 SM4。
SM2 非对称加密的结果由 C1、C2、C3 三部分组成,其中 C1 是生成随机数的计算出的椭圆曲线点,C2 是密文数据,C3 是 SM3 的摘要值。 需要注意的是,最开始的国密标准的结果是按 C1C2C3 顺序的,新标准的是按 C1C3C2 顺序存放的,sm-crypto 支持设置 cipherMode,也就是 C1C2C3 的排列顺序。
SM2 加密(encrypt)和解密(decrypt)
const sm2 = require('sm-crypto').sm2
// 1 - C1C3C2,0 - C1C2C3,默认为1
const cipherMode = 1
// 获取密钥对
let keypair = sm2.generateKeyPairHex()
let publicKey = keypair.publicKey // 公钥
let privateKey = keypair.privateKey // 私钥
let msgString = "this is the data to be encrypted"
let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) // 加密结果
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果
console.log("encryptData: ", encryptData)
console.log("decryptData: ", decryptData)
// encryptData: ddf261103fae06d0efe20ea0fe0d82bcc170e8efd8eeae24e9559b3835993f0ed2acb8ba6782fc21941ee74ca453d77664a5cb7dbb91517e6a3b0c27db7ce587ae7af54f8df48d7fa822b7062e2af66c112aa57de94d12ba28e5ba96bf4439d299b41da4a5282d054696adc64156d248049d1eb1d0af28d76b542fe8a95d427e
// decryptData: this is the data to be encrypted
SM2 签名(sign)和校验(verify)
const sm2 = require('sm-crypto').sm2
// 获取密钥对
let keypair = sm2.generateKeyPairHex()
let publicKey = keypair.publicKey // 公钥
let privateKey = keypair.privateKey // 私钥
// 纯签名 + 生成椭圆曲线点
let msgString = "this is the data to be signed"
let sigValueHex = sm2.doSignature(msgString, privateKey) // 签名
let verifyResult = sm2.doVerifySignature(msgString, sigValueHex, publicKey) // 验签结果
console.log("sigValueHex: ", sigValueHex)
console.log("verifyResult: ", verifyResult)
// sigValueHex: 924cbb9f2b5adb554ef77129ff1e3a00b2da42017ad3ec2f806d824a77646987ba8c8c4fb94576c38bc11ae69cc98ebbb40b5d47715171ec7dcea913dfc6ccc1
// verifyResult: true
国密SM9
SM9 为标识加密算法,IBE(Identity-Based Cryptography),非对称加密,标识加密将用户的标识(如微信号、邮件地址、手机号码、QQ 号等)作为公钥,省略了交换数字证书和公钥过程,使得安全系统变得易于部署和管理, SM9算法不需要申请数字证书,适用于互联网应用的各种新兴应用的安全保障,如基于云技术的密码服务、电子邮件安全、智能终端保护、物联网安全、云存储安全等等。
这些安全应用可采用手机号码或邮件地址作为公钥,实现数据加密、身份认证、通话加密、通道加密等安全应用,并具有使用方便、易于部署的特点。
在商用密码体系中,SM9 主要用于用户的身份认证,据新华网公开报道,SM9 的加密强度等同于 3072 位密钥的 RSA 加密算法。
自标准发布以来,已有厂商着手研制支持SM9算法的智能密码钥匙、标识密码机、密钥管理系统等系列基础产品,更多的应用单位基于SM9算法设计其系统方案。可以预见的是,SM9算法将会在更广的领域发挥其自身优势。
评论区