引言

        久等了,上篇文章的彩蛋部分来了,数据通信中,安全是至关重要的一环,常见的加密算法有AES对称加密算法,RSA非对称加密算法,稍后以图文并茂的形式告诉你它们之间的区别,本文将详细介绍如何在Java中实现RSA非对称加密算法对数据进行加密解密

AES和RSA区别

算法 类型 安全性 密钥长度/输出长度 速度 应用场景
AES 对称加密 128位、192位、256位 适用于大规模数据加密,入HTTPS协议的数据传输
RSA 非对称加密 1024位、2048位、4096位 较慢 适用于数据安全传输、数字签名和身份验证
  • 安全性
    • AES:支持多种密钥长度,长度越长安全性越高,能抵抗多种攻击,入暴力破解、拆分攻击和线性攻击等
    • RSA:支持多种密钥长度,长度越长安全性越高,能确保密钥持有者才能解密信息
  • 工作原理
    • AES:采用对称密钥加密,即加密解密使用相同密钥
    • RSA:采用非对称加密,即使用公钥加密,私钥解密,基于大数难题:质因数分解问题和离散对数问题,通过选择两个大素数并计算其乘积作为模数N,然后选择一个φ(N)互质的整数e作为公钥指数,最后计算私钥指数d是的e*d=1 mod φ(N)
  • 性能
    • AES:具有高效的加密和解密速度
    • RSA:加密解密速度相对较慢,尤其在处理大数据的时候
  • 应用场景 
    • AES:数据传输、文件加密等,保护敏感数据
    • RSA:安全数据传输、数字签名、身份验证和密钥交换等,通过密钥保证了数据的绝对安全

        实际应用中根据业务场景选择合适的加密算法,本文以用户密码传输加密为例,这种属于安全数据,我采用RSA非对称加密算法对其进行加密解密操作

承接我的上篇文章,如何高效地架构一个Java项目,我们在项目的登录接口中使用RSA非对称加密算法编写简单的代码示例

代码示例

创建RSA工具类

package com.muze.project.util;

import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA非对称加密算法工具类
 * @author muze
 */
public class RSAUtil {
    /**
     * RSA算法
     */
    private static final String RSA_ALGORITHM = "RSA";

    /**
     * 公私钥长度
     */
    private static final Integer KEY_LENGTH = 2048;

    /**
     * RSA算法公钥
     */
    private static final String RSA_PUBLIC_KEY = "RSAPublicKey";

    /**
     * RSA算法私钥
     */
    private static final String RSA_PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 数据加密公钥
     */
    private static final String PUBLIC_KEY = "生成后替换";

    /**
     * 数据解密公钥
     */
    private static final String PRIVATE_KEY = "生成后替换";

