用非對稱性加密保障Web登錄安全

一、技術(shù)背景介紹

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

二、Web登錄安全隱患

如果你的后臺管理系統(tǒng)登錄情況和下圖的情況一樣,登錄時用戶名、密碼是明文傳輸?shù)?,那么你是時候考慮要用非對稱性加密來保障Web登錄安全了。

登錄名密碼被暴露

三、HTTPS就一定安全嗎?

HTTPS存在兩種可能的風(fēng)險:

1、HTTPS可以保證傳輸過程中的信息不被別人截獲,但是細(xì)細(xì)思考下,HTTPS是應(yīng)用層協(xié)議,下層采用SSL保證信息安全,但是在客戶端和服務(wù)端,密文同樣是可以被截獲的;

2、HTTPS報文在傳輸過程中,如果客戶端被惡意引導(dǎo)安裝“中間人”的WEB信任證書,那么HTTPS中的“中間人攻擊”一樣會將明文密碼泄露給別人。

結(jié)論是:無論HTTP還是HTTPS,密碼必須密文傳輸。

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

4.1 Base64轉(zhuǎn)碼可以嗎?

有同學(xué)可能會考慮,現(xiàn)在登錄名、密碼是明文不安全,那我將用戶名、密碼機(jī)進(jìn)行Base64轉(zhuǎn)碼別人不就看不懂了嗎?這個同學(xué)有點天真有點可愛,因為Base64完全沒有安全性可言,你能用Base64轉(zhuǎn)碼,別人就能用Base64解碼,而且不用寫代碼,百度一下Base64在線解碼就可以了,所以這種方案不可行。

4.2 對稱加密可以嗎?

答案仍然是:不行。稍微有點技術(shù)能力的人,只要利用相關(guān)工具或手段,就能看到你前端所采用的對稱加密算法和密鑰,分分鐘破解,所以這種方式仍然不夠安全。

4.3 MD5可以嗎?

有同學(xué)考慮用MD5方案的原因大概是認(rèn)為MD5是不可逆的,所以轉(zhuǎn)碼后,即使“黑客”拿到了密文那也是是非常難破解的(前提是密碼不是常用密碼,如:admin、123456這種,不然就有可能被暴力破解或者密碼庫進(jìn)行破解)。問題是,你前端MD5轉(zhuǎn)碼后傳到后端,后端無法進(jìn)行解碼,所以也不認(rèn)識你傳的是個什么鬼,不能進(jìn)行下一步的相關(guān)邏輯處理,所以這種方案仍然不可行。

五、非對稱性加密可以很好地解決這個問題

對于還不了解什么是非對稱性加密的同學(xué),可以先看看非對稱性加密的基礎(chǔ)知識點。

5.1 非對稱性加密定義

1976年,美國學(xué)者Dime和Henman為解決信息公開傳送和密鑰管理問題,提出一種新的密鑰交換協(xié)議,允許在不安全的媒體上的通訊雙方交換信息,安全地達(dá)成一致的密鑰,這就是“公開密鑰系統(tǒng)”。

5.2 非對稱性加密簡介
  • 非對稱加密算法又稱現(xiàn)代加密算法。
  • 非對稱加密是計算機(jī)通信安全的基石,保證了加密數(shù)據(jù)不會被破解。
  • 與對稱加密算法不同,非對稱加密算法需要兩個密鑰:公開密鑰(publickey) 和私有密(privatekey)
  • 公開密鑰和私有密鑰是一對
  • 如果用公開密鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的私有密鑰才能解密。
  • 如果用私有密鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的公開密鑰才能解密。
  • 因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。

即:
1 、A與B要通信,A和B都要產(chǎn)生一對用于加密和解密的公鑰和私鑰
2、A的私鑰保密,A的公鑰告訴B;B的私鑰保密,B的公鑰告訴A。
3、A要給B發(fā)送信息時,A用B的公鑰加密信息,因為A知道B的公鑰。
4、A將這個消息發(fā)給B(已經(jīng)用B的公鑰加密消息)。
5、B收到這個消息后,B用自己的私鑰解密A的消息。其他所有收到這個報文的人都無法解密,因為只有B才有B的私鑰。

5.3 非對稱加密經(jīng)典算法

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

5.4 特點

算法強(qiáng)度復(fù)雜,安全性依賴于算法與密鑰。

5.5缺點

由于其算法復(fù)雜,而使得加密解密速度沒有對稱加密解密的速度快。了解完非對稱性加密的內(nèi)容后,我們可以發(fā)現(xiàn),用非對稱性加密就能很好的解決Web登錄用戶名、密碼的安全性問題。

六、采用RSA非對稱性加密算法加密用戶名、密碼

話不多說,直接上代碼

6.1 后端Java相關(guān)代碼
/**
 * @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";


    /**
     * 創(chuàng)建RSA 公鑰-私鑰
     * @return
     */
    public static RsaKey createKeys(){
        return createKeys(1024);
    }

    /**
     * 創(chuàng)建RSA 公鑰-私鑰
     * @param keySize
     * @return
     */
    public static RsaKey createKeys(int keySize){
        //為RSA算法創(chuàng)建一個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 密鑰字符串(經(jīng)過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 密鑰字符串(經(jīng)過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+"]的數(shù)據(jù)時發(fā)生異常", 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);

    }
}

在你之前的授權(quán)代碼中將前端傳過來的密文用你的私鑰進(jìn)行解密,代碼大概是這樣

 username = RsaUtil.privateDecrypt(username,"你的私鑰");
 password = RsaUtil.privateDecrypt(password,"你的私鑰");
后臺解密用戶名、密碼
6.2 前端React相關(guān)代碼

導(dǎo)入jsencrypt庫

import JSEncrypt from 'jsencrypt';

配置公鑰

//后端生成RSA公鑰-私鑰對后給出公鑰
const loginRsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYEUuQvo6GHyAid3lVlAG/CIoj9OwljFaNHP5OjBwXxHvucRxVVhgrO2UarRGmLWGH145IkWgMVRnT4/n4UwfbnxHPdrGn0kc7OlpuCQlUlccRByINSB9jcX5QYemswrqHxr+jyMiqrjtioftfjksy2uNhYNBMi82hjgo6o7G/swIDAQAB"

對用戶名、密碼用公鑰進(jìn)行加密

/**
 * Spring Security授權(quán)
 * @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'); //對+號進(jìn)行轉(zhuǎn)義字符,不然傳輸?shù)胶蠖藭兂煽崭?
    var cipherUsername = jsencrypt.encrypt(username);
    cipherUsername = cipherUsername.replace(/\+/g,'%2B'); //對+號進(jìn)行轉(zhuǎn)義字符,不然傳輸?shù)胶蠖藭兂煽崭?
    return request(`${baseServerURL}/login?username=${cipherUsername}&&password=${cipherPassword}`, {
        method: 'POST'
    });
}

前端代碼大概是這樣


前端加密代碼

參考資料/推薦閱讀

Web登錄其實沒那么簡單

react中使用一些加密庫進(jìn)行RSA、md5、base64加密

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容