用非对称性加密保障Web登录安全

一、技术背景介绍

  • 后端:Java+Spring Boot + Spring Security + JWT
  • 前端:Ant Design Pro

二、Web登录安全隐患

如果你的后台管理系统登录情况和下图的情况一样,登录时用户名、密码是明文传输的,那么你是时候考虑要用非对称性加密来保障Web登录安全了。

登录名密码被暴露

三、HTTPS就一定安全吗?

HTTPS存在两种可能的风险:

1、HTTPS可以保证传输过程中的信息不被别人截获,但是细细思考下,HTTPS是应用层协议,下层采用SSL保证信息安全,但是在客户端和服务端,密文同样是可以被截获的;

2、HTTPS报文在传输过程中,如果客户端被恶意引导安装“中间人”的WEB信任证书,那么HTTPS中的“中间人攻击”一样会将明文密码泄露给别人。

结论是:无论HTTP还是HTTPS,密码必须密文传输。

四、采用什么方式才能保障安全

4.1 Base64转码可以吗?

有同学可能会考虑,现在登录名、密码是明文不安全,那我将用户名、密码机进行Base64转码别人不就看不懂了吗?这个同学有点天真有点可爱,因为Base64完全没有安全性可言,你能用Base64转码,别人就能用Base64解码,而且不用写代码,百度一下Base64在线解码就可以了,所以这种方案不可行。

4.2 对称加密可以吗?

答案仍然是:不行。稍微有点技术能力的人,只要利用相关工具或手段,就能看到你前端所采用的对称加密算法和密钥,分分钟破解,所以这种方式仍然不够安全。

4.3 MD5可以吗?

有同学考虑用MD5方案的原因大概是认为MD5是不可逆的,所以转码后,即使“黑客”拿到了密文那也是是非常难破解的(前提是密码不是常用密码,如:admin、123456这种,不然就有可能被暴力破解或者密码库进行破解)。问题是,你前端MD5转码后传到后端,后端无法进行解码,所以也不认识你传的是个什么鬼,不能进行下一步的相关逻辑处理,所以这种方案仍然不可行。

五、非对称性加密可以很好地解决这个问题

对于还不了解什么是非对称性加密的同学,可以先看看非对称性加密的基础知识点。

5.1 非对称性加密定义

1976年,美国学者Dime和Henman为解决信息公开传送和密钥管理问题,提出一种新的密钥交换协议,允许在不安全的媒体上的通讯双方交换信息,安全地达成一致的密钥,这就是“公开密钥系统”。

5.2 非对称性加密简介
  • 非对称加密算法又称现代加密算法。
  • 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解。
  • 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey) 和私有密(privatekey)
  • 公开密钥和私有密钥是一对
  • 如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密。
  • 如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。
  • 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

即:
1 、A与B要通信,A和B都要产生一对用于加密和解密的公钥和私钥
2、A的私钥保密,A的公钥告诉B;B的私钥保密,B的公钥告诉A。
3、A要给B发送信息时,A用B的公钥加密信息,因为A知道B的公钥。
4、A将这个消息发给B(已经用B的公钥加密消息)。
5、B收到这个消息后,B用自己的私钥解密A的消息。其他所有收到这个报文的人都无法解密,因为只有B才有B的私钥。

5.3 非对称加密经典算法

RSA:可用于加密和签名
DSA:仅用于签名,但速度更快

5.4 特点

算法强度复杂,安全性依赖于算法与密钥。

5.5缺点

由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。了解完非对称性加密的内容后,我们可以发现,用非对称性加密就能很好的解决Web登录用户名、密码的安全性问题。

六、采用RSA非对称性加密算法加密用户名、密码

话不多说,直接上代码

6.1 后端Java相关代码
/**
 * @author Alan Chen
 * @description
 * @date 2019-11-23
 */
public class RsaKey {

    //公钥
    private String publicKey;

    //私钥
    private String privateKey;

}
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author Alan Chen
 * @description 公钥加密-私钥解密 ; 私钥加密-公钥解密
 * @date 2019-11-23
 */
public class RsaUtil {

    public static final String CHARSET = "UTF-8";
    public static final String RSA_ALGORITHM = "RSA";


    /**
     * 创建RSA 公钥-私钥
     * @return
     */
    public static RsaKey createKeys(){
        return createKeys(1024);
    }

    /**
     * 创建RSA 公钥-私钥
     * @param keySize
     * @return
     */
    public static RsaKey createKeys(int keySize){
        //为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg = null;
        try{
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        }catch(NoSuchAlgorithmException e){
            e.printStackTrace();
        }

        //初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        //生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();

        //得到公钥
        Key publicKey = keyPair.getPublic();

        String publicKeyStr = new String(Base64.encodeBase64(publicKey.getEncoded()));

        //得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = new String(Base64.encodeBase64(privateKey.getEncoded()));

        RsaKey rsaKey = new RsaKey();
        rsaKey.setPublicKey(publicKeyStr);
        rsaKey.setPrivateKey(privateKeyStr);

        return rsaKey;
    }


    /**
     * 公钥加密
     * @param originalText 原文
     * @param publicKey
     * @return
     */
    public static String publicEncrypt(String originalText, String publicKey){
        RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
        return publicEncrypt(originalText,rsaPublicKey);
    }

    /**
     * 公钥解密
     * @param cipherText
     * @param publicKey
     * @return
     */
    public static String publicDecrypt(String cipherText, String publicKey){
        RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
        return publicDecrypt(cipherText,rsaPublicKey);
    }

