RSA在前端加密後端解密的應用

問題來源

本人要搭出一個系統的原型,需要賬戶登錄功能,那最實用的密碼加密算法肯定是RSA了,鑑於密鑰使用的特殊性,所以在前端加密後端解密是最好的選擇了。本文有兩個可行的思路,主要詳細寫第一個(以JFinal框架爲例)。

思路

爲了避免密鑰對傳到前端被截獲,所以密鑰要在後端生成,並存於session裏,然後把公鑰傳給前端供前端JS加密,前端ajax只傳回賬號和公鑰加密密碼,後端得到公鑰加密密碼後取得session中的私鑰進行解密(一般來說密碼最好再用對稱加密處理一下再存進數據庫裏更妥,例如MD5)
  • 在一個會話期間只生成一次密鑰對:這就需要使用HttpSessionListener來對會話的創建和銷燬進行密鑰對生成(記得在web.xml裏註冊監聽器)
/**
 * 會話監聽器
 * @author Wilson 2016/04/02
 *
 */
public class SessionListener implements  HttpSessionListener{
	private static final Logger logger = Logger.getLogger(SessionListener.class);
	    
    @Override
    public void sessionCreated(HttpSessionEvent hse) {
        try {
            Map<String,Object> keyPair = CryptoKit.initKey();
            hse.getSession().setAttribute("rsa_public_key", CryptoKit.getPublicKey(keyPair));
            hse.getSession().setAttribute("rsa_private_key", CryptoKit.getPrivateKey(keyPair));
        } catch (Exception e) {
            logger.error(e);
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent hse) {
        
    }
}
  • 在功能裏創建密鑰對,每次調用頁面都重新生成一對,同樣放在session裏(記得判斷和清除上一次的密鑰對),可能比上一個方法更爲保密,但確實不方便,見仁見智把
/**
 * 登陸控制器
 * @author Wilson 2017/04/03
 *
 */
public class LoginController extends Controller{
	public void index() throws Exception {
		HttpSession session = getSession();
		
		session.removeAttribute("rsa_public_key");
		session.removeAttribute("rsa_private_key");
		
		Map<String,Object> keyPair = CryptoKit.initKey();
		String pubKey = CryptoKit.getPublicKey(keyPair);
        session.setAttribute("rsa_public_key", pubKey);
        session.setAttribute("rsa_private_key", CryptoKit.getPrivateKey(keyPair));
		setAttr("rsa_public_key",pubKey);
		renderFreeMarker("Login.html");
	}
}

步驟

  • 先設計好加密工具類(這裏借用了網上的代碼,並改用common.codec來處理base64)
/**
 * 密文工具
 * @author Wilson 2017/04/01
 *
 */
public class CryptoKit {
	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";  
    private static final int MAX_ENCRYPT_BLOCK = 117; //RSA最大加密明文大小
    private static final int MAX_DECRYPT_BLOCK = 128; //RSA最大解密密文大小
    
    public static final String KEY_SHA = "SHA";  
    public static final String KEY_MD5 = "MD5";
	
	private CryptoKit(){}
	
    public static void main(String[] args) throws Exception{
    	Map<String, Object> keyPair =  initKey();
    	String privateKey = getPrivateKey(keyPair);
    	String publicKey = getPublicKey(keyPair);

    	System.err.println("公鑰加密——私鑰解密"); 
		String source = "這是一行沒有任何意義的文字,你看完了等於沒看,不是嗎?"; 
		System.out.println("\r加密前文字:\r\n" + source); 
		byte[] data = source.getBytes(); 
		byte[] encodedData = encryptByPublicKey(data, publicKey);
		byte[] encodedData2 = encryptByPublicKey(data, publicKey);
		System.out.println("加密後文字:\r\n" + new String(encodedData));
		System.out.println("加密後文字:\r\n" + new String(encodedData2));
		byte[] decodedData = decryptByPrivateKey(encodedData, privateKey); 
		String target = new String(decodedData); 
		System.out.println("解密後文字: \r\n" + target);
		
		
		System.err.println("私鑰加密——公鑰解密"); 
		byte[] encodedDa = encryptByPrivateKey(data, privateKey); 
		System.out.println("加密後:\r\n" + new String(encodedDa)); 
		byte[] decodedDa = decryptByPublicKey(encodedDa, publicKey); 
		String tar = new String(decodedDa); 
		System.out.println("解密後: \r\n" + tar); 
		
		
		System.err.println("私鑰簽名——公鑰驗證簽名"); 
		String sign = sign(encodedData, privateKey); 
		System.err.println("簽名:\r" + sign); 
		boolean status = verify(encodedData, publicKey, sign); 
		System.err.println("驗證結果:\r" + status);  
    }
    
    /** 
     * BASE64解密 
     *  
     * @param key 
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptBASE64(String key) {  
        return Base64.decodeBase64(key);
    }  
   
    /** 
     * BASE64加密 
     *  
     * @param key 
     * @return 
     * @throws Exception 
     */  
    public static String encryptBASE64(byte[] key) {  
        return Base64.encodeBase64String(key); 
    }  
   
    /** 
     * MD5加密 (信息摘要不可逆)
     *  
     * @param data 
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptMD5(byte[] data) throws Exception {  
        MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);  
        md5.update(data);  
        return md5.digest();  
    }  
   
    /** 
     * SHA加密
     *  
     * @param data 
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptSHA(byte[] data) throws Exception {  
        MessageDigest sha = MessageDigest.getInstance(KEY_SHA);  
        sha.update(data);  
   
        return sha.digest();
    }  
    
    /** 
     * 用私鑰對信息生成數字簽名 
     *  
     * @param data 加密數據 
     * @param privateKey 私鑰 
     * @return 
     * @throws Exception 
     */  
    public static String sign(byte[] data, String privateKey) throws Exception {  
        // 解密由base64編碼的私鑰  
        byte[] keyBytes = decryptBASE64(privateKey);  
   
        // 構造PKCS8EncodedKeySpec對象  
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);  
   
