java RSA非对称加密详解

简介

RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。1987年首次公布,当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。此博文旨在实现具体用法,对算法原理不作阐述,算法详情请百度~

非对称加密 VS 对称加密 VS 不可逆加密

对称加密是因为加密和解密的钥匙相同,而非对称加密是加密和解密的钥匙不同。对称和非对称加密都是可逆的(因为有密钥对,一个负责加密,一个负责解密)。

对称加密

对称加密称为密钥加密,速度快,但加密和解密的钥匙必须相同,只有通信双方才能知道密钥,常见的有DES,3DES,AES对称加密。

非对称加密

非对称加密称为公钥加密,算法更加复杂,速度慢,加密和解密钥匙不相同,任何人都可以知道公钥,只有一个人持有私钥可以解密。常见的就是RSA了。

不可逆加密

还有一种加密方法:不可逆加密。典型的代表就是MD5加密了。

对称加密算法、非对称加密算法和不可逆加密算法可以分别应用于数据加密、身份认证和数据安全传输。

使用场景

使用场景就太多了,网络交互时,我们希望数据能经过加密后再传输,比如账户密码之类~

加解密的两种实现方式

RSA非对称加密,在我们具体实现的环境中,有两种方法,通过文件形式和字符串形式。

通过文件加解密

我们可以通过将公钥和私钥以文件形式保存,对某些需要加密的字符串进行加解密~

生成密钥对

不管是java,c,还是在其他语言当中,rsa算法是不变的。此为java
当中生成密钥对,并将密钥对保存到文件中,代码如下:

private static void generateKeyPair() throws Exception{
        /** RSA算法要求有一个可信任的随机数源 */
        SecureRandom sr = new SecureRandom();
        /** 为RSA算法创建一个KeyPairGenerator对象 */
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);
        /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
        kpg.initialize(KEYSIZE, sr);
        /** 生成密匙对 */
        KeyPair kp = kpg.generateKeyPair();
        /** 得到公钥 */
        Key publicKey = kp.getPublic();
        /** 得到私钥 */
        Key privateKey = kp.getPrivate();
        /** 用对象流将生成的密钥写入文件 */
        ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("publickey.keystore"));
        ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("privatekey.keystore"));
        oos1.writeObject(publicKey);
        oos2.writeObject(privateKey);
        /** 清空缓存,关闭文件输出流 */
        oos1.close();
        oos2.close();
    }

可以在相对路径下找到publickey.keystore和private.keystore两文件。

对字符串加密

public static String encrypt(String source) throws Exception{

        /** 将文件中的公钥对象读出 */
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("publickey.keystore"));
        Key key = (Key) ois.readObject();
        ois.close();
        /** 得到Cipher对象来实现对源数据的RSA加密 */
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] b = source.getBytes();
        /** 执行加密操作 */
        byte[] b1 = cipher.doFinal(b);
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(b1);
    }

对字符串解密

对用公钥加密后的数据,通过相对应的私钥解密:

public static String decrypt(String cryptograph) throws Exception{
        /** 将文件中的私钥对象读出 */
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("private.keystore"));
        Key key = (Key) ois.readObject();
        /** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, key);
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] b1 = decoder.decodeBuffer(cryptograph);
        /** 执行解密操作 */
        byte[] b = cipher.doFinal(b1);
        return new String(b);
    }

测试

public static void main(String[] args) throws Exception {

        String source = "luoxiaohui";//要加密的字符串
        String cryptograph = encrypt(source);//生成的密文
        System.out.println("生成的密文--->"+cryptograph);

        String target = decrypt(cryptograph);//解密密文
        System.out.println("解密密文--->"+target);
    }

打印的log如下所示:

生成的密文--->d0MzejV4uoQ4QJQJ+l22jdHQ0IEJshXxdIbyvmm3NBs7j/+9yPbOhsgLmywytZzKxsPDewSgcRf5
+xi1BedMxVs3amb6tBicRX0uL02kKnE4d/K2W76JMS2g0oqbB+sX9BAFc8YgzJ4ZUoP44dZWSGVd
TZHiRSnz2PPncmFqFsE=
解密密文--->luoxiaohui

优化

说明RSA加解密已经能实现啦!但看着这个密文感觉有点丑,有一些特殊符号,处于强迫症,我想把密文转为16进制:

String source = "luoxiaohui";//要加密的字符串

        String cryptograph = encrypt(source);//生成的密文
        String hexCrypt = HexUtil.bytes2Hex(cryptograph.getBytes(),false);
        System.out.println("生成的密文--->"+hexCrypt);

        String target = decrypt(HexUtil.hex2String(hexCrypt));//解密密文
        System.out.println("解密密文--->"+target);