    /**
     * 私钥加密
     * @param originalText
     * @param privateKey
     * @return
     */
    public static String privateEncrypt(String originalText, String privateKey){
        RSAPrivateKey rsaPrivateKey=  getPrivateKey(privateKey);
        return privateEncrypt(originalText,rsaPrivateKey);
    }


    /**
     * 私钥解密
     * @param cipherText 密文
     * @param privateKey
     * @return
     */
    public static String privateDecrypt(String cipherText, String privateKey){
        RSAPrivateKey rsaPrivateKey=  getPrivateKey(privateKey);
        return privateDecrypt(cipherText,rsaPrivateKey);
    }


    /**
     * 得到公钥
     * @param publicKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    private static RSAPublicKey getPublicKey(String publicKey) {
        //通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
        RSAPublicKey key = null;
        try {
            key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return key;
    }

    /**
     * 公钥加密
     * @param originalText
     * @param publicKey
     * @return
     */
    private static String publicEncrypt(String originalText, RSAPublicKey publicKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, originalText.getBytes(CHARSET), publicKey.getModulus().bitLength()));
        }catch(Exception e){
            throw new RuntimeException("加密字符串[" + originalText + "]时遇到异常", e);
        }
    }

    /**
     * 得到私钥
     * @param privateKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    private static RSAPrivateKey getPrivateKey(String privateKey){
        //通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
        RSAPrivateKey key = null;
        try {
            key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return key;
    }


    /**
     * 私钥解密
     * @param cipherText
     * @param privateKey
     * @return
     */

    private static String privateDecrypt(String cipherText, RSAPrivateKey privateKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), privateKey.getModulus().bitLength()), CHARSET);
        }catch(Exception e){
            throw new RuntimeException("解密字符串[" + cipherText + "]时遇到异常", e);
        }
    }

    private static String privateEncrypt(String originalText, RSAPrivateKey privateKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, originalText.getBytes(CHARSET), privateKey.getModulus().bitLength()));
        }catch(Exception e){
            throw new RuntimeException("加密字符串[" + originalText + "]时遇到异常", e);
        }
    }


    private static String publicDecrypt(String cipherText, RSAPublicKey publicKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), publicKey.getModulus().bitLength()), CHARSET);
        }catch(Exception e){
            throw new RuntimeException("解密字符串[" + cipherText + "]时遇到异常", e);
        }
    }

    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize){
        int maxBlock = 0;
        if(opmode == Cipher.DECRYPT_MODE){
            maxBlock = keySize / 8;
        }else{
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try{
            while(datas.length > offSet){
                if(datas.length-offSet > maxBlock){
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                }else{
                    buff = cipher.doFinal(datas, offSet, datas.length-offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        }catch(Exception e){
            throw new RuntimeException("加解密阀值为["+maxBlock+"]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        IOUtils.closeQuietly(out);
        return resultDatas;
    }

}
/**
 * @author Alan Chen
 * @description
 * @date 2019-11-23
 */
public class CreateRsaKey {

    public static void main (String[] args){

        RsaKey rsaKey = RsaUtil.createKeys();

        System.out.println("公钥" );
        System.out.println(rsaKey.getPublicKey());

        System.out.println("私钥");
        System.out.println(rsaKey.getPrivateKey());

        String str = "alanchen";
        System.out.println("明文:" + str);

        System.out.println("公钥加密——私钥解密");

        //公钥加密
        String encodedData = RsaUtil.publicEncrypt(str,rsaKey.getPublicKey());

        System.out.println("公钥加密后密文:" + encodedData);

        //私钥解密
        String decodedData = RsaUtil.privateDecrypt(encodedData, rsaKey.getPrivateKey());

        System.out.println("私钥解密后文字: " + decodedData);

    }
}

在你之前的授权代码中将前端传过来的密文用你的私钥进行解密,代码大概是这样

 username = RsaUtil.privateDecrypt(username,"你的私钥");
 password = RsaUtil.privateDecrypt(password,"你的私钥");
后台解密用户名、密码
6.2 前端React相关代码

导入jsencrypt库

import JSEncrypt from 'jsencrypt';

配置公钥

//后端生成RSA公钥-私钥对后给出公钥
const loginRsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYEUuQvo6GHyAid3lVlAG/CIoj9OwljFaNHP5OjBwXxHvucRxVVhgrO2UarRGmLWGH145IkWgMVRnT4/n4UwfbnxHPdrGn0kc7OlpuCQlUlccRByINSB9jcX5QYemswrqHxr+jyMiqrjtioftfjksy2uNhYNBMi82hjgo6o7G/swIDAQAB"

对用户名、密码用公钥进行加密

/**
 * Spring Security授权
 * @param {*} params
 */
export async function authority(params) {
    const { username, password } = params;

    var jsencrypt = new JSEncrypt();
    jsencrypt.setPublicKey(loginRsaPublicKey);

    var cipherPassword = jsencrypt.encrypt(password);
    cipherPassword = cipherPassword.replace(/\+/g,'%2B'); //对+号进行转义字符,不然传输到后端会变成空格

    var cipherUsername = jsencrypt.encrypt(username);
    cipherUsername = cipherUsername.replace(/\+/g,'%2B'); //对+号进行转义字符,不然传输到后端会变成空格

    return request(`${baseServerURL}/login?username=${cipherUsername}&&password=${cipherPassword}`, {
        method: 'POST'
    });
}

前端代码大概是这样


前端加密代码

参考资料/推荐阅读

Web登录其实没那么简单

react中使用一些加密库进行RSA、md5、base64加密

使用jsencrypt(rsa加密方式)给js加密防被刷

你可能感兴趣的:(用非对称性加密保障Web登录安全)