        // KEY_ALGORITHM 指定的加密算法  
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
   
        // 取私鑰匙對象  
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);  
   
        // 用私鑰對信息生成數字簽名  
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);  
        signature.initSign(priKey);  
        signature.update(data);  
   
        return encryptBASE64(signature.sign());  
    }  
   
    /** 
     * 校驗數字簽名 
     *  
     * @param data 加密數據 
     * @param publicKey 公鑰 
     * @param sign 數字簽名
     * @return 校驗成功返回true 失敗返回false 
     * @throws Exception 
     */  
    public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {  
   
        // 解密由base64編碼的公鑰  
        byte[] keyBytes = decryptBASE64(publicKey);  
   
        // 構造X509EncodedKeySpec對象  
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);  
   
        // KEY_ALGORITHM 指定的加密算法  
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
   
        // 取公鑰匙對象  
        PublicKey pubKey = keyFactory.generatePublic(keySpec);  
   
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);  
        signature.initVerify(pubKey);  
        signature.update(data);  
   
        // 驗證簽名是否正常  
        return signature.verify(decryptBASE64(sign));  
    }  
   
    /** 
     * 用私鑰解密 
     * @param data 
     * @param key (BASE64) 
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception {      
        // 取得私鑰  
    	byte[] keyBytes = decryptBASE64(key);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
        Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); 
        
    	// 對數據解密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.DECRYPT_MODE, privateKey);  
        
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對數據分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
            else cache = cipher.doFinal(data, offSet, inputLen - offSet);
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }  
   
    /** 
     * 用公鑰解密 
     * @param data 
     * @param key (BASE64)
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception {  
        // 取得公鑰  
    	byte[] keyBytes = decryptBASE64(key);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
        Key publicKey = keyFactory.generatePublic(x509KeySpec);  
   
        // 對數據解密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.DECRYPT_MODE, publicKey);  
        
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對數據分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }  
   
    /** 
     * 用公鑰加密 
     *  
     * @param data 
     * @param key (BASE64)
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception {  
        // 對公鑰解密  
        byte[] keyBytes = decryptBASE64(key);  
   
        // 取得公鑰  
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
        Key publicKey = keyFactory.generatePublic(x509KeySpec);  
   
        // 對數據加密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對數據分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            else cache = cipher.doFinal(data, offSet, inputLen - offSet);
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }  
   
    /** 
     * 用私鑰加密 
     *  
     * @param data 
     * @param key (BASE64)
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception {  
        // 對密鑰解密  
        byte[] keyBytes = decryptBASE64(key);  
   
        // 取得私鑰  
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);  
        Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);  
   
        // 對數據加密  
        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, Object> keyMap) throws Exception {  
    	RSAPrivateKey key = (RSAPrivateKey) keyMap.get(PRIVATE_KEY);
        return encryptBASE64(key.getEncoded());  
    }  
   
    /** 
     * 取得公鑰 
     *  
     * @param keyMap 
     * @return 
     * @throws Exception 
     */  
    public static String getPublicKey(Map<String, Object> keyMap) throws Exception {  
    	RSAPublicKey key = (RSAPublicKey) keyMap.get(PUBLIC_KEY);  
        return encryptBASE64(key.getEncoded());  
    }  
   
    /** 
     * 初始化密鑰對 
     *  
     * @return 
     * @throws Exception 
     */  
    public static Map<String, Object> initKey() throws Exception {  
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);  
        keyPairGen.initialize(1024);  
   
        KeyPair keyPair = keyPairGen.generateKeyPair();  
   
        // 公鑰  
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  
   
        // 私鑰  
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();  
   
        Map<String, Object> keyMap = new HashMap<String, Object>(2);  
   
        keyMap.put(PUBLIC_KEY, publicKey);  
        keyMap.put(PRIVATE_KEY, privateKey);  
        return keyMap;  
    } 
}
  • 前端JS,利用了jsencrypt.js來進行加密,通過後端的公鑰便可以進行密碼的加密(這裏是requireJS的寫法)
<script>
require([
         'jquery',
         '${base}/res/plugin/cryptico/jsencrypt.min.js'
         ], function($,jsencrypt) {
	var t = '${rsa_public_key}';
	var encrypt = new jsencrypt.JSEncrypt();
    encrypt.setPublicKey(t);
    var encrypted = encrypt.encrypt("123456");
	$.ajax({
		url: '${base}/index/login',
		data: {user:'admin',password:encrypted},
		success: function(data){
			
		}
	});
});
</script>
  • 後端java
/**
 * 登陸控制器
 * @author Wilson 2017/04/03
 *
 */
public class LoginController extends Controller{
	public void index() {
		String pubKey = getSessionAttr("rsa_public_key");
		setAttr("rsa_public_key",pubKey);
		renderFreeMarker("Login.html");
	}
	
	/**
	 * 登陸
	 * @throws Exception 
	 */
	public void login() throws Exception {
		String user = getPara("user");
		String password = getPara("password");
		String priKey = getSessionAttr("rsa_private_key");
		String text2 = new String(CryptoKit.decryptByPrivateKey(CryptoKit.decryptBASE64(password), priKey));
		//TO DO
		renderNull();
	}
}








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章