生成的log如下:

生成的密文--->42376F665A4C425770344D557444664F4E41526E326E5A37665978754F4E6C357062747069454D776F386E5573634D4F382B4475436279774234466A435236386C5631734E6C55724D637A470A7343736C53342F52536664766F313775304A2B4C4C434F326C4552364A7A523363737970477076337537753852376756484C4C704F43454C6E4264324C7A4955626B6D77594F544C6663724A0A75743271564B74512F734270653451546837383D
解密密文--->luoxiaohui

这样生成的密文看着美观很多了,而且,在网络传输时,不用因为有特殊字符而去转码了。

通过字符串加解密

有时候,我们项目将公钥私钥保存在文件中不太方便,或者是跟不同公司项目合作,对方只给你一个公钥字符串,此时要知道如何去实现加解密。

如何将文件形式的公钥私钥转成字符串形式的公钥私钥

其实转为字符串形式,我们主要是要从公钥私钥对象中,得到两个参数,模量modulus和指数系数exponent,这两参数能重新构成公钥私钥对象,从而进行加解密,

private static void generateKeyPairString() throws Exception{
/** RSA算法要求有一个可信任的随机数源 */
        SecureRandom sr = new SecureRandom();
        /** 为RSA算法创建一个KeyPairGenerator对象 */
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);
        /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
        kpg.initialize(KEYSIZE, sr);
        /** 生成密匙对 */
        KeyPair kp = kpg.generateKeyPair();
        /** 得到公钥 */
        Key publicKey = kp.getPublic();
        /** 得到私钥 */
        Key privateKey = kp.getPrivate();
        /** 用字符串将生成的密钥写入文件 */

        String algorithm = publicKey.getAlgorithm(); // 获取算法
        KeyFactory keyFact = KeyFactory.getInstance(algorithm);
        BigInteger prime = null;
        BigInteger exponent = null;

        RSAPublicKeySpec keySpec = (RSAPublicKeySpec)keyFact.getKeySpec(publicKey, RSAPublicKeySpec.class);

        prime = keySpec.getModulus();
        exponent = keySpec.getPublicExponent();
        System.out.println("公钥模量:"+HexUtil.bytes2Hex(prime.toByteArray()));
        System.out.println("公钥指数:"+HexUtil.bytes2Hex(exponent.toByteArray()));


        System.out.println(privateKey.getAlgorithm());
        RSAPrivateCrtKeySpec privateKeySpec = (RSAPrivateCrtKeySpec)keyFact.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
        BigInteger privateModulus = privateKeySpec.getModulus();
        BigInteger privateExponent = privateKeySpec.getPrivateExponent();

        System.out.println("私钥模量:"+HexUtil.bytes2Hex(privateModulus.toByteArray()));
        System.out.println("私钥指数:"+HexUtil.bytes2Hex(privateExponent.toByteArray()));
    }

在main()方法中执行此方法,能得到公钥私钥的指数系数和模量,以16进制保存,打印的log如下:

公钥模量:00d23587d5b17c717c033926981e10b0d39cd162e226dc5fee7073f91201c099fc86b6323acfd7bcf17e3cbb2a0ac4b0918322f0e6d6af94ba8b9094fbab4fe842ac418638c4bc83305e22a3ee9b9c5fa100daa3070f1fa2de56cffe3b80a74553d883e9695be523c568d38dfa56da9f4dab081d753f52a649dca85e07bc0fcdfd
公钥指数:010001
私钥模量:00d23587d5b17c717c033926981e10b0d39cd162e226dc5fee7073f91201c099fc86b6323acfd7bcf17e3cbb2a0ac4b0918322f0e6d6af94ba8b9094fbab4fe842ac418638c4bc83305e22a3ee9b9c5fa100daa3070f1fa2de56cffe3b80a74553d883e9695be523c568d38dfa56da9f4dab081d753f52a649dca85e07bc0fcdfd
私钥指数:00924cc75926c9e181da0c709bf670cf60b807d2b66b2d7d66c9c52d5826f81133fbddda5fac400e345513977fcf36cd5cb8d41cadcc452f5215c86ea829b6d7822c57a96c7f4bd00d121dcde41bddef186d6ca2130047482f0ae92dcb5b9524c856f0b13e948d4fa59fe3fa7d7af4533e662503e314f6840db5523935a15c9e51

用公钥字符串加密

