本文目录
- 前言
- RSA非对称加密技术的定义
- RAS非对称加密技术使用场景
- RSA秘钥初始化
- 秘钥初始化文件、加密、解密、获取秘钥文件工具类
- 后端测试加密、解密
- 公钥获取接口开发
- 附页
- 前端加密测试
- 小咸鱼的技术窝
前言
这段时间一方面忙着毕业论文的事情,一边忙着工作上的事情,也是好久都没有动笔了,其实在这段时间技术上也收货蛮多的了,包括编码规范上的,还有技术深度上的,SQL上的等等,遇到了很多企业开发项目上的问题,但是我最最最想分享的技术,首当其冲就是本文了,后续的内容会不定时更新,感兴趣的小伙伴可以关注一下我。本文要分享的东西是,Oauth2 分布式认证授权相关的东西,用到了Oauth2、RSA加密相关的技术,其中遇到了一些问题,最后也是解决了,特此记录一下,并分享一些心得感悟!~
RSA非对称加密技术的定义
简单点来说总结如下:
- 公钥加密的数据只有私钥能解开
- 私钥加密的数据只有公钥钥能解开
由于加密、解密用到的秘钥不是同一把,因而被赋予非对称的美名,那么 非对称加密的好处
- 安全:公钥任何人都可以获取,私钥保存在自己服务器上。加密、解密用到的秘钥不是同一把。
- 我个人觉得扩展性比较好。假设我们开发了一个认证中心,下面对接了5套子服务,无论新增多少套新的子服务需要使用到认证中心,颁发给子服务一个公钥进行开发即可,解密的逻辑认证中心无需更改。总结即:一个私钥可以对接多个公钥。如果使用对称加密技术,是不是每增加一套子服务,认证中心也要增加一套解密的逻辑!
RAS非对称加密技术使用场景
- 任何涉及到网络传输数据的地方都可以使用(登录过程中的密码公钥加密,后端私钥解密。一对一聊天对发送人发送的内容加密,接收人私钥解密等等)
- 根据业务需求对你需要的数据进行加密,这里就不一一列举了
RSA秘钥初始化
没啥好说的就是利用JAVA IO流相关的方法在指定路劲中创建我们的秘钥文件。下面的代码逻辑总结:如果没有文件目录就先创建目录,接着该目录下不存在秘钥文件就接着创建文件,存在就不创建文件。采用了 KeyPairGenerator.generateKeyPair()的方式初始化秘钥
@Value("${rsa.private.key.path}")
private String privateKeyFilePath;
@Value("${rsa.public.key.path}")
private String publicKeyFilePath;
@PostConstruct
public void initRSAKey() {
log.info("init rsa key file....");
FileWriter privateKeyFileWriter = null;
FileWriter publicKeyFileWriter = null;
try {
System.err.println("privateKeyFilePath:" + privateKeyFilePath);
System.err.println("publicKeyFilePath:" + publicKeyFilePath);
File privateKeyFile = new File(privateKeyFilePath);
File publicKeyFile = new File(publicKeyFilePath);
File privateKeyParentFile = privateKeyFile.getParentFile();
File publicKeyParentFile = publicKeyFile.getParentFile();
log.info("privateKeyParentFile:" + privateKeyParentFile + " mkdir " + " publicKeyParentFile:" + publicKeyParentFile + " mkdir");
if (!privateKeyFile.exists()) privateKeyParentFile.mkdir();
if (!publicKeyParentFile.exists()) publicKeyParentFile.mkdir();
log.info("privateKeyFilePath:" + privateKeyFilePath + " is file:" + privateKeyFile.isFile() + ",publicKeyFilePath:" + publicKeyFilePath + " is file:" + publicKeyFile.isFile());
if (privateKeyFile.isFile() && publicKeyFile.isFile()) return;
boolean createPrivateKeyFile = privateKeyFile.createNewFile();
boolean createPublicKeyFile = publicKeyFile.createNewFile();
if (!createPrivateKeyFile || !createPublicKeyFile) {
privateKeyFile.delete();
publicKeyFile.delete();
log.info("rsa key file create failed!");
return;
}
KeyPair keyPair = MyRSA.initKey();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
byte[] privateKeyByte = privateKey.getEncoded();
byte[] publicKeyByte = publicKey.getEncoded();
privateKeyFileWriter = new FileWriter(privateKeyFile);
privateKeyFileWriter.write(Base64.encodeBase64String(privateKeyByte));
privateKeyFileWriter.flush();
publicKeyFileWriter = new FileWriter(publicKeyFile);
publicKeyFileWriter.write(Base64.encodeBase64String(publicKeyByte));
publicKeyFileWriter.flush();
log.info("init rsa key file success!");
} catch (Exception e) {
e.printStackTrace();
log.error("init rsa key file error;Exception Message:" + e.getMessage());
} finally {
if (privateKeyFileWriter != null) {
try {
privateKeyFileWriter.close();
} catch (IOException e) {
e.printStackTrace();
log.error("init rsa key file error;Exception Message:" + e.getMessage());
}
}
if (publicKeyFileWriter != null) {
try {
publicKeyFileWriter.close();
} catch (IOException e) {
e.printStackTrace();
log.error("init rsa key file error;Exception Message:" + e.getMessage());
}
}
}
}
养成好的开发习惯,将路劲配置在YAML文件中!
rsa:
public:
key:
path: D:\\auth_key\\public.key
private:
key:
path: D:\\auth_key\\private.key
秘钥初始化文件、加密、解密、获取秘钥文件工具类
补充:最近安全检测报告说我的 RSA 密钥检测过弱,于是修改密钥长度为 1024 或者更大些即可。(2023-09-20)
工具类没啥好说的,开箱即用,里面包含了如下方法:
- 私钥加密
- 私钥解密
- 公钥加密
- 公钥解密
- 获取私钥
- 获取公钥
- 初始化公钥和私钥
public class MyRSA {
public static final String KEY_ALGORITHM = "RSA";
/**
* 密钥长度,DH算法的默认密钥长度是1024
* 密钥长度必须是64的倍数,在512到65536位之间
*/
private static final int KEY_SIZE = 1024;
public static KeyPair initKey() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static PublicKey getPublicKey(String pulickPath, String algorithm) throws Exception {
// 将文件内容转为字符串
String publicKeyString = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset());
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范 进行Base64解码
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString));
// 生成公钥
return keyFactory.generatePublic(spec);
}
public static PrivateKey getPrivateKey(String priPath, String algorithm) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
// 将文件内容转为字符串
String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 构建密钥规范 进行Base64解码
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
// 生成私钥
return keyFactory.generatePrivate(spec);
}
/**
* 私钥加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
/**
* 私钥解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] key) {
try {
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
} catch (Exception e) {
throw new RSAException(e.getMessage());
}
}
/**
* 公钥解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
}
后端测试加密、解密
事先我将秘钥直接生成在 D:/auth_key/public.key 路劲下了,直接调用工具类中的方法进行一波加密、解密的操作,效果入下图
PublicKey publickey = MyRSA.getPublicKey("D:/auth_key/public.key", "RSA");
String s = Base64.encodeBase64String(MyRSA.encryptByPublicKey("123456".getBytes(), publickey.getEncoded()));
System.err.println("公钥加密后的数据:"+s);
PrivateKey privateKey = MyRSA.getPrivateKey("D:/auth_key/private.key", "RSA");
String pwd = new String(MyRSA.decryptByPrivateKey(Base64.decodeBase64(s.getBytes()), privateKey.getEncoded()));
System.err.println("私钥解密后的数据:"+pwd);
公钥获取接口开发
接口就是使用基本的JAVA IO流相关的方法,读取公钥文件中的内容,返回给前端
@Value("${rsa.public.key.path}")
private String publicKeyFilePath;
@ApiOperation(value = "获取RSA公钥接口")
@GetMapping("/rsa/pubKey")
public Result pubKey() {
try {
BufferedReader keyFileReader = new BufferedReader(new FileReader(publicKeyFilePath));
String publicKey = keyFileReader.readLine();
return Result.success(publicKey);
} catch (IOException e) {
e.printStackTrace();
return Result.failed("解析公钥文件出现错误");
}
}
❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀到此本文结束❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀
附页
其实初始化秘钥文件还有另外一种方法,那就是利用 .jks 文件,初始化 KeyPair 这么一个Bean,需要用到公钥私钥的时候从注入 KeyPair 这个Bean,调用其中的获取 公钥、私钥方法即可。.jks 文件可以用 JAVA JDK 中自带的 Key Tool 工具类生成。这种方式初始化的 KeyPair 用于 JWT 的非对称加密校验。下面来简单测试一下,如下图(KeyPair生成的公钥加密、私钥解密)。值得一提的是利用jks文件生成的 KeyPair ,从中提取出来的公钥加密的数据,不能利用 keyPairGenerator生成的 KeyPair,从中提取的私钥解密
@Autowired
private KeyPair keyPair;
@Test
public void getKey() throws Exception {
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(rsaPublicKey).build();
String rsas = Base64.encodeBase64String(MyRSA.encryptByPublicKey("rsa公钥加密".getBytes(), key.toPublicKey().getEncoded()));
System.err.println("KeyPair公钥加密的数据: "+rsas);
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String b = new String(MyRSA.decryptByPrivateKey(Base64.decodeBase64(rsas.getBytes()), rsaPrivateKey.getEncoded()));
System.err.println("KeyPair私钥钥解密的数据: "+b);
PublicKey publickey = MyRSA.getPublicKey("D:/auth_key/public.key", "RSA");
String s = Base64.encodeBase64String(MyRSA.encryptByPublicKey("123456".getBytes(), publickey.getEncoded()));
System.err.println("公钥加密后的数据:"+s);
PrivateKey privateKey = MyRSA.getPrivateKey("D:/auth_key/private.key", "RSA");
String pwd = new String(MyRSA.decryptByPrivateKey(Base64.decodeBase64(s.getBytes()), privateKey.getEncoded()));
System.err.println("私钥解密后的数据:"+pwd);
}
关于 KeyPair 的东西本文只是提了一下,具体的初始化 KeyPair 以及 JWT 非对称加密校验的过程,之后有时间会陆续更新文章~。
前端加密测试
在输入框中放入公钥秘钥,点击test然后弹框中内容就是加密后的数据。
源码如下
<html>
<head>
<script type="text/javascript" src="./jsencrypt.js"></script>
<script type="text/javascript">
document.title = "RSA加密示例"
function a(){
var pubkey = document.getElementById('pubkey').value;
var encrypt = new JSEncrypt();
encrypt.setPublicKey(pubkey);
var encrypted = encrypt.encrypt(document.getElementById('input').value);
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(document.getElementById('privkey').value);
var uncrypted = decrypt.decrypt(encrypted);
console.log('输入框内容: '+document.getElementById('input').value);
console.log('加密后内容: '+encrypted);
console.log('解密后内容: '+uncrypted);
if (uncrypted == document.getElementById('input').value) {
alert(encrypted);
console.log(encrypt);
alert(uncrypted);
}
else {
alert('加密失败!');
}
}
</script>
</head>
<body>
<h1>JSEncrypt Example</h1>
<div class="container">
<label for="privkey">Private Key (私钥)</label>
<br/>
<textarea id="privkey" rows="20" cols="65"></textarea>
<br/>
<label for="pubkey">Public Key (公钥)</label>
<br/>
<textarea id="pubkey" rows="10" cols="65"></textarea>
<br/>
<label for="input">Text to encrypt:</label><br/>
<textarea id="input" name="input" type="text" rows=4 cols="65">111111</textarea><br/>
<input id="testme" type="button" value="Test" onclick="a()"/><br/>
</div>
</body>
</html>
js工具类
百度搜索 jsencrypt.js 下载一个就好了
小咸鱼的技术窝
关注不迷路,日后分享更多技术干货,B站、微信公众号同名,名称都是(小咸鱼的技术窝)更多详情在主页