使用RSA算法實(shí)現(xiàn)表單數(shù)據(jù)加密——前端js后端Java

前言

有時(shí)候我們會(huì)遇到這樣一個(gè)需求,提交表單的時(shí)候?qū)Ρ韱沃械奶囟〝?shù)據(jù)進(jìn)行加密后傳到后臺(tái),再由后臺(tái)進(jìn)行解密酥馍。本文會(huì)針對(duì)該類需求的實(shí)現(xiàn)思路進(jìn)行講解,希望能夠讓各位讀者有所收獲阅酪。

說在前面

我們知道加解密算法可以分為對(duì)稱加密非對(duì)稱加密旨袒。對(duì)于這個(gè)需求而言,我們不可以采用對(duì)稱加密术辐,原因是前后端使用的秘鑰為同一個(gè)砚尽,如果被中間人攔截到用戶的請(qǐng)求,那么他可以根據(jù)暴露在前端的秘鑰輕而易舉地解密出用戶的敏感信息辉词。
非對(duì)稱加密可以較好的解決這個(gè)問題必孤,把公鑰放在前端頁(yè)面上,私鑰放在后臺(tái)中用于校驗(yàn)较屿,我們本篇文章將選擇RSA算法來進(jìn)行講解隧魄。

開發(fā)思路:

前端(Js):
  1. 引入加密的js函數(shù)庫(kù)
  2. 定義加密方法后添加到表單提交前置方法onsubmit
后端(Java):
  1. 初始化工具類獲取公鑰和私鑰
  2. 私鑰放在配置文件或者靜態(tài)常量中,公鑰放在前端js方法中
  3. 定義解密方法隘蝎,對(duì)請(qǐng)求參數(shù)進(jìn)行解密

具體代碼如下:

前端JS
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/login" method="post" onsubmit="toEncrypt()">
    用戶名<input type="text" id="u_name" name="username">
    密碼<input type="password" id="pwd" name="password">
    <input type="submit" value="提交">
</form>
</form>
<script src="${pageContext.request.contextPath}/js/jsencrypt.min.js"></script>
<script type="text/javascript">
        function toEncrypt(value) {
            var encrypt = new JSEncrypt();
            var password = document.getElementById('pwd');
            encrypt.setPublicKey('java生成的公鑰');
            password.value = encrypt.encrypt(password.value);
            return true;
        }
</script>
</body>
</html>

表單提交有一個(gè)前置方法onsubmit,常用于做表單校驗(yàn)襟企,只有當(dāng)返回值為true的時(shí)候嘱么,表單的數(shù)據(jù)才會(huì)提交到后臺(tái)。這里的話顽悼,我們?cè)诩用芡陻?shù)據(jù)后曼振,直接返回true就行。
注:文檔中引用的函數(shù)庫(kù)大家可以自行在GitHub上面下載: jsencrypt 項(xiàng)目

后端java代碼:
  • 加解密工具類
    這里要注意蔚龙,工具類依賴了依賴 commons-codec
import org.apache.commons.codec.binary.Base64;

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

/**
 * Created by lake on 17-4-12.
 */
public class RSACoder {
    public static final String KEY_ALGORITHM = "RSA";
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    private static final String PUBLIC_KEY = "RSAPublicKey";
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    public static byte[] decryptBASE64(String key) {
        return Base64.decodeBase64(key);
    }

    public static String encryptBASE64(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    /**
     * 用私鑰對(duì)信息生成數(shù)字簽名
     *
     * @param data       加密數(shù)據(jù)
     * @param privateKey 私鑰
     * @return
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        // 解密由base64編碼的私鑰
        byte[] keyBytes = decryptBASE64(privateKey);
        // 構(gòu)造PKCS8EncodedKeySpec對(duì)象
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 取私鑰匙對(duì)象
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 用私鑰對(duì)信息生成數(shù)字簽名
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(priKey);
        signature.update(data);
        return encryptBASE64(signature.sign());
    }

    /**
     * 校驗(yàn)數(shù)字簽名
     *
     * @param data      加密數(shù)據(jù)
     * @param publicKey 公鑰
     * @param sign      數(shù)字簽名
     * @return 校驗(yàn)成功返回true 失敗返回false
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKey, String sign)
            throws Exception {
        // 解密由base64編碼的公鑰
        byte[] keyBytes = decryptBASE64(publicKey);
        // 構(gòu)造X509EncodedKeySpec對(duì)象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 取公鑰匙對(duì)象
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(pubKey);
        signature.update(data);
        // 驗(yàn)證簽名是否正常
        return signature.verify(decryptBASE64(sign));
    }

    public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception{
        // 對(duì)密鑰解密
        byte[] keyBytes = decryptBASE64(key);
        // 取得私鑰
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 對(duì)數(shù)據(jù)解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 解密<br>
     * 用私鑰解密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(String data, String key)
            throws Exception {
        return decryptByPrivateKey(decryptBASE64(data),key);
    }

    /**
     * 解密<br>
     * 用公鑰解密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, String key)
            throws Exception {
        // 對(duì)密鑰解密
        byte[] keyBytes = decryptBASE64(key);
        // 取得公鑰
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicKey = keyFactory.generatePublic(x509KeySpec);
        // 對(duì)數(shù)據(jù)解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 加密<br>
     * 用公鑰加密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(String data, String key)
            throws Exception {
        // 對(duì)公鑰解密
        byte[] keyBytes = decryptBASE64(key);
        // 取得公鑰
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicKey = keyFactory.generatePublic(x509KeySpec);
        // 對(duì)數(shù)據(jù)加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data.getBytes());
    }

    /**
     * 加密<br>
     * 用私鑰加密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String key)
            throws Exception {
        // 對(duì)密鑰解密
        byte[] keyBytes = decryptBASE64(key);
        // 取得私鑰
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 對(duì)數(shù)據(jù)加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 取得私鑰
     *
     * @param keyMap
     * @return
     * @throws Exception
     */
    public static String getPrivateKey(Map<String, Key> keyMap)
            throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return encryptBASE64(key.getEncoded());
    }

