2015年3月13日 星期五

JSP伺服器之間溝通時使用公私鑰進行加密或確認彼此身份


JSP伺服器之間溝通時使用公私鑰進行加密或確認彼此身份

產生一組公私鑰

JSP伺服器之間相互溝通時使用公私鑰加密或認證相當容易,在這個範例中,我們假設有兩台伺服器A和B,則需要建立兩組金鑰,一組給A伺服器,一組給B伺服器,每一組金鑰都包含一支公鑰和一支私鑰,A伺服器的私鑰自己保管不能外流,公鑰則複製一份給B伺服器,同樣的B伺服器的私鑰自己保管不能外流,公鑰則複製一份給A伺服器。

發話的時候使用對方的公鑰加密,收話的時候使用自已的私鑰解密

當B伺服器要對A伺服器發話進行溝通時,發話的內容在B伺服器上可全部或是重點欄位(以便加速)使用A伺服器的公鑰來加密,加密的內容從B伺服器傳送到A伺服器時,將會在A伺服器上使用A伺服器的私鑰來解密,所以任何伺服器如果沒有A伺服器的公鑰,就沒辦法對A伺服器發話,且若A伺服器上沒有A伺服器的私鑰就無法解密這些傳話內容。

同樣的道理,A伺服器須使用B伺服器的公鑰才能對B伺服器發話,B伺服器須使用B伺服器的私鑰來解密收到的通話內容,如此即可加密彼此的溝通,進一步互相驗證彼此,防止溝通的內容在網路上被竊聽。

綜合以上可以得到一個結論,就是發話的時候使用對方的公鑰加密,收話的時候使用自已的私鑰解密。

建立公私鑰的網頁範例

將以下兩支JSP程式碼各放在A和B兩個JSP伺服器的例如Module/Ca路徑下,所以如果您在Linux上使用Tomcat作為JSP應用程式伺服器,則完整的路徑例如為/usr/Tomcat/webapps/ROOT/Module/Ca。

在此範例中,我們須先在AB伺服器上均建立路徑/usr/cert,來存放產生的公私鑰,為了簡化程式碼,我們將下面的函式keyPairGen()獨立出來,以便用來產生一組公私鑰。

產生一組公私鑰的函式 /Module/Ca/keyPairGen.jsp

<%!
// 程式碼來源 : http://playjsp.blogspot.com ,作者:Steven,不限使用請註明出處。
// 提供公私鑰存放路徑、驗算法種類與公私鑰記憶體大小來產生一組公私鑰
public boolean keyPairGen(String Pub_File_Name_In, String Pri_File_Name_In, String Algorithm_In, int keySize){

try {

//產生公私鑰
java.security.KeyPairGenerator kpg = java.security.KeyPairGenerator.getInstance(Algorithm_In);
kpg.initialize(keySize);
java.security.KeyPair kp = kpg.generateKeyPair();
java.security.PublicKey pubk = kp.getPublic();
java.security.PrivateKey prik = kp.getPrivate();

//將公鑰存檔
java.io.FileOutputStream fos_pub = new java.io.FileOutputStream(Pub_File_Name_In);
fos_pub.write(pubk.getEncoded());
fos_pub.flush();
fos_pub.close();

//將私鑰存檔
java.io.FileOutputStream fos_pri = new java.io.FileOutputStream(Pri_File_Name_In);
fos_pri.write(prik.getEncoded());
fos_pri.flush();
fos_pri.close();

//} catch (NoSuchAlgorithmException exc) {
//} catch (java.io.IOException e) {
} catch (Exception e) {

out.print(e);
return false;

}
return true;

}
%>

而下面的GenkeyExample.jsp是一個JSP網頁,直接呼叫上面的方法來產生一組公私鑰。

呼叫範例網頁 /Module/Ca/GenkeyExample.jsp

<%@ page language="java" contentType="text/html" %>
<%@ include file="/Module/Ca/keyPairGen.jsp" %>
<%

// 程式碼來源 : http://playjsp.blogspot.com ,作者:Steven,不限使用請註明出處。
//檔名如果是中文,請自行處理編碼轉換
String File_Name = request.getParameter("File_Name");

//公私鑰的演算法,其他也可以輸入像是NONE/PKCS1PADDING/EC DSA,
//RSA, MD5 or SHA-1 ,
//DiffieHellman (1024) DSA (1024) RSA (1024, 2048) 等等
String Algorithm_In = "RSA";

//公私鑰加密長度,一般1024即可,數值越大越安全,但運算速度會慢些
int keySize = 2048;

