• RSA 加解密(Java 实现)


      RSA 算法是一种非对称加解密算法。服务方生成一对 RSA 密钥,即公钥 + 私钥,将公钥提供给调用方,调用方使用公钥对数据进行加密后,服务方根据私钥进行解密。

    一、基础工具类

      下方工具类涵盖了生成 RSA 密钥对、加密、解密的方法,并附上了测试过程。

    package com.test.utils;
    
    import lombok.extern.slf4j.Slf4j;
    
    import javax.crypto.Cipher;
    import java.io.ByteArrayOutputStream;
    import java.security.*;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    import java.util.HashMap;
    import java.util.Map;
    
    @Slf4j
    public class RSAUtil {
    
        public static final String KEY_ALGORITHM = "RSA";
    
        private static final String PUBLIC_KEY = "RSAPublicKey";
    
        private static final String PRIVATE_KEY = "RSAPrivateKey";
    
    	// 1024 bits 的 RSA 密钥对,最大加密明文大小
        private static final int MAX_ENCRYPT_BLOCK = 117;
    
        // 1024 bits 的 RSA 密钥对,最大解密密文大小
        private static final int MAX_DECRYPT_BLOCK = 128;
    
        // 生成密钥对
        public static Map<String, Object> initKey(int keysize) throws Exception {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            // 设置密钥对的 bit 数,越大越安全
            keyPairGen.initialize(keysize);
            KeyPair keyPair = keyPairGen.generateKeyPair();
    
            // 获取公钥
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            // 获取私钥
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            Map<String, Object> keyMap = new HashMap<>(2);
            keyMap.put(PUBLIC_KEY, publicKey);
            keyMap.put(PRIVATE_KEY, privateKey);
            return keyMap;
        }
    
        // 获取公钥字符串
        public static String getPublicKeyStr(Map<String, Object> keyMap) {
            // 获得 map 中的公钥对象,转为 key 对象
            Key key = (Key) keyMap.get(PUBLIC_KEY);
            // 编码返回字符串
            return encryptBASE64(key.getEncoded());
        }
    
        // 获取私钥字符串
        public static String getPrivateKeyStr(Map<String, Object> keyMap) {
            // 获得 map 中的私钥对象,转为 key 对象
            Key key = (Key) keyMap.get(PRIVATE_KEY);
            // 编码返回字符串
            return encryptBASE64(key.getEncoded());
        }
    
        // 获取公钥
        public static PublicKey getPublicKey(String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {
            byte[] publicKeyByte = Base64.getDecoder().decode(publicKeyString);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyByte);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            return keyFactory.generatePublic(keySpec);
        }
    
        // 获取私钥
        public static PrivateKey getPrivateKey(String privateKeyString) throws Exception {
            byte[] privateKeyByte = Base64.getDecoder().decode(privateKeyString);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyByte);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            return keyFactory.generatePrivate(keySpec);
        }
    
        /**
         * BASE64 编码返回加密字符串
         *
         * @param key 需要编码的字节数组
         * @return 编码后的字符串
         */
        public static String encryptBASE64(byte[] key) {
            return new String(Base64.getEncoder().encode(key));
        }
    
    	/**
         * BASE64 解码,返回字节数组
         *
         * @param key 待解码的字符串
         * @return 解码后的字节数组
         */
        public static byte[] decryptBASE64(String key) {
            return Base64.getDecoder().decode(key);
        }
    
        /**
         * 公钥加密
         *
         * @param text         待加密的明文字符串
         * @param publicKeyStr 公钥
         * @return 加密后的密文
         */
        public static String encrypt1(String text, String publicKeyStr) {
            try {
                log.info("明文字符串为:[{}]", text);
                Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr));
                byte[] tempBytes = cipher.doFinal(text.getBytes("UTF-8"));
                return Base64.getEncoder().encodeToString(tempBytes);
            } catch (Exception e) {
                throw new RuntimeException("加密字符串[" + text + "]时遇到异常", e);
            }
        }
    
        /**
         * 私钥解密
         *
         * @param secretText    待解密的密文字符串
         * @param privateKeyStr 私钥
         * @return 解密后的明文
         */
        public static String decrypt1(String secretText, String privateKeyStr) {
            try {
                // 生成私钥
                Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr));
                // 密文解码
                byte[] secretTextDecoded = Base64.getDecoder().decode(secretText.getBytes("UTF-8"));
                byte[] tempBytes = cipher.doFinal(secretTextDecoded);
                return new String(tempBytes);
            } catch (Exception e) {
                throw new RuntimeException("解密字符串[" + secretText + "]时遇到异常", e);
            }
        }
    
        public static void main(String[] args) throws Exception {
            Map<String, Object> keyMap;
            String cipherText;
            // 原始明文
            String content = "春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。";
    
            // 生成密钥对
            keyMap = initKey(1024);
            String publicKey = getPublicKeyStr(keyMap);
            log.info("公钥:[{}],长度:[{}]", publicKey, publicKey.length());
            String privateKey = getPrivateKeyStr(keyMap);
            log.info("私钥:[{}],长度:[{}]", privateKey, privateKey.length());
    
            // 加密
            cipherText = encrypto(content, publicKey);
            log.info("加密后的密文:[{}],长度:[{}]", cipherText, cipherText.length());
    
            // 解密
            String plainText = decrypt1(cipherText, privateKey);
            log.info("解密后明文:[{}]", plainText);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163

      运行上述 main 函数,输出如下所示,可见加解密成功。

    公钥:[MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVk5GORH7kMgZBI7xN6dvlQ6NXO8/emNe/KOxFJRI1r49ecxSb41IBV7rcf+5z7hihbhXqUm65d3O0xcqDOqRYI0LgBxERsX0cPcAl7zSGDgynUC8jlTxBWlQjwb758vvmDRX3g5v0btUPObcyB1IgM3cpVDmsb5Obcl0Wxk2gGwIDAQAB],长度:[216]
    私钥:[MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJWTkY5EfuQyBkEjvE3p2+VDo1c7z96Y178o7EUlEjWvj15zFJvjUgFXutx/7nPuGKFuFepSbrl3c7TFyoM6pFgjQuAHERGxfRw9wCXvNIYODKdQLyOVPEFaVCPBvvny++YNFfeDm/Ru1Q85tzIHUiAzdylUOaxvk5tyXRbGTaAbAgMBAAECgYBFLe5JNYbWtghMgmGqS3onrEPUqdY3ZsuKHzw/sqicaelOTr1+aAHamx/Ssdywg7Oing7GxH9vij7aogxK64lsf/OD9Eq2DCvpbNValV/GVABRcCQLO6neHWujen3ex13ky1qtqCS6oZgbqDZyFAj5EGydJdbAgGTZ/rU0Mqbn+QJBANzi3i7a14KXRJYXx7r1UcTVbs30P0jk2F5SaPYsM0UW4Ec5qmqCv6mCYn6GHCJnlJiHvYt9QOUvWB9xuwHtXg0CQQCtWrITaT7P/jHqxzVc02nM420sNtOCrkSy60gZtfNSWFtTl/ddz2ACrN+fbfolUJhHaus3+ekyFoqpIdfNkpTHAkEAlrwO6SSYWtrFiDOULiZI9ay837klEqZwbPWKASwqlKRGyvQ0MlklWBTNCBCW1Heg9PH0zGLeTUggt9yRxH/qZQJAGaGhVtFm4iX5h3cw4qq3p/2wdKseluHhcnrrTDHk6jX6Ot/rSUmSLpMU9WOzarUB7v1WDg67dhZzJhLE77ZOnQJAAe/5aqiYn2SvAxMislav4PcNnbqnHVH+mmRlRP0xeuOKQQzAEn2nqYBY7vCUw3+SYGxe+EbNMLnrcHvTpQKPsw==],长度:[848]
    明文字符串为:[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。]
    加密后的密文:[iQIlICQHZJUUxslhyUJSjEdTWm+/B/OWhWXZHhX+ji5Zf7FjaNHr2BCb92ZOmA9BxsAPsS3bg1SajGyBP8fYIkKJpiqHuQ70Jt9xgwdTnaazJEw3BXWRIJIGwniKcEHZZQe+PjjZ4XNGaNSqCa8NtbuJJ6dfFYnv/T/6ANdBwyA=],长度:[172]
    解密后明文:[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    二、异常及原因分析

      当原始明文长度较小时,使用上述工具类加解密,没有问题。但是当明文长度过长时,会出现加密异常,如下所示。

    Exception in thread "main" java.lang.RuntimeException: 加密字符串[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。江流宛转绕芳甸,月照花林皆似霰。空里流霜不觉飞,汀上白沙看不见。江天一色无纤尘,皎皎空中孤月轮。江畔何人初见月?江月何年初照人?人生代代无穷已,江月年年望相似。]时遇到异常
    	at com.test.utils.RSAUtil.encrypto(RSAUtil.java:100)
    	at com.test.utils.RSAUtil.main(RSAUtil.java:146)
    Caused by: javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
    	at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)
    	at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
    	at javax.crypto.Cipher.doFinal(Cipher.java:2165)
    	at com.test.utils.RSAUtil.encrypto(RSAUtil.java:97)
    	... 1 more
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      原因分析如下。
      RSA 算法一次能加密的明文长度与密钥长度成正比,如 RSA 1024 实际可加密的明文长度最大是 1024 bits。如果小于这个长度怎么办?就需要进行数据补齐(padding),因为如果没有 padding,用户则无法确定解密后内容的真实长度。字符串之类的内容问题还不大,以 0 作为结束符,但对二进制数据就很难理解,因为不确定后面的 0 是内容还是内容结束符。
      只要用到 padding,那么就要占用实际的明文长度。对于 1024 bits(1024 / 8 = 128 byte[] ) 密钥对而言,如果 padding 方式使用默认的 OPENSSL_PKCS1_PADDING(需要占用 11 字节用于填充),则明文长度最多为 128 - 11 = 117 bytes,于是才有 117 字节的说法,即下面这种常见的说法:len_in_byte(原始明文数据)= len_in_bit(key)/ 8 - 11。
      我们一般使用的 padding 标准有 NoPadding、OAEPPadding、PKCS1Padding 等,其中PKCS1 建议的 padding 就占用了 11 个字节。对于 RSA 加密来讲,padding 也是要参与加密的,所以实际的明文只有 117 字节了。
      我们在把明文送给 RSA 加密器前,要确认这个值是不是大于位长,也就是如果接近位长,那么需要先 padding 再分段加密。除非我们是 “定长定量自己可控可理解” 的加密,不需要 padding。

      各种 padding 对输入数据长度的要求总结如下。

    • 私钥加密时的要求
    padding最大数据长度
    RSA_PKCS1_PADDINGRSA_size - 11
    RSA_NO_PADDINGRSA_size - 0
    RSA_X931_PADDINGRSA_size - 2
    • 公钥加密时的要求
    padding最大数据长度
    RSA_PKCS1_PADDINGRSA_size - 11
    RSA_SSLV23_PADDINGRSA_size - 11
    RSA_X931_PADDINGRSA_size - 2
    RSA_NO_PADDINGRSA_size - 0
    RSA_PKCS1_OAEP_PADDINGRSA_size - 2 * SHA_DIGEST_LENGTH - 2
    三、分段加解密
    	// 分段加密
        public static String encrypt2(String plainText, String publicKeyStr) throws Exception {
            log.info("明文:[{}],长度:[{}]", plainText, plainText.length());
            byte[] plainTextArray = plainText.getBytes("UTF-8");
            PublicKey publicKey = getPublicKey(publicKeyStr);
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int inputLen = plainTextArray.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            int i = 0;
            byte[] cache;
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(plainTextArray, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(plainTextArray, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptText = out.toByteArray();
            out.close();
            return Base64.getEncoder().encodeToString(encryptText);
        }
    
        // 分段解密
        public static String decrypt2(String encryptTextHex, String privateKeyStr) throws Exception {
            byte[] encryptText = Base64.getDecoder().decode(encryptTextHex);
            PrivateKey privateKey = getPrivateKey(privateKeyStr);
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            int inputLen = encryptText.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] plainText = out.toByteArray();
            out.close();
            return new String(plainText);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

      备注:当密钥对改为 2048 bits 时,最大加密明文大小为 2048 (bits) / 8 - 11(byte) = 245 byte,上述代码中的两个常量就变更为:

    	// 2048 bits 的 RSA 密钥对,最大加密明文大小
        private static final int MAX_ENCRYPT_BLOCK = 245;
    
        // 2048 bits 的 RSA 密钥对,最大解密密文大小
        private static final int MAX_DECRYPT_BLOCK = 256;
    
    • 1
    • 2
    • 3
    • 4
    • 5

      PKCS1 和 PKCS8 的区别如下。

    • 以 “-----BEGIN RSA PRIVATE KEY-----” 开头,以 “-----END RSA PRIVATE KEY-----” 结束的就是 PKCS1 格式;
    • 以 “-----BEGIN PRIVATE KEY-----” 开头,以 “-----END PRIVATE KEY-----” 结束的就是 PKCS8 格式。

      通常 JAVA 中需要 PKCS8 格式的密钥。

      在线 RSA 加解密网站:RSA 加解密

    文章参考:
  • 相关阅读:
    Vue图片路径问题(动态引入)
    【Android知识笔记】进程通信(一)
    React - React v18 的 批处理
    FullGC 过多 为什么会让CPU飙升100%
    Express 基础操作和详解
    关于漏洞:检测到目标SSL证书已过期【原理扫描】
    小程序iPhonex适配
    Redis--模糊查询--方法/实例
    【嵌入式linux开发】智能家居入门6:最新ONENET,物联网开放平台(QT、微信小程序、MQTT协议、ONENET云平台、旭日x3派)
    容器适配器【stack、queue、priority_queue和反向迭代器】
  • 原文地址:https://blog.csdn.net/piaoranyuji/article/details/126140261