Java RSA非对称加密详解

来源:互联网 发布:程序员年度工作总结 编辑:程序博客网 时间:2024/05/16 02:35

原文

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();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

可以在相对路径下找到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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

对字符串解密

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

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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

测试

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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

打印的log如下所示:

生成的密文--->d0MzejV4uoQ4QJQJ+l22jdHQ0IEJshXxdIbyvmm3NBs7j/+9yPbOhsgLmywytZzKxsPDewSgcRf5+xi1BedMxVs3amb6tBicRX0uL02kKnE4d/K2W76JMS2g0oqbB+sX9BAFc8YgzJ4ZUoP44dZWSGVdTZHiRSnz2PPncmFqFsE=解密密文--->luoxiaohui
  • 1
  • 2
  • 3
  • 4

优化

说明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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

生成的log如下:

生成的密文--->42376F665A4C425770344D557444664F4E41526E326E5A37665978754F4E6C357062747069454D776F386E5573634D4F382B4475436279774234466A435236386C5631734E6C55724D637A470A7343736C53342F52536664766F313775304A2B4C4C434F326C4552364A7A523363737970477076337537753852376756484C4C704F43454C6E4264324C7A4955626B6D77594F544C6663724A0A75743271564B74512F734270653451546837383D解密密文--->luoxiaohui
  • 1
  • 2
  • 3

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

通过字符串加解密

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

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

其实转为字符串形式,我们主要是要从公钥私钥对象中,得到两个参数,模量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()));    }
  • 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

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

公钥模量:00d23587d5b17c717c033926981e10b0d39cd162e226dc5fee7073f91201c099fc86b6323acfd7bcf17e3cbb2a0ac4b0918322f0e6d6af94ba8b9094fbab4fe842ac418638c4bc83305e22a3ee9b9c5fa100daa3070f1fa2de56cffe3b80a74553d883e9695be523c568d38dfa56da9f4dab081d753f52a649dca85e07bc0fcdfd公钥指数:010001私钥模量:00d23587d5b17c717c033926981e10b0d39cd162e226dc5fee7073f91201c099fc86b6323acfd7bcf17e3cbb2a0ac4b0918322f0e6d6af94ba8b9094fbab4fe842ac418638c4bc83305e22a3ee9b9c5fa100daa3070f1fa2de56cffe3b80a74553d883e9695be523c568d38dfa56da9f4dab081d753f52a649dca85e07bc0fcdfd私钥指数:00924cc75926c9e181da0c709bf670cf60b807d2b66b2d7d66c9c52d5826f81133fbddda5fac400e345513977fcf36cd5cb8d41cadcc452f5215c86ea829b6d7822c57a96c7f4bd00d121dcde41bddef186d6ca2130047482f0ae92dcb5b9524c856f0b13e948d4fa59fe3fa7d7af4533e662503e314f6840db5523935a15c9e51
  • 1
  • 2
  • 3
  • 4
  • 5

用公钥字符串加密

直接上代码

 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;    }
  • 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

用私钥字符串解密

直接上代码~

/**     * 根据给定的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;    }
  • 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

测试

在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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

打印log如下:

加密后数据:35b7eece347b916732af92d293979328573c8f470209a46548d0b7d1a3d5b6751dd162c84b1d5153fdab6b590ea85a3f32fee1fd658f875b210ff792ff24b4b1960b8944f25e671ded9bb087ffe1915b8449f2e517d4b3039a13f27047befa39af8399b5452307fd8751dc8833dbd9b616a264ff2e3bdf3f827d1a90ec4a243f解密后数据:luoxiaohui
  • 1
  • 2

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

踩过的坑

java加密 C语言解密

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

Cipher ci = Cipher.getInstance(ALGORITHOM);
  • 1

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

RSAUtil 工具类下载

HexUtil 工具类下载

RSATest main方法

RSA 详细的算法使用java类

另外附上几篇好的文章

对称加密与非对称加密http://www.cnblogs.com/jfzhu/p/4020928.html

一个其他类似的Hex Util工具类:http://blog.csdn.net/lisheng19870305/article/details/68944485

对称和非对称加解密 签名  数字证书 : http://blog.csdn.net/wm_1991/article/details/51969922

公钥,私钥,数字签名:http://blog.csdn.net/tabactivity/article/details/49685319

原创粉丝点击