侧边栏壁纸
博主头像
Easy to understand and humorous

行动起来,活在当下

  • 累计撰写 39 篇文章
  • 累计创建 4 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

非对称加密算法: RSA、SM2、SM9

fengyang
2025-06-16 / 1 评论 / 1 点赞 / 24 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

非对称加密算法

常见算法

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算法将会在更广的领域发挥其自身优势。

1

评论区