1. 程式人生 > >RSA加密演算法的簡單案例

RSA加密演算法的簡單案例

RSA加密演算法是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前為止已知的絕大多數密碼攻擊。

那關於RSA加密演算法有哪些應用呢?以下舉一個數據庫身份驗證的案例。

在使用資料集進行身份認證時,密碼存在資料庫中,認證時使用者輸入的密碼與資料庫中密碼相同則認證通過,若資料庫被破解了則對系統造成威脅,怎樣保證系統安全呢?這裡就可以應用RSA加密演算法,對許可權加密。

思路:

就是在url中傳使用者名稱密碼時,先把使用者名稱進行翻轉,然後再進行加密,如輸入的密碼為12,實際後臺進行加密的值為21,再與資料庫進行驗證,這樣就可以避免資料庫被破解檢視到的是21的加密碼,登陸系統時以21是無法登陸成功的。

以報表軟體FineReport為例,這是一個能讀取各類資料庫的報表軟體,分客戶端和前端展示。

實現方案:

1、把RSA加密使用的第三方包,放到工程web-inf/lib資料夾下即可。

2、呼叫js檔案

RSA資料夾為前端js加密時需要呼叫js檔案,因此需要將Barrett.js、BigInt.js、RSA.js放到工程目錄下如:WebReport/js,新建js資料夾放入js檔案。

3、定義RSA加密類

定義RSAUtil.java類檔案,先執行類中generateKeyPair()方法,會在伺服器D盤中生成一個隨機的RSAKey.txt檔案,儲存公鑰和金鑰,每訪問一次這個方法會重新整理一次txt檔案。

package com.fr.privilege;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;

/**
 * RSA 工具類。提供加密,解密,生成金鑰對等方法。
 * 需要到http://www.bouncycastle.org下載bcprov-jdk14-123.jar。
 * 
 */