    /**
     * 生成数据加密公钥和数据解密私钥
     * @return 数据加密公钥和解密私钥键值对
     * @throws NoSuchAlgorithmException 不匹配算法异常
     */
    private static Map<String, Key> generatePublicAndPrivateKey() throws NoSuchAlgorithmException {
        // 创建KeyPairGenerator(Java加密架构(JCA)中的一个类)实例,用于生成公钥和私钥
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        // 初始化KeyPairGenerator实例,指定公钥和私钥长度,安全和性能综合考虑推荐2048
        keyPairGenerator.initialize(KEY_LENGTH);
        // 生成包含密钥对的实例
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 提取公钥
        Key publicKey = keyPair.getPublic();
        // 提取私钥
        Key privateKey = keyPair.getPrivate();
        // 创建一个初始容量位2的键值对,用于存储公私钥
        Map<String, Key> keyMap = new HashMap<>(2);
        // 将公钥放入键值对
        keyMap.put(RSA_PUBLIC_KEY, publicKey);
        // 将私钥放入键值对
        keyMap.put(RSA_PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 获取公钥
     * @param keyMap 公私钥键值对
     * @return 公钥字符串
     */
    private static String getPublicKey(Map<String, Key> keyMap) {
        // 从公私钥键值对中取出公钥
        Key publicKey = keyMap.get(RSA_PUBLIC_KEY);
        // 获取公钥字节数组
        byte[] publicKeyBytes = publicKey.getEncoded();
        // 使用Base64编码将公钥字节数组转换为字符串
        return Base64Utils.encodeToString(publicKeyBytes);
    }

    /**
     * 获取私钥
     * @param keyMap 公私钥键值对
     * @return 私钥字符串
     */
    private static String getPrivateKey(Map<String, Key> keyMap) {
        Key privateKey = keyMap.get(RSA_PRIVATE_KEY);
        byte[] privateKeyBytes = privateKey.getEncoded();
        return Base64Utils.encodeToString(privateKeyBytes);
    }

    /**
     * 公钥加密
     * @param needEncrypt 需要加密数据
     * @return 公钥加密后的字符串
     * @throws Exception 异常
     */
    public static String encryptByPublicKey(String needEncrypt) throws Exception {
        // 使用Base64编码将数据加密公钥转换为字节数组
        byte[] publicKeyBytes = Base64Utils.decodeFromString(PUBLIC_KEY);
        // 使用数据加密公钥字符数组创建一个X509EncodedKeySpec(密钥规范:公钥的ASN.1编码的一种表示形式)实例
        KeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        // 创建KeyFactory(JCE框架中的一个类)实例,用于转换公钥和私钥
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        // 从x509EncodedKeySpec(密钥规范:公钥的ASN.1编码的一种表示形式)中生成一个公钥
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        // 创建Cipher实例,用于加密解密
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        // 初始化Cipher实例,设置加密模式并指定要使用的公钥
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 加密为字节数组
        byte[] encryptBytes = cipher.doFinal(needEncrypt.getBytes());
        // 使用Base64编码将加密后的数组转换为字符串
        return Base64Utils.encodeToString(encryptBytes);
    }

    /**
     * 私钥解密
     * @param needDecrypt 需要解密数据
     * @return 私钥解密后的字符串
     * @throws Exception 异常
     */
    public static String decryptByPrivateKey(String needDecrypt) throws Exception {
        // 使用Base64编码将数据加密私钥转换为字节数组
        byte[] privateBytes = Base64Utils.decodeFromString(PRIVATE_KEY);
        // 使用数据解密私钥字符数组创建一个PKCS8EncodedKeySpec(密钥规范:私钥的ASN.1编码的一种表示形式)实例
        KeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);
        // 创建KeyFactory(JCE框架中的一个类)实例,用于转换公钥和私钥
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        // 从pkcs8EncodedKeySpec(密钥规范:私钥的ASN.1编码的一种表示形式)中生成一个私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        // 创建Cipher实例,用于加密解密
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        // 初始化Cipher实例,设置解密模式并指定要使用的私钥
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 使用Base64编码将需要解密数据转换为字节数组
        byte[] needDecryptBytes = Base64Utils.decodeFromString(needDecrypt);
        // 解密为字节数组
        byte[] decryptBytes = cipher.doFinal(needDecryptBytes);
        return new String(decryptBytes);
    }

    /**
     * 生成数据加密公钥和数据解密私钥后,将值替换到常量PUBLIC_KEY和PRIVATE_KEY
     * @param args 启动参数
     * @throws Exception 异常
     */
    public static void main(String[] args) throws Exception {
        Map<String, Key> keyMap = generatePublicAndPrivateKey();
        String publicKey = getPublicKey(keyMap);
        String privateKey = getPrivateKey(keyMap);
        System.out.println("数据加密公钥:" + publicKey);
        System.out.println("数据解密私钥:" + privateKey);

        // 替换公私钥后将上方代码注释,放开下面注释,测试加密解密功能
//        String data = "原始数据";
//        String encrypt = encryptByPublicKey(data);
//        System.out.println("公钥加密后的数据:" + encrypt);
//        String decrypt = decryptByPrivateKey(encrypt);
//        System.out.println("私钥解密后的数据:" + decrypt);
    }
}

生成公私钥 

运行main方法后替换数据加密公钥和解密私钥

 注释掉生成公私钥代码,放开测试加密解密功能代码

测试加密解密功能

测试加密解密功能结果,保留公钥加密后的数据

修改登录接口实现

将公钥提供给前端同事,然后修改登录接口中的密码校验逻辑,使用保留的公钥加密后的数据进行接口调试

/**
 * 用户业务实现层
 * @author muze
 */
@Service
public class UserServiceImpl implements IUserService {
    /**
     * 注入用户数据层
     */
    @Autowired
    private UserMapper userMapper;
    @Override
    public String login(UserLoginDTO userLoginDTO) {
        // 取出用户名和密码
        String username = userLoginDTO.getUsername();
        String password = userLoginDTO.getPassword();
        // 构建查询条件
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getUsername, username);
        // 查询用户
        User user = userMapper.selectOne(userLambdaQueryWrapper);
        //************************  新增密码解密  ************************//
        String decryptPassword;
        try {
            decryptPassword = RSAUtil.decryptByPrivate(password);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 如果用户为空或者解密后的输入密码与用户密码不匹配则返回:用户名或密码错误
        if (user == null || !decryptPassword.equals(user.getPassword())) return "用户名或密码错误";
        // 使用SaToken的工具类StpUtil调用登录方法login,入参:用户id
        StpUtil.login(user.getId());
        // 返回:登录成功
        return "登录成功";
    }
}

测试登录接口

启动项目,使用接口调试工具测试修改后的加密解密登录功能

        到这里我们就一起完成了一个简单的Java中实现RSA非对称加密算法对数据进行加密解密,相信你已经掌握了,赶快去试试吧,希望这篇文章能对你有所帮助 !

彩蛋:细心的你肯定发现了,当我们进行密码解密时会有异常,捕获后抛出了一个运行时异常,这样不利于排查问题和给用户提供友好的错误提示,所以我们应该使用自定义异常来提升排查问题的速度和提高用户体验感,小编会在后续更新一篇与自定义异常相关的文章,敬请期待

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