//公私鑰檔案存放路徑如果不存在的話,就先建立該路徑
String Pub_File_Name_In = "/usr/cert/pubKey_"+File_Name+".key";
String Pri_File_Name_In = "/usr/cert/priKey_"+File_Name+".key";
java.io.File file1 = new java.io.File("/usr/cert");
if (!file1.exists()) file1.mkdirs();

//產生公私鑰檔案
if (!(new java.io.File(Pub_File_Name_In).exists())) {

keyPairGen(Pub_File_Name_In, Pri_File_Name_In, Algorithm_In, keySize);

}

%>

則在A伺服器上使用瀏覽器執行例如http://127.0.0.1/Module/Ca/GenkeyExample.jsp?File_Name=A,就可以產生A伺服器的公私鑰共兩個檔案,分別為公鑰/usr/cert/pubKey_A.key與私鑰/usr/cert/priKey_A.key。

同樣的在B伺服器上使用瀏覽器執行例如http://127.0.0.1/Module/Ca/GenkeyExample.jsp?File_Name=B,就可以產生B伺服器的公私鑰共兩個檔案,分別為公鑰/usr/cert/pubKey_B.key與私鑰/usr/cert/priKey_B.key。

將A伺服器的公鑰/usr/cert/pubKey_A.key複製一份到B伺服器的/usr/cer路徑下,私鑰不必也不應複製過去。
將B伺服器的公鑰/usr/cert/pubKey_B.key複製一份到A伺服器上/usr/cer路徑下,私鑰不必也不應複製過去。

(你也可以在同一台伺服器上產生所有伺服器的公私鑰,再依上面的範例來佈署公私鑰)

A伺服器對B伺服器發話加密範例

接著,我們要在A伺服器上,使用B伺服器的公鑰來加密一段文字,然後轉成Base64編碼才能透過網路傳送到B伺服器上,為了簡化程式碼,我們將此動作拆成幾個函式來完成如下:

keyReadByte() 讀出鑰匙檔案的內容。
keyEncrypt2Base64() 利用鑰匙內容將一段文字加密並轉成Base64格式。
UrlConnect() 從JSP伺服器上透過網路傳送一段文字到另外一個伺服器。

讀出鑰匙檔案的內容 /Module/Ca/keyReadByte.jsp

<%!
// 程式碼來源 : http://playjsp.blogspot.com ,作者:Steven,不限使用請註明出處。
// 傳入鑰匙檔案路徑,傳回鑰匙檔案內容
public byte[] keyReadByte(String File_Name_In){

byte[] fileBytes = null;
try{

java.io.File keyFile = new java.io.File(File_Name_In);
java.io.FileInputStream fis = new java.io.FileInputStream(keyFile);
java.io.DataInputStream dis = new java.io.DataInputStream(fis);
fileBytes = new byte[(int)keyFile.length()];
dis.readFully(fileBytes);

} catch (Exception e) {

return null;//out.print(e);

}
return fileBytes;

}
%>

 

利用鑰匙內容將一段文字加密並轉成Base64格式 /Module/Ca/keyEncrypt2Base64.jsp

<%!
// 程式碼來源 : http://playjsp.blogspot.com ,作者:Steven,不限使用請註明出處。
//利用鑰匙檔案將一段文字加密並轉成Base64格式
//傳入要加密的文字、鑰匙檔案內容與演算法種類,傳回被加密的文字
public String keyEncrypt2Base64(String Str_In, byte[] publicfile_Bytes, String Algorithm_In){

String base64str = "";
try {

//transfer byte into key
java.security.KeyFactory kf = java.security.KeyFactory.getInstance(Algorithm_In);
java.security.PublicKey pk = kf.generatePublic(new java.security.spec.X509EncodedKeySpec(publicfile_Bytes));

//加密 cipher 並轉成 Base64
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(Algorithm_In);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, pk);
base64str = new sun.misc.BASE64Encoder().encodeBuffer(cipher.doFinal(Str_In.getBytes()));

} catch(Exception e) {

return "";

}
return base64str;

}
%>

 

從JSP伺服器上透過網路傳送一段文字到另外一個伺服器 /Module/Jf/UrlConnect.jsp

請到以下網址 http://playjsp.blogspot.com/2015/03/jsp-httpurlconnection.html 頁面搜尋[/Module/Jf/UrlConnect.jsp]字串,並複製其程式碼。

好了,現在可以一次呼叫上面三個函式,從A伺服器傳送一段加密文字到B伺服器了,範例網頁如下,只要在A伺服上的瀏覽器,執行例如http://127.0.0.1/Module/Ca/SendExample.jsp即可,當然B伺服器上接收的程式要先安置好(參考本文下一段),此處網頁的執行才不會出錯。

