原文
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); }
对字符串解密
对用公钥加密后的数据,通过相对应的私钥解密:
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如下所示:
--->++++--->
优化
说明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如下:
--->--->
这样生成的密文看着美观很多了,而且,在网络传输时,不用因为有特殊字符而去转码了。
通过字符串加解密
有时候,我们项目将公钥私钥保存在文件中不太方便,或者是跟不同公司项目合作,对方只给你一个公钥字符串,此时要知道如何去实现加解密。
如何将文件形式的公钥私钥转成字符串形式的公钥私钥
其实转为字符串形式,我们主要是要从公钥私钥对象中,得到两个参数,模量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
用公钥字符串加密
直接上代码
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) }
打印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”。
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