直接上代码

 public static RSAPublicKey getRSAPublicKey(String hexModulus, String hexPublicExponent) {
        if (isBlank(hexModulus) || isBlank(hexPublicExponent)) {
            System.out.println("hexModulus and hexPublicExponent cannot be empty. return null(RSAPublicKey).");
            return null;
        }
        byte[] modulus = null;
        byte[] publicExponent = null;
        try {
            modulus = HexUtil.hex2Bytes(hexModulus);
            publicExponent = HexUtil.hex2Bytes(hexPublicExponent);
        } catch (Exception ex) {
            System.out.println("hexModulus or hexPublicExponent value is invalid. return null(RSAPublicKey).");
            ex.printStackTrace();
        }
        if (modulus != null && publicExponent != null) {
            return generateRSAPublicKey(modulus, publicExponent);
        }
        return null;
    }

    public static String encryptString(PublicKey publicKey, String plaintext) {
        if (publicKey == null || plaintext == null) {
            return null;
        }
        byte[] data = plaintext.getBytes();
        try {
            byte[] en_data = encrypt(publicKey, data);
            return new String(HexUtil.bytes2Hex(en_data));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

用私钥字符串解密

直接上代码~

/**
     * 根据给定的16进制系数和专用指数字符串构造一个RSA专用的私钥对象。
     *
     * @param hexModulus         系数。
     * @param hexPrivateExponent 专用指数。
     * @return RSA专用私钥对象。
     */
    public static RSAPrivateKey getRSAPrivateKey(String hexModulus, String hexPrivateExponent) {
        if (isBlank(hexModulus) || isBlank(hexPrivateExponent)) {
            System.out.println("hexModulus and hexPrivateExponent cannot be empty. RSAPrivateKey value is null to return.");
            return null;
        }
        byte[] modulus = null;
        byte[] privateExponent = null;
        try {
            modulus = HexUtil.hex2Bytes(hexModulus);
            privateExponent = HexUtil.hex2Bytes(hexPrivateExponent);
        } catch (Exception ex) {
            System.out.println("hexModulus or hexPrivateExponent value is invalid. return null(RSAPrivateKey).");
            ex.printStackTrace();
        }
        if (modulus != null && privateExponent != null) {
            return generateRSAPrivateKey(modulus, privateExponent);
        }
        return null;
    }

    /**
     * 使用给定的私钥解密给定的字符串。
     *
     * 若私钥为 {@code null},或者 {@code encrypttext} 为 {@code null}或空字符串则返回 {@code null}。
     * 私钥不匹配时,返回 {@code null}。
     *
     * @param privateKey  给定的私钥。
     * @param encrypttext 密文。
     * @return 原文字符串。
     */
    public static String decryptString(PrivateKey privateKey, String encrypttext) {
        if (privateKey == null || isBlank(encrypttext)) {
            return null;
        }
        try {
            byte[] en_data = HexUtil.hex2Bytes(encrypttext);
            byte[] data = decrypt(privateKey, en_data);
            return new String(data);
        } catch (Exception ex) {
            System.out.println(String.format("\"%s\" Decryption failed. Cause: %s", encrypttext, ex.getCause().getMessage()));

        }
        return null;
    }

测试

在main()中执行方法:

public static void main(String[] args) {

        String source = "luoxiaohui";

        PublicKey publicKey = RSAUtil.getRSAPublicKey(publicModulus, publicexponent);
        String encript = RSAUtil.encryptString(publicKey, source);
        System.out.println("加密后数据:"+encript);

        PrivateKey privateKey = RSAUtil.getRSAPrivateKey(privateModulus, privateexponent);
        String newSource = RSAUtil.decryptString(privateKey, encript);

        System.out.println("解密后数据:"+newSource);
    }

打印log如下:

加密后数据:35b7eece347b916732af92d293979328573c8f470209a46548d0b7d1a3d5b6751dd162c84b1d5153fdab6b590ea85a3f32fee1fd658f875b210ff792ff24b4b1960b8944f25e671ded9bb087ffe1915b8449f2e517d4b3039a13f27047befa39af8399b5452307fd8751dc8833dbd9b616a264ff2e3bdf3f827d1a90ec4a243f
解密后数据:luoxiaohui

到此,通过原有的模量和指数加密,然后再解密,功能已全部实现~

踩过的坑

java加密 C语言解密

由于项目用RSA非对称加密需求,当前端用java加密,后端用C语言解密,需要注意

Cipher ci = Cipher.getInstance(ALGORITHOM);

当中的填充参数ALGORITHOM,要跟C语言对称,java有多种填充方式,默认是”RSA”,而c语言中有且只有一种”RSA/ECB/NoPadding”,如果后台对应c语言解密,切记我们java前端需要将此参数改为”RSA/ECB/NoPadding”。

代码下载

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值