public class RSAUtil {
	/**
	 * * 生成金鑰對 *
	 * 
	 * @return KeyPair *
	 * @throws EncryptException
	 */
	public static KeyPair generateKeyPair() throws Exception {
		try {
			KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
			final int KEY_SIZE = 1024;// 沒什麼好說的了,這個值關係到塊加密的大小,可以更改,但是不要太大,否則效率會低
			keyPairGen.initialize(KEY_SIZE, new SecureRandom());
			KeyPair keyPair = keyPairGen.generateKeyPair();
			saveKeyPair(keyPair);
			return keyPair;
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	public static KeyPair getKeyPair() throws Exception {
		FileInputStream fis = new FileInputStream("C:/RSAKey.txt");
		ObjectInputStream oos = new ObjectInputStream(fis);
		KeyPair kp = (KeyPair) oos.readObject();
		oos.close();
		fis.close();
		return kp;
	}

	public static void saveKeyPair(KeyPair kp) throws Exception {

		FileOutputStream fos = new FileOutputStream("C:/RSAKey.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		// 生成金鑰
		oos.writeObject(kp);
		oos.close();
		fos.close();
	}

	/**
	 * * 生成公鑰 *
	 * 
	 * @param modulus *
	 * @param publicExponent *
	 * @return RSAPublicKey *
	 * @throws Exception
	 */
	public static RSAPublicKey generateRSAPublicKey(byte[] modulus,
			byte[] publicExponent) throws Exception {
		KeyFactory keyFac = null;
		try {
			keyFac = KeyFactory.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} catch (NoSuchAlgorithmException ex) {
			throw new Exception(ex.getMessage());
		}

		RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
				modulus), new BigInteger(publicExponent));
		try {
			return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
		} catch (InvalidKeySpecException ex) {
			throw new Exception(ex.getMessage());
		}
	}

	/**
	 * * 生成私鑰 *
	 * 
	 * @param modulus *
	 * @param privateExponent *
	 * @return RSAPrivateKey *
	 * @throws Exception
	 */
	public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus,
			byte[] privateExponent) throws Exception {
		KeyFactory keyFac = null;
		try {
			keyFac = KeyFactory.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} catch (NoSuchAlgorithmException ex) {
			throw new Exception(ex.getMessage());
		}

		RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(
				modulus), new BigInteger(privateExponent));
		try {
			return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
		} catch (InvalidKeySpecException ex) {
			throw new Exception(ex.getMessage());
		}
	}

	/**
	 * * 加密 *
	 * 
	 * @param key
	 *            加密的金鑰 *
	 * @param data
	 *            待加密的明文資料 *
	 * @return 加密後的資料 *
	 * @throws Exception
	 */
	public static byte[] encrypt(PublicKey pk, byte[] data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(Cipher.ENCRYPT_MODE, pk);
			int blockSize = cipher.getBlockSize();// 獲得加密塊大小,如:加密前資料為128個byte,而key_size=1024
			// 加密塊大小為127
			// byte,加密後為128個byte;因此共有2個加密塊,第一個127
			// byte第二個為1個byte
			int outputSize = cipher.getOutputSize(data.length);// 獲得加密塊加密後塊大小
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
					: data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0;
			while (data.length - i * blockSize > 0) {
				if (data.length - i * blockSize > blockSize)
					cipher.doFinal(data, i * blockSize, blockSize, raw, i
							* outputSize);
				else
					cipher.doFinal(data, i * blockSize, data.length - i
							* blockSize, raw, i * outputSize);
				// 這裡面doUpdate方法不可用,檢視原始碼後發現每次doUpdate後並沒有什麼實際動作除了把byte[]放到
				// ByteArrayOutputStream中,而最後doFinal的時候才將所有的byte[]進行加密,可是到了此時加密塊大小很可能已經超出了
				// OutputSize所以只好用dofinal方法。

				i++;
			}
			return raw;
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	/**
	 * * 解密 *
	 * 
	 * @param key
	 *            解密的金鑰 *
	 * @param raw
	 *            已經加密的資料 *
	 * @return 解密後的明文 *
	 * @throws Exception
	 */
	public static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("RSA",
					new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(cipher.DECRYPT_MODE, pk);
			int blockSize = cipher.getBlockSize();
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;

			while (raw.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	/**
	 * * *
	 * 
	 * @param args *
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		RSAPublicKey rsap = (RSAPublicKey) RSAUtil.generateKeyPair()
				.getPublic();
		String test = "hello world";
		byte[] en_test = encrypt(getKeyPair().getPublic(), test.getBytes());
		System.out.println("123:" + new String(en_test));
		byte[] de_test = decrypt(getKeyPair().getPrivate(), en_test);
		System.out.println(new String(de_test));
	}
}

4、定義密碼驗證類

定義TestPasswordValidatorRSA.java密碼驗證類

定義一個類,命名為TestPasswordValidatorRSA.java,擴充套件於AbstractPasswordValidator,重寫其中密碼驗證方法encodePassword,先把輸入的密碼進行翻轉,然後再進行加密,返回密碼進行驗證,具體程式碼如下:

package com.fr.privilege;  

import com.fr.privilege.providers.dao.AbstractPasswordValidator;  
public class TestPasswordValidatorRSA extends AbstractPasswordValidator{  
    //@Override
    public String encodePassword( String clinetPassword) {
    	try {
    		//對密碼進行翻轉如輸入ab翻轉後為ba
    		StringBuffer sb = new StringBuffer();  
	        sb.append(new String(clinetPassword));
	        String bb = sb.reverse().toString();
    		//進行加密
    		byte[] en_test = RSAUtil.encrypt(RSAUtil.getKeyPair().getPublic(),bb.getBytes());     		
    		//進行解密,如果資料庫裡面儲存的是加密碼,則此處不需要進行解密
    		byte[] de_test = RSAUtil.decrypt(RSAUtil.getKeyPair().getPrivate(),en_test);  
    		//返回加密密碼
    		clinetPassword=new String(de_test);		
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return clinetPassword; //即獲取加密密碼再與資料庫密碼匹配。  
    }

	@Override
	public boolean validatePassword(String arg0, String arg1) {
		// TODO Auto-generated method stub
		return false;
	}


}

5、編譯類檔案

首先編譯RSAUtil.java類檔案在伺服器的D盤生成RSAKey.txt檔案,再編譯TestPasswordValidatorRSA.java類,把編譯後的class檔案放到專案工程web-inf/classes/com/fr/privilege資料夾中。

6、登陸Login.jsp頁面設定

客戶端請求到登入頁面,隨機生成一字串,此隨機字串作為金鑰加密密碼,如下程式碼:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="com.fr.privilege.providers.dao.RSAUtil"%>
<%!public String Testmo() {
		String module = "";
		try {
			java.security.interfaces.RSAPublicKey rsap = (java.security.interfaces.RSAPublicKey) RSAUtil
					.getKeyPair().getPublic();
			module = rsap.getModulus().toString(16);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return module;
	}%>
<%!public String Testem() {
		String empoent = "";
		try {
			java.security.interfaces.RSAPublicKey rsap = (java.security.interfaces.RSAPublicKey) RSAUtil
					.getKeyPair().getPublic();
			empoent = rsap.getPublicExponent().toString(16);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return empoent;
	}%>
<html>
	<head>
		<script type="text/javascript"
			src="ReportServer?op=emb&resource=finereport.js"></script>
		<script type="text/javascript" src="js/RSA.js"></script>
		<script type="text/javascript" src="js/BigInt.js"></script>
		<script type="text/javascript" src="js/Barrett.js"></script>
		<script type="text/javascript">    
	function bodyRSA()  
	{  
    setMaxDigits(130);  
    var a = "<%=Testmo()%>";
	var b = "<%=Testem()%>";
	key = new RSAKeyPair(b,"",a);
	} 
function doSubmit() {   
	bodyRSA(); 
    var username = FR.cjkEncode(document.getElementById("username").value); //獲取輸入的使用者名稱    
    var password = FR.cjkEncode(document.getElementById("password").value);  //獲取輸入的引數    
    $.ajax({    
        url : "ReportServer?op=auth_login&fr_username=" + username + "&fr_password=" + password,   //將使用者名稱和密碼傳送到報表認證地址op=auth_login   
        data : {__redirect__ : 'false'},        
        complete : function(res) {    
            var jo = FR.jsonDecode(res.responseText);    
            if(jo.url) {    
               window.location=jo.url+ "&_=" + new Date().getTime();   //認證成功跳轉頁面,因為ajax不支援重定向所有需要跳轉的設定  
            }    
            else{    
               alert("使用者名稱密碼錯誤!")  //認證失敗    
            }    
        }    
    })    
}    
</script>
	</head>
	<body>
		<p>
			請登入
		</p>
		<form name="login" method="POST">
			<p>
				使用者名稱:
				<input id="username" type="text" />
			</p>
			<p>
				密 碼:
				<input id="password" type="password" />
			</p>
			<input type="button" value="登入" onclick="doSubmit()"  />
		</form>
	</body>
</html>