分布式环境下使用RSA算法实现登录密码的加密传输

目录

效果

RSA介绍

实现思路

服务端实现

​RSAService:RSA算法的相关操作

RedisService:公钥和密钥的存储和获取

获取公钥的接口

客户端使用公钥加密

服务端使用私钥解密


效果

RSA介绍

        RSA是一种非对称加密算法。
        非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将公钥公开,需要向甲方发送信息的其他角色(乙方)使用该密钥(甲方的公钥)对机密信息进行加密后再发送给甲方;甲方再用自己私钥对加密后的信息进行解密。甲方想要回复乙方时正好相反,使用乙方的公钥对数据进行加密,同理,乙方使用自己的私钥来进行解密。

        在我们Web环境中,甲方即为我们的服务端,乙方即为客户端。服务端需要生成一个公钥和一个私钥,私钥保管在服务端不能泄露,公钥可以公开给所有客户端。

在这里插入图片描述

实现思路

  1. 服务端在启动时,生成一个公钥和一个私钥。由于是分布式环境,需要将公钥和私钥存储于Redis,并设置过期时间。公钥和私钥的被动 “续期”,参见代码实现。
  2. 为了公开向客户端公开公钥,服务端需要提供一个获取公钥的接口,该接口从Redis中获取公钥返回给客户端。
  3. 登录页面(客户端)通过公钥将登录密码加密,通过http请求服务端
  4. 服务端拿到加密后的密码(密文),从Redis中获取私钥解密,得到原始密码。将原始密码以注册时相同的加密规则加密,与数据库中存储的密码比对,即可判断密码是否正确

服务端实现

下面的所有实现代码均在:https://github.com/passerbyYSQ/forum

​RSAService:RSA算法的相关操作

/**
 * @author passerbyYSQ
 * @create 2022-09-25 16:42
 */
public interface RSAService {
    KeyPair generateKeyPair();

    String getPublicKey();

    String getPrivateKey();

    String decryptByPrivateKey(String encryptedText);

    String encryptByPublicKey(String rawText);
}
/**
 * @author passerbyYSQ
 * @create 2022-09-25 15:36
 */
@Service
@Slf4j
public class RSAServiceImpl implements RSAService, ApplicationListener<ContextRefreshedEvent> {
    @Resource
    private RedisService redisService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 对于web应用会出现父子容器,这样就会触发两次
        if (event.getApplicationContext().getParent() == null) {
            redisService.saveRSAKeyPair();
            log.info("成功将RSA的KeyPair缓存至Redis");
        }
    }

    @Override
    public KeyPair generateKeyPair() {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(1024, new SecureRandom());
            return generator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    public String getPublicKey() {
        KeyPair keyPair = redisService.getRSAKeyPair();
        byte[] publicBytes = keyPair.getPublic().getEncoded();
        byte[] base64Bytes = Base64.getEncoder().encode(publicBytes);
        return new String(base64Bytes);
    }

    public String getPrivateKey() {
        KeyPair keyPair = redisService.getRSAKeyPair();
        byte[] privateBytes = keyPair.getPrivate().getEncoded();
        byte[] base64Bytes = Base64.getEncoder().encode(privateBytes);
        return new String(base64Bytes);
    }

    public String decryptByPrivateKey(String encryptedText) {
        if (ObjectUtils.isEmpty(encryptedText)) {
            return null;
        }
        try {
            KeyPair keyPair = redisService.getRSAKeyPair();
            byte[] decodedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
            byte[] decryptedBytes = cipher.doFinal(decodedBytes);
            return new String(decryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public String encryptByPublicKey(String rawText) {
        if (ObjectUtils.isEmpty(rawText)) {
            return null;
        }
        try {
            KeyPair keyPair = redisService.getRSAKeyPair();
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
            byte[] encryptedBytes = cipher.doFinal(rawText.getBytes());
            byte[] base64Bytes = Base64.getEncoder().encode(encryptedBytes);
            return new String(base64Bytes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

RedisService:公钥和密钥的存储和获取

/**
 * @author passerbyYSQ
 * @create 2021-06-02 13:18
 */
public interface RedisService {

    void saveRSAKeyPair();

    KeyPair getRSAKeyPair();
}
/**
 * @author passerbyYSQ
 * @create 2021-06-02 13:21
 */
@Slf4j
@Service
public class RedisServiceImpl implements RedisService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private RSAService rsaService;
    
    @Override
    public void saveRSAKeyPair() {
        String key = String.format(Constant.REDIS_KEY_KEY_PAIR, "RSA");
        KeyPair keyPair = rsaService.generateKeyPair();
        if (!ObjectUtils.isEmpty(keyPair)) {
            redisTemplate.opsForValue().setIfAbsent(key, keyPair, Constant.DURATION_KEY_PAIR);
        }
    }

    @Override
    public KeyPair getRSAKeyPair() {
        String key = String.format(Constant.REDIS_KEY_KEY_PAIR, "RSA");
        KeyPair keyPair = (KeyPair) redisTemplate.opsForValue().get(key);
        if (ObjectUtils.isEmpty(keyPair)) {
            saveRSAKeyPair(); // KeyPair过期,则重新续费
        }
        // 重新获取KeyPair,因为如果并发续费,成功续上的有可能不是本机生成的KeyPair
        return (KeyPair) redisTemplate.opsForValue().get(key);
    }
}

获取公钥的接口

        Controller中新增一个方法,调用RedisService中的RsaService的getPublicKey()方法获取公钥。此处略,在项目中我是直接通过模板引擎,将公钥植入到登录页面的。如下图,代码仅供参考,思路是一样的。

客户端使用公钥加密

        前端使用的加密类库是 jsencrypt(https://github.com/travist/jsencrypt),js文件下载:https://github.com/travist/jsencrypt/tree/master/bin

        使用比较简单,参见上面截图

服务端使用私钥解密

        服务端调用 RsaService的decryptByPrivateKey()方法解密即可获得明文。下面代码截图,仅供参考,思路是一样的。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值