RSA加密演算法的簡單案例

傑華園發表於2016-08-16

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檔案。

點選(此處)摺疊或開啟

  1. package com.fr.privilege;

  2. import java.io.ByteArrayOutputStream;
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. import java.math.BigInteger;
  8. import java.security.KeyFactory;
  9. import java.security.KeyPair;
  10. import java.security.KeyPairGenerator;
  11. import java.security.NoSuchAlgorithmException;
  12. import java.security.PrivateKey;
  13. import java.security.PublicKey;
  14. import java.security.SecureRandom;
  15. import java.security.interfaces.RSAPrivateKey;
  16. import java.security.interfaces.RSAPublicKey;
  17. import java.security.spec.InvalidKeySpecException;
  18. import java.security.spec.RSAPrivateKeySpec;
  19. import java.security.spec.RSAPublicKeySpec;

  20. import javax.crypto.Cipher;

  21. /**
  22.  * RSA 工具類。提供加密,解密,生成金鑰對等方法。
  23.  * 需要到下載bcprov-jdk14-123.jar。
  24.  *
  25.  */
  26. public class RSAUtil {
  27.     /**
  28.      * * 生成金鑰對 *
  29.      *
  30.      * @return KeyPair *
  31.      * @throws EncryptException
  32.      */
  33.     public static KeyPair generateKeyPair() throws Exception {
  34.         try {
  35.             KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
  36.                     new org.bouncycastle.jce.provider.BouncyCastleProvider());
  37.             final int KEY_SIZE = 1024;// 沒什麼好說的了,這個值關係到塊加密的大小,可以更改,但是不要太大,否則效率會低
  38.             keyPairGen.initialize(KEY_SIZE, new SecureRandom());
  39.             KeyPair keyPair = keyPairGen.generateKeyPair();
  40.             saveKeyPair(keyPair);
  41.             return keyPair;
  42.         } catch (Exception e) {
  43.             throw new Exception(e.getMessage());
  44.         }
  45.     }

  46.     public static KeyPair getKeyPair() throws Exception {
  47.         FileInputStream fis = new FileInputStream("C:/RSAKey.txt");
  48.         ObjectInputStream oos = new ObjectInputStream(fis);
  49.         KeyPair kp = (KeyPair) oos.readObject();
  50.         oos.close();
  51.         fis.close();
  52.         return kp;
  53.     }

  54.     public static void saveKeyPair(KeyPair kp) throws Exception {

  55.         FileOutputStream fos = new FileOutputStream("C:/RSAKey.txt");
  56.         ObjectOutputStream oos = new ObjectOutputStream(fos);
  57.         // 生成金鑰
  58.         oos.writeObject(kp);
  59.         oos.close();
  60.         fos.close();
  61.     }

  62.     /**
  63.      * * 生成公鑰 *
  64.      *
  65.      * @param modulus *
  66.      * @param publicExponent *
  67.      * @return RSAPublicKey *
  68.      * @throws Exception
  69.      */
  70.     public static RSAPublicKey generateRSAPublicKey(byte[] modulus,
  71.             byte[] publicExponent) throws Exception {
  72.         KeyFactory keyFac = null;
  73.         try {
  74.             keyFac = KeyFactory.getInstance("RSA",
  75.                     new org.bouncycastle.jce.provider.BouncyCastleProvider());
  76.         } catch (NoSuchAlgorithmException ex) {
  77.             throw new Exception(ex.getMessage());
  78.         }

  79.         RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
  80.                 modulus), new BigInteger(publicExponent));
  81.         try {
  82.             return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
  83.         } catch (InvalidKeySpecException ex) {
  84.             throw new Exception(ex.getMessage());
  85.         }
  86.     }

  87.     /**
  88.      * * 生成私鑰 *
  89.      *
  90.      * @param modulus *
  91.      * @param privateExponent *
  92.      * @return RSAPrivateKey *
  93.      * @throws Exception
  94.      */
  95.     public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus,
  96.             byte[] privateExponent) throws Exception {
  97.         KeyFactory keyFac = null;
  98.         try {
  99.             keyFac = KeyFactory.getInstance("RSA",
  100.                     new org.bouncycastle.jce.provider.BouncyCastleProvider());
  101.         } catch (NoSuchAlgorithmException ex) {
  102.             throw new Exception(ex.getMessage());
  103.         }

  104.         RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(
  105.                 modulus), new BigInteger(privateExponent));
  106.         try {
  107.             return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
  108.         } catch (InvalidKeySpecException ex) {
  109.             throw new Exception(ex.getMessage());
  110.         }
  111.     }

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

  147.                 i++;
  148.             }
  149.             return raw;
  150.         } catch (Exception e) {
  151.             throw new Exception(e.getMessage());
  152.         }
  153.     }

  154.     /**
  155.      * * 解密 *
  156.      *
  157.      * @param key
  158.      * 解密的金鑰 *
  159.      * @param raw
  160.      * 已經加密的資料 *
  161.      * @return 解密後的明文 *
  162.      * @throws Exception
  163.      */
  164.     public static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception {
  165.         try {
  166.             Cipher cipher = Cipher.getInstance("RSA",
  167.                     new org.bouncycastle.jce.provider.BouncyCastleProvider());
  168.             cipher.init(cipher.DECRYPT_MODE, pk);
  169.             int blockSize = cipher.getBlockSize();
  170.             ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
  171.             int j = 0;

  172.             while (raw.length - j * blockSize > 0) {
  173.                 bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
  174.                 j++;
  175.             }
  176.             return bout.toByteArray();
  177.         } catch (Exception e) {
  178.             throw new Exception(e.getMessage());
  179.         }
  180.     }

  181.     /**
  182.      * * *
  183.      *
  184.      * @param args *
  185.      * @throws Exception
  186.      */
  187.     public static void main(String[] args) throws Exception {
  188.         RSAPublicKey rsap = (RSAPublicKey) RSAUtil.generateKeyPair()
  189.                 .getPublic();
  190.         String test = "hello world";
  191.         byte[] en_test = encrypt(getKeyPair().getPublic(), test.getBytes());
  192.         System.out.println("123:" + new String(en_test));
  193.         byte[] de_test = decrypt(getKeyPair().getPrivate(), en_test);
  194.         System.out.println(new String(de_test));
  195.     }
  196. }