    /**
     * 取得公鑰
     *
     * @param keyMap
     * @return
     * @throws Exception
     */
    public static String getPublicKey(Map<String, Key> keyMap)
            throws Exception {
        Key key = keyMap.get(PUBLIC_KEY);
        return encryptBASE64(key.getEncoded());
    }

    /**
     * 初始化密鑰
     *
     * @return
     * @throws Exception
     */
    public static Map<String, Key> initKey() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator
                .getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        Map<String, Key> keyMap = new HashMap(2);
        keyMap.put(PUBLIC_KEY, keyPair.getPublic());// 公鑰
        keyMap.put(PRIVATE_KEY, keyPair.getPrivate());// 私鑰
        return keyMap;
    }
}

通過工具類生成公鑰和私鑰 冰评, 分別保存到前端和后端

/**
     * 初始化密鑰,我們可以從initKey方法中直接獲取公鑰和私鑰
     *  這里要注意的是木羹,每次生成的秘鑰對(duì)都是不一樣的甲雅,我們選一個(gè)進(jìn)行保存就行
     *
     * @return
     * @throws Exception
     */
    public static void initKey() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator
                .getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        System.out.println("公鑰是:" + keyPair.getPublic());
        System.out.println("私鑰是:" + keyPair.getPrivate());
    }

接收到前端的代碼后進(jìn)行解密

    public void login(HttpServletRequest request,HttpServletResponse response){
        ...  獲取前端傳進(jìn)來的密碼
       password =  RSACoder.decryptByPrivateKey("解密前的密碼"解孙,"后端的私鑰");
    }
需求外的優(yōu)化點(diǎn)

我們會(huì)發(fā)現(xiàn),由于加密動(dòng)作的存在抛人,用戶提交代碼后弛姜,可以肉眼看到原密碼會(huì)變成32位的加密后字符,這樣樣式上會(huì)不太好看妖枚。我們可以采用一個(gè)隱藏的<input>標(biāo)簽來做密碼的間接傳輸廷臼,并將name屬性賦給新的input標(biāo)簽(JS要配套著去改)。這樣我們就可以在后臺(tái)接收到轉(zhuǎn)換的參數(shù)了
前端JS改動(dòng)如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/login" method="post" onsubmit="toEncrypt()">
    用戶名<input type="text" id="u_name" name="username">
    密碼<input type="password" id="pwd" >
    <input type="hidden" name="password" id="tem_pwd" >
    <input type="submit" value="提交">
</form>
</form>
<script src="${pageContext.request.contextPath}/js/jsencrypt.min.js"></script>
<script type="text/javascript">
        function toEncrypt(value) {
            var encrypt = new JSEncrypt();
            var u_password = document.getElementById('pwd');
            var tem_password = document.getElementById('pwd');
            encrypt.setPublicKey('java生成的公鑰');
            tem_password.value = encrypt.encrypt(u_password .value);
            return true;
        }
</script>
</body>
</html>

參考文章:
http://www.reibang.com/p/ff8281f034f4 Java 與 js完美RSA非對(duì)稱加密
https://www.cnblogs.com/web-wjg/p/7894657.html js實(shí)現(xiàn)表單提交submit()绝页,onsubmit

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荠商,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子续誉,更是在濱河造成了極大的恐慌莱没,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屈芜,死亡現(xiàn)場(chǎng)離奇詭異郊愧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)井佑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門属铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躬翁,你說我怎么就攤上這事焦蘑。” “怎么了盒发?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵例嘱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我宁舰,道長(zhǎng)拼卵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任蛮艰,我火速辦了婚禮腋腮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘壤蚜。我一直安慰自己即寡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布袜刷。 她就那樣靜靜地躺著聪富,像睡著了一般。 火紅的嫁衣襯著肌膚如雪著蟹。 梳的紋絲不亂的頭發(fā)上墩蔓,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天梢莽,我揣著相機(jī)與錄音,去河邊找鬼钢拧。 笑死蟹漓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的源内。 我是一名探鬼主播葡粒,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼膜钓!你這毒婦竟也來了嗽交?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤颂斜,失蹤者是張志新(化名)和其女友劉穎夫壁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沃疮,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盒让,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了司蔬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邑茄。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俊啼,死狀恐怖肺缕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情授帕,我是刑警寧澤同木,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站跛十,受9級(jí)特大地震影響彤路,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芥映,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一斩萌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屏轰,春花似錦、人聲如沸憋飞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榛做。三九已至唁盏,卻和暖如春内狸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厘擂。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工昆淡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刽严。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓昂灵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親舞萄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眨补,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容