简介
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”。