一、非对称加密
1、定义
非对称加密算法需要两个密钥:公开密钥(Public Key:简称公钥)和私有密钥(Private Key:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
2、基本过程
甲方生成一对密钥并将公钥公开,需要向甲方发送信息的其他角色(乙方)使用该密钥(甲方的公钥)对机密信息进行加密后再发送给甲方;甲方再用自己私钥对加密后的信息进行解密。甲方想要回复乙方时正好相反,使用乙方的公钥对数据进行加密,同理,乙方使用自己的私钥来进行解密。
(图片来自网络)
3、签名
有了公私钥对,似乎解决了加密通信的难题。但是实际使用中又出问题了,那就是甲方收到消息后如何确认发信人是乙方而不是第三方呢?其实也很简单,只要发消息前多进行一次使用自己的私钥加密的过程就可以了,这次使用自己私钥加密信息的步骤就叫做签名。
私钥只有自己持有,公钥和私钥存在一一对应关系,即使用公钥只能解密出对应私钥加密的信息,因此就可以用私钥的加密过程当做验证身份的手段了。其实公钥、私钥加密数据的方法与原理都相同,只是按照用途分别命名了而已。
一般,公钥用来加密,私钥用来签名。
4、常见算法
常见的非对称加密算法有:RSA、ECC、Diffie-Hellman、El Gamal、DSA。其中,RSA算法使用最广泛。下面,介绍RSA算法的Java实现。
二、RSA算法实现
实现过程要用到Base64编码和解码,采用了Spring的工具类org.springframework.util.Base64Utils(可以调整为其他Base64工具类),完整实现见文末附录。
1、生成密钥对
具体实现:
/**
* 生成密钥对
*
* @return
* @throws Exception
*/
public KeyPair genKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512);
return keyGen.generateKeyPair();
}
调用过程:
public static void main(String[] args) throws Exception {
RSAUtil util = new RSAUtil();
// 生成密钥对
KeyPair keyPair = util.genKeyPair();
// 打印密钥对字符串
String publicKey = new String(Base64Utils.encode(keyPair.getPublic().getEncoded()));
String privateKey = new String(Base64Utils.encode(keyPair.getPrivate().getEncoded()));
log.info("\n公钥: {}\n私钥: {}", publicKey, privateKey);
}
输出结果:
公钥: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKK9VZq5Apq0kPSKBTJUybUEa/5WkvrM6Y2E0P0lF3J4ujtcvAF6PSRW9hm2CyKpmHnYOuH9dgBy23Y3HyT8TIcCAwEAAQ==
私钥: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAor1VmrkCmrSQ9IoFMlTJtQRr/laS+szpjYTQ/SUXcni6O1y8AXo9JFb2GbYLIqmYedg64f12AHLbdjcfJPxMhwIDAQABAkAZU70YZNW+bP6oSDix0hdISEVkYmXiiXSJtVNvKlAhXOAajnpDbTM3+mODpHq0NXrc9J26d3+E3pMI4Lcf4v4tAiEAy3yocAej0qJ5RK9NE4qCg1oeioMlMgt7nU+RBlW04KUCIQDMvLT0MZJHymAFbBEV/1TGICcN7Qn8yo7oU0GpgSEkuwIgTdvgxxzlPg8Uv4cjwrpYvdGZpf4QGVnzbnmnT/kzQFECIQDGhtG83Hio7n9Poquqte1BNRpJsal2nAAZHepU8CbwUwIhAJHDbY4+U42NzIcX9A56kx6u+Wk6jPgOiTFc0yYcWJmd
2、加密和解密
具体实现:
/**
* 加密
*
* @param content 原始字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception
*/
public String encrypt(String content, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return new String(Base64Utils.encode(bytes));
}
/**
* 解密
*
* @param content 密文字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception
*/
public String decrypt(String content, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(Base64Utils.decodeFromString(content));
return new String(bytes);
}
调用过程:
public static void main(String[] args) throws Exception {
RSAUtil util = new RSAUtil();
// 生成密钥对
KeyPair keyPair = util.genKeyPair();
// 加密和解密
String content = "Hello World!";
String encryptContent = util.encrypt(content, keyPair.getPublic());
String decryptContent = util.decrypt(encryptContent, keyPair.getPrivate());
log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
}
输出结果:
原始字符串: Hello World!
加密后:L5hzF00QDSpMJse9nMTs0bl3yi/P2PJHO1u6nULzgTMhWVIrZuAMA0ISzb6eIaMFK/UsLzr4Dnh8TB0b1Y0XMA==
解密后:Hello World!
3、密钥对象
一般来说,密钥对是以字符串形式存储在配置文件中的,这就需要转换为对象,并重载一下加密和解密方法。
具体实现:
private PublicKey getPublicKey(String publicKey) throws Exception {
byte[] keyBytes = Base64Utils.decode(publicKey.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(keySpec);
}
private PrivateKey getPrivateKey(String privateKey) throws Exception {
byte[] keyBytes = Base64Utils.decode(privateKey.getBytes(StandardCharsets.UTF_8));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
方法重载:
/**
* 加密
*
* @param content 原始字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception
*/
public String encrypt(String content, String publicKey) throws Exception {
PublicKey key = this.getPublicKey(publicKey);
return encrypt(content, key);
}
/**
* 解密
*
* @param content 密文字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception
*/
public String decrypt(String content, String privateKey) throws Exception {
PrivateKey key = this.getPrivateKey(privateKey);
return decrypt(content, key);
}
调用过程:
public static void main(String[] args) throws Exception {
RSAUtil util = new RSAUtil();
// 生成密钥对
KeyPair keyPair = util.genKeyPair();
// 打印密钥对字符串
String publicKey = new String(Base64Utils.encode(keyPair.getPublic().getEncoded()));
String privateKey = new String(Base64Utils.encode(keyPair.getPrivate().getEncoded()));
log.info("\n公钥: {}\n私钥: {}", publicKey, privateKey);
// 加密和解密
String content = "Hello World!";
String encryptContent = util.encrypt(content, publicKey);
String decryptContent = util.decrypt(encryptContent, privateKey);
log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
}
4、签名与验签(MD5)
常用签名有2种:MD5、SHA1,以下是MD5的具体实现:
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeMD5(String content, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return new String(Base64Utils.encode(signature.sign()));
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeMD5(String content, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
方法重载:
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeMD5(String content, String privateKey) throws Exception {
PrivateKey key = this.getPrivateKey(privateKey);
return encodeMD5(content, key);
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeMD5(String content, String sign, String publicKey) throws Exception {
PublicKey key = this.getPublicKey(publicKey);
return decodeMD5(content, sign, key);
}
调用过程:
public static void main(String[] args) throws Exception {
RSAUtil util = new RSAUtil();
// 生成密钥对
KeyPair keyPair = util.genKeyPair();
// 签名与验签(MD5)
String content2 = "lewis2951";
String sign = util.encodeMD5(content2, privateKey);
boolean verify = util.decodeMD5(content2, sign, publicKey);
log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
}
重载的方法,调用也是一样的,这里暂略。
输出结果:
原始字符串:lewis2951
签名字符串:KhcflLlhHq1MV0qEKi3bdOTjoA+Ylj0yTfFeKf3kJtv6ZVKWeWTPtqgaccmUJrSmXgpwyS8zoVLkYZcgs9D3uA==
验签结果:true
5、签名与验签(SHA1)
以下是SHA1的具体实现:
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeSHA1(String content, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return new String(Base64Utils.encode(signature.sign()));
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeSHA1(String content, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
方法重载:
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeSHA1(String content, String privateKey) throws Exception {
PrivateKey key = this.getPrivateKey(privateKey);
return encodeSHA1(content, key);
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeSHA1(String content, String sign, String publicKey) throws Exception {
PublicKey key = this.getPublicKey(publicKey);
return decodeSHA1(content, sign, key);
}
调用过程:
public static void main(String[] args) throws Exception {
RSAUtil util = new RSAUtil();
// 生成密钥对
KeyPair keyPair = util.genKeyPair();
// 签名与验签(SHA1)
String content2 = "lewis2951";
String sign = util.encodeSHA1(content2, privateKey);
boolean verify = util.decodeSHA1(content2, sign, publicKey);
log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
}
三、附录
1、完整实现
package org.lewis.demo.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
@Slf4j
public class RSAUtil {
/**
* 生成密钥对
*
* @return
* @throws Exception
*/
public KeyPair genKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512);
return keyGen.generateKeyPair();
}
/**
* 加密
*
* @param content 原始字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception
*/
public String encrypt(String content, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return new String(Base64Utils.encode(bytes));
}
/**
* 解密
*
* @param content 密文字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception
*/
public String decrypt(String content, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(Base64Utils.decodeFromString(content));
return new String(bytes);
}
private PublicKey getPublicKey(String publicKey) throws Exception {
byte[] keyBytes = Base64Utils.decode(publicKey.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(keySpec);
}
private PrivateKey getPrivateKey(String privateKey) throws Exception {
byte[] keyBytes = Base64Utils.decode(privateKey.getBytes(StandardCharsets.UTF_8));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
/**
* 加密
*
* @param content 原始字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception
*/
public String encrypt(String content, String publicKey) throws Exception {
PublicKey key = this.getPublicKey(publicKey);
return encrypt(content, key);
}
/**
* 解密
*
* @param content 密文字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception
*/
public String decrypt(String content, String privateKey) throws Exception {
PrivateKey key = this.getPrivateKey(privateKey);
return decrypt(content, key);
}
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeMD5(String content, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return new String(Base64Utils.encode(signature.sign()));
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeMD5(String content, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeMD5(String content, String privateKey) throws Exception {
PrivateKey key = this.getPrivateKey(privateKey);
return encodeMD5(content, key);
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeMD5(String content, String sign, String publicKey) throws Exception {
PublicKey key = this.getPublicKey(publicKey);
return decodeMD5(content, sign, key);
}
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeSHA1(String content, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return new String(Base64Utils.encode(signature.sign()));
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeSHA1(String content, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
/**
* 签名
*
* @param content 待签名字符串(明文)
* @param privateKey 密钥
* @return 签名字符串(密文)
* @throws Exception
*/
public String encodeSHA1(String content, String privateKey) throws Exception {
PrivateKey key = this.getPrivateKey(privateKey);
return encodeSHA1(content, key);
}
/**
* 验签
*
* @param content 待验签字符串(密文)
* @param sign 待比较的签名
* @param publicKey 公钥
* @return 验签结果
* @throws Exception
*/
public boolean decodeSHA1(String content, String sign, String publicKey) throws Exception {
PublicKey key = this.getPublicKey(publicKey);
return decodeSHA1(content, sign, key);
}
public static void main(String[] args) throws Exception {
RSAUtil util = new RSAUtil();
// 生成密钥对
KeyPair keyPair = util.genKeyPair();
// 打印密钥对字符串
String publicKey = new String(Base64Utils.encode(keyPair.getPublic().getEncoded()));
String privateKey = new String(Base64Utils.encode(keyPair.getPrivate().getEncoded()));
log.info("\n公钥: {}\n私钥: {}", publicKey, privateKey);
// 加密和解密
String content = "Hello World!";
// String encryptContent = util.encrypt(content, keyPair.getPublic());
// String decryptContent = util.decrypt(encryptContent, keyPair.getPrivate());
// log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
String encryptContent = util.encrypt(content, publicKey);
String decryptContent = util.decrypt(encryptContent, privateKey);
log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
// 签名与验签(MD5)
// String content2 = "lewis2951";
// String sign = util.encodeMD5(content2, privateKey);
// boolean verify = util.decodeMD5(content2, sign, publicKey);
// log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
// 签名与验签(SHA1)
String content2 = "lewis2951";
String sign = util.encodeSHA1(content2, privateKey);
boolean verify = util.decodeSHA1(content2, sign, publicKey);
log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
}
}