美文网首页框架建设收集
用非对称性加密保障Web登录安全

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

作者: AC编程 | 来源:发表于2019-12-05 17:13 被阅读0次

    一、技术背景介绍

    • 后端: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登录安全

        本文链接:https://www.haomeiwen.com/subject/asodgctx.html