4.Java开源RSA/SM2非对称加密算法介绍

前期内容导读:

  1. Java开源RSA/AES/SHA1/PGP/SM2/SM3/SM4加密算法介绍
  2. Java开源AES/SM4/3DES对称加密算法介绍及其实现
  3. Java开源AES/SM4/3DES对称加密算法的验证说明
  • 非对称加密主要是指秘钥对是非对称的(相对于对称加密而言),简单理解就是加密秘钥和解密秘钥不同,一般叫做公钥和私钥。公钥是需要给出去的,私钥需要自己保存,属于非常重要的隐私数据;
  • 非对称加密在加解密能力的基础上,还衍生出了签名和验证的能力,用于应对内容是否被篡改,就像古代的蜡封一样;
  • 由于非对称加密算法比较复杂,加解密效率并不高,加之当下服务都在分布式化,运行分布式服务的机器配置(包括VM)都不高,所以实际很少使用其加解密特性,大多都是使用其签名和验签能力,比如:加密机、PGP加密等;
  • 国际上非对称加密算法主要为RSA,与之对应的国密非对称加密算法为SM2,当下SM2的应用还相对较少;

1. 开源组件 非对称秘钥加密介绍

  • 加密组件引入方法:
    <dependency>
        <groupId>com.biuqu</groupId>
        <artifactId>bq-encryptor</artifactId>
        <version>1.0.5</version>
    </dependency>
    

1.1 非对称秘钥加密算法列表如下:

名称全称加密长度常用模式填充模式签名算法常用算法加密特点签名特点
RSA3人名缩写1024
2048
3072
4096
NONE
ECB
NoPadding
PKCS1Padding
OAEPPadding
SHA512WITHRSA
SHA256WITHRSA
RSA/NONE/NoPadding
RSA/ECB/PKCS1Padding
支持公钥加密,私钥解密
支持私钥加密,公钥解密
不支持分段加密
支持私钥签名,公钥验签
SM2SM2椭圆曲线公钥密码算法256--SM3WithSM2-国密算法,安全性优于RSA 2048,可用于替换RSA;
支持公钥加密,私钥解密
支持分段加密
支持私钥签名,公钥验签

说明:

  1. 加密长度: 在非加密算法中通常是指一次性可加密的密文块的长度。注意:加密长度不等于秘钥长度,在RSA/SM2中秘钥长度与加密长度是存在一定关系的;
  2. 在非对称加密算法中,RSA加密时,PKCS1Padding/OAEPPadding/NoPadding等多种填充算法,填充长度是不同的,这会直接影响加密的明文的长度;
  3. RSA虽然支持私钥加密公钥解密,但是这是不符合实际的应用场景的,因为公钥通常是要给出去的,有时不止给1个客户方,这样所有的客户方都可以解密了,框架中虽然可以支持这种方式,但是不推荐使用;
  4. 可以看下BouncyCastle源码org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.java,里面有各种填充模式;并且里面做了全大写转换匹配,所以不会有大小写问题;

1.2 非对称秘钥加密算法的特点如下:

  • 加密长度、秘钥和明文的关系表如下:
加密算法秘钥初始值
(byte)
加密长度
(bit)
私钥长度
(byte)
公钥长度
(byte)
生成耗时
(ms)
RSA10001024633-63616279
RSA100020481216-1218294612
RSA100030721792-17954222038
RSA100040962372-23765508577
SM210002561509115

说明:

  1. RSA私钥长度是个范围,公钥长度是固定值;
  2. SM2公私钥长度是固定值;
  3. 自验发现SM2生成秘钥的效率要远远高于RSA;RSA加密长度提升时,其生成耗时也增长了好几倍(限于本人机器性能和精力有限,仅做了20+次简单验证);
  • 明文长度、加密长度、填充算法与密文长度关系表:
加密算法加密长度
(bit)
填充算法填充长度
(byte)
明文长度
(byte)
密文长度
(byte)
加密耗时
(ms)
RSA1024/2048/3072/4096NoPadding01-${EncLen}${EncLen}400-500
RSA1024/2048/3072/4096PKCS1Padding111-(${EncLen}-11)${EncLen}400-500
RSA1024/2048/3072/4096OAEPPadding421-(${EncLen}-42)${EncLen}400-500
SM2256--19815
SM2256--29915
SM2256--15
SM2256--6516215

说明:

  1. ${EncLen}表示加密长度,RSA每次可加密的明文长度=${EncLen}-填充长度,明文超过部分需要做二次加密调用,但BouncyCastle RSA不支持二次调用;
  2. RSA加密时,PKCS1Padding填充符占用11byte,NoPadding占0byte,OAEPPadding填充符占用42byte;在C/C++资料中有PKCS1OAEPPadding填充算法,可能和Java中的OAEPPadding 是同一种填充算法,待验证;尤其说明下,好多线上资料都说OAEPPadding填充占41byte,大家也可以自行验证下;
  3. RSA加密时,选择NoPadding时,在批量加密时,经常报错,但因为这种模式本来就不安全,本身也是不推荐使用的;
  4. SM2 密文长度不是定长的,密文最低是98byte,每增加1byte的数据,密文也相应增加1byte;
  5. BouncyCastle SM2支持分段加密;

2. 开源组件 非对称秘钥加密实现

2.1 对称秘钥加密代码设计

bq-encryptor非对称秘钥加密代码设计
算法名称算法实现类抽象类是否安全补充说明
RSARsaEncryptionBaseSingleSignature2048及以上是安全的
SM2Sm2EncryptionRSA的国内替代算法

2.2 非对称秘钥加密核心逻辑

  • 非对称加密RSA与SM2实现的原理完全不同,所以抽象类BaseSingleSignature只做了公共接口定义,比如:生成秘钥、加密、解密、签名、验签等,具体的实现分别在算法实现类里面;

  • RSA与SM2的设计与实现会单独讲解;

3. 开源组件 非对称加密使用

以SM2算法为例,可以有如下3种使用方式:

  • 使用方式1:直接创建SM2加密对象
    BaseSingleSignature encryption = new Sm2Encryption();
    
  • 使用方式2:通过算法工厂创建SM2加密对象
    BaseSingleSignature encryption = EncryptionFactory.SM2.createAlgorithm();
    

4.RSA算法的的应用

4.1 RSA在JwtToken的应用

  • spring-security-oauth2-authorization-server自定义Jwt认证服务的秘钥:
    /**
     * 注入秘钥管理服务
     *
     * @param JWK 秘钥对象
     * @return 秘钥管理服务
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource(JWK jwk)
    {
        JWKSet jwkSet = new JWKSet(jwk);
        return (jwkSelector, context) -> jwkSelector.select(jwkSet);
    }
    
    ...
    
    /**
     * 生成JWK对象
     *
     * @param priKey 私钥(非必传时,表示仅需公钥验证)
     * @param pubKey 公钥
     * @param kid    秘钥id(可重新设置,重启后对所有客户端生效)
     * @return JWK秘钥对象
     */
    private static JWK genRsaKey(byte[] priKey, byte[] pubKey, String kid)
    {
        BaseSingleSignature encryption = EncryptionFactory.RSA.createAlgorithm();
        RSAPublicKey rsaKey = (RSAPublicKey)encryption.toPubKey(pubKey);
        RSAKey.Builder builder = new RSAKey.Builder(rsaKey);
        if (null != priKey)
        {
            PrivateKey rsaPriKey = encryption.toPriKey(priKey);
            builder.privateKey(rsaPriKey);
        }
        if (null == kid)
        {
            kid = UUID.randomUUID().toString();
        }
        return builder.keyID(kid).build();
    }
    
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