傳送範例網頁 /Module/Ca/SendExample.jsp

<%@ page language="java" import="java.io.*" contentType="text/html" %>
<%@ include file="/Module/Ca/keyReadByte.jsp" %>
<%@ include file="/Module/Ca/keyEncrypt2Base64.jsp" %>
<%@ include file="/Module/Jf/UrlConnect.jsp" %>
<%

//B伺服器的公鑰檔案路徑
String Pub_File_Name = "/usr/cert/pubKey_B.key";
//抓現在哦系統時間當作等等要加密的內容
String accessKey = Long.toString(System.currentTimeMillis());
//將上面的 accessKey 內容加密並轉成 Base64 格式
String base64encString = keyEncrypt2Base64(accessKey, keyReadByte(Pub_File_Name), "RSA");

//用來存放傳回結果的記憶體
StringBuffer resultBuffer = new StringBuffer();

//要上傳的內容
StringBuffer postBuffer = new StringBuffer();
postBuffer.append(
//base64encString 是加密過的內容
java.net.URLEncoder.encode("accessKey", "UTF-8") + "=" + java.net.URLEncoder.encode(base64encString, "UTF-8")
//這個是沒有加密的內容
+ "&" + java.net.URLEncoder.encode("Server_Name", "UTF-8") + "=" + java.net.URLEncoder.encode("A", "UTF-8")
);

//將傳送內容印出來
out.print("<P>傳送給B伺服器的認證碼為["+accessKey+"]");
out.print("<P>加密後為["+base64encString+"]");

//開始傳送,下面 192.168.2.254 是B伺服器的網址
UrlConnect(postBuffer, "http://192.168.2.254/Module/Ca/AnswerExample.jsp", resultBuffer, "");

//將回傳內容印出來
out.print("<P>B伺服器的回覆 : " + resultBuffer.toString());

%>

B伺服器收話解密範例

同樣為了簡化程式,我們在此處使用 keyDecryptFromBase64() 函式來負責將收到的 Base64 格式字串解密如下:

將收到的 Base64 格式字串解密 /Module/Ca/keyDecryptFromBase64.jsp

<%!
// 程式碼來源 : http://playjsp.blogspot.com ,作者:Steven,不限使用請註明出處。
//利用鑰匙檔案將一段已經加密且轉成Base64格式的文字解密
public String keyDecryptFromBase64(String base64str, byte[] fileBytes_pri, String Algorithm_In){

String Str_Out = "";
try {

//由 Base64 格式轉回原先的格式
byte[] inpBytes = new sun.misc.BASE64Decoder().decodeBuffer(base64str);
//transfer byte into Private key
java.security.KeyFactory kf = java.security.KeyFactory.getInstance(Algorithm_In);
java.security.PrivateKey pk = kf.generatePrivate(new java.security.spec.PKCS8EncodedKeySpec(fileBytes_pri));
//用鑰匙解密
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(Algorithm_In);
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, pk);
Str_Out = new String(cipher.doFinal(inpBytes));

} catch( Exception e ){

return "";

}
return Str_Out;

}
%>

B伺服器收話解密的JSP網頁程式AnswerExample.jsp如下,範例中直接呼叫上面的函式,以便解密A伺服器送來的資料:

呼叫範立網頁 /Module/Ca/AnswerExample.jsp

<%@ page language="java" contentType="text/html" %>
<%@ include file="/Module/Ca/keyReadByte.jsp" %>
<%@ include file="/Module/Ca/keyDecryptFromBase64.jsp" %>
<%

//接收A傳來的內容,若是中文請自行轉碼
String accessKey = request.getParameter("accessKey");
String Server_Name = request.getParameter("Server_Name");

//B伺服器自己私鑰的存放路徑
String Pri_File_Name = "/usr/cert/priKey_B.key";

//使用B伺服器自己的私鑰解密
String accessKey_Ori = keyDecryptFromBase64(accessKey, keyReadByte(Pri_File_Name), "RSA");
String ResultStr = "remote server name is ["+Server_Name+"], accessKey is ["+accessKey_Ori+"]";

//至於 ResultStr 要不要先用 A 伺服器的公鑰加密再傳給 A,就看使用者自己的應用了,此處簡單略過
out.print(ResultStr);

%>

範例執行結果

前面 http://127.0.0.1/Module/Ca/SendExample.jsp 執行結果如下,相同的道理,B伺服器對A伺服器回話時,也要先使用A伺服器的公鑰來加密對話內容,A伺服器收到後使用A伺服器的私鑰來解密,此處範例簡單省略此步驟。

 

沒有留言:

張貼留言