RAS非对称加密技术在分布式项目中的应用

时间:2024-01-21 13:05:06



本文目录

  • 前言
  • RSA非对称加密技术的定义
  • RAS非对称加密技术使用场景
  • RSA秘钥初始化
  • 秘钥初始化文件、加密、解密、获取秘钥文件工具类
  • 后端测试加密、解密
  • 公钥获取接口开发
  • 附页
  • 前端加密测试
  • 小咸鱼的技术窝


前言

这段时间一方面忙着毕业论文的事情,一边忙着工作上的事情,也是好久都没有动笔了,其实在这段时间技术上也收货蛮多的了,包括编码规范上的,还有技术深度上的,SQL上的等等,遇到了很多企业开发项目上的问题,但是我最最最想分享的技术,首当其冲就是本文了,后续的内容会不定时更新,感兴趣的小伙伴可以关注一下我。本文要分享的东西是,Oauth2 分布式认证授权相关的东西,用到了Oauth2、RSA加密相关的技术,其中遇到了一些问题,最后也是解决了,特此记录一下,并分享一些心得感悟!~

RSA非对称加密技术的定义

简单点来说总结如下:

  • 公钥加密的数据只有私钥能解开
  • 私钥加密的数据只有公钥钥能解开

由于加密、解密用到的秘钥不是同一把,因而被赋予非对称的美名,那么 非对称加密的好处

  • 安全:公钥任何人都可以获取,私钥保存在自己服务器上。加密、解密用到的秘钥不是同一把。
  • 我个人觉得扩展性比较好。假设我们开发了一个认证中心,下面对接了5套子服务,无论新增多少套新的子服务需要使用到认证中心,颁发给子服务一个公钥进行开发即可,解密的逻辑认证中心无需更改。总结即:一个私钥可以对接多个公钥。如果使用对称加密技术,是不是每增加一套子服务,认证中心也要增加一套解密的逻辑!

RAS非对称加密技术使用场景

  1. 任何涉及到网络传输数据的地方都可以使用(登录过程中的密码公钥加密,后端私钥解密。一对一聊天对发送人发送的内容加密,接收人私钥解密等等)
  2. 根据业务需求对你需要的数据进行加密,这里就不一一列举了

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);

RAS非对称加密技术在分布式项目中的应用_非对称加密解密

公钥获取接口开发

接口就是使用基本的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);
    }

RAS非对称加密技术在分布式项目中的应用_RSA_02


关于 KeyPair 的东西本文只是提了一下,具体的初始化 KeyPair 以及 JWT 非对称加密校验的过程,之后有时间会陆续更新文章~。

前端加密测试

在输入框中放入公钥秘钥,点击test然后弹框中内容就是加密后的数据。

RAS非对称加密技术在分布式项目中的应用_数据_03


源码如下

<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站、微信公众号同名,名称都是(小咸鱼的技术窝)更多详情在主页