4、定義密碼驗證類

定義TestPasswordValidatorRSA.java密碼驗證類

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

點選(此處)摺疊或開啟

  1. package com.fr.privilege;

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

  23.     @Override
  24.     public boolean validatePassword(String arg0, String arg1) {
  25.         // TODO Auto-generated method stub
  26.         return false;
  27.     }


  28. }

5、編譯類檔案

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

6、登陸Login.jsp頁面設定

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

點選(此處)摺疊或開啟

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page import="com.fr.privilege.providers.dao.RSAUtil"%>
  3. <%!public String Testmo() {
  4.         String module = "";
  5.         try {
  6.             java.security.interfaces.RSAPublicKey rsap = (java.security.interfaces.RSAPublicKey) RSAUtil
  7.                     .getKeyPair().getPublic();
  8.             module = rsap.getModulus().toString(16);
  9.         } catch (Exception e) {
  10.             // TODO Auto-generated catch block
  11.             e.printStackTrace();
  12.         }
  13.         return module;
  14.     }%>
  15. <%!public String Testem() {
  16.         String empoent = "";
  17.         try {
  18.             java.security.interfaces.RSAPublicKey rsap = (java.security.interfaces.RSAPublicKey) RSAUtil
  19.                     .getKeyPair().getPublic();
  20.             empoent = rsap.getPublicExponent().toString(16);
  21.         } catch (Exception e) {
  22.             // TODO Auto-generated catch block
  23.             e.printStackTrace();
  24.         }
  25.         return empoent;
  26.     }%>
  27. <html>
  28.     <head>
  29.         <script type="text/javascript"
  30.             src="ReportServer?op=emb&resource=finereport.js"></script>
  31.         <script type="text/javascript" src="js/RSA.js"></script>
  32.         <script type="text/javascript" src="js/BigInt.js"></script>
  33.         <script type="text/javascript" src="js/Barrett.js"></script>
  34.         <script type="text/javascript">
  35.     function bodyRSA()
  36.     {
  37.     setMaxDigits(130);
  38.     var a = "<%=Testmo()%>";
  39.     var b = "<%=Testem()%>";
  40.     key = new RSAKeyPair(b,"",a);
  41.     }
  42. function doSubmit() {
  43.     bodyRSA();
  44.     var username = FR.cjkEncode(document.getElementById("username").value); //獲取輸入的使用者名稱
  45.     var password = FR.cjkEncode(document.getElementById("password").value); //獲取輸入的引數
  46.     $.ajax({
  47.         url : "ReportServer?op=auth_login&fr_username=" + username + "&fr_password=" + password, //將使用者名稱和密碼傳送到報表認證地址op=auth_login
  48.         data : {__redirect__ : 'false'},
  49.         complete : function(res) {
  50.             var jo = FR.jsonDecode(res.responseText);
  51.             if(jo.url) {
  52.                window.location=jo.url+ "&_=" + new Date().getTime(); //認證成功跳轉頁面,因為ajax不支援重定向所有需要跳轉的設定
  53.             }
  54.             else{
  55.                alert("使用者名稱密碼錯誤!") //認證失敗
  56.             }
  57.         }
  58.     })
  59. }
  60. </script>
  61.     </head>
  62.     <body>
  63.         <p>
  64.             請登入
  65.         </p>
  66.         <form name="login" method="POST">
  67.             <p>
  68.                 使用者名稱:
  69.                 <input id="username" type="text" />
  70.             </p>
  71.             <p>
  72.                 密 碼:
  73.                 <input id="password" type="password" />
  74.             </p>
  75.             <input type="button" value="登入" onclick="doSubmit()" />
  76.         </form>
  77.     </body>
  78. </html>

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/21472864/viewspace-2123552/,如需轉載,請註明出處,否則將追究法律責任。

相關文章