最近做的一個項目,伺服器為Java,採用SSH框架,客戶端為Android和IOS。當用戶登錄時,從客戶端向伺服器提交用戶名和密碼。這就存在一個問題,如果數據包在網路上被其他人截取了,密碼就有可能泄露。 可以採用Base64對密碼編碼,但是Base64要進行解碼是很容易的事。 另一種方法是對密碼進行 ...
最近做的一個項目,伺服器為Java,採用SSH框架,客戶端為Android和IOS。當用戶登錄時,從客戶端向伺服器提交用戶名和密碼。這就存在一個問題,如果數據包在網路上被其他人截取了,密碼就有可能泄露。
可以採用Base64對密碼編碼,但是Base64要進行解碼是很容易的事。
另一種方法是對密碼進行MD5加密,MD5是不可逆的,只能加密不能解密。但是其他人截取了密碼的MD5字元串以後,可以原封不動的將MD5加密後的字元串提交給伺服器,伺服器肯定會判斷這是正確的密碼,這樣還是可以登錄進去。
解決的方法就只能採用加密演算法了。加密演算法分為對稱加密和非對稱加密。對稱加密演算法,加密和解密使用相同的密鑰,密鑰在網路傳輸的過程中有可能被截取,所以不是很安全。非對稱加密,使用公鑰加密,只能使用私鑰解密,公鑰是公開的,私鑰是不公開的。即使在傳遞的過程中,公鑰被其他人獲取了也無所謂,因為公鑰是用來加密的,只有私鑰才能解密,而私鑰是不會傳遞的,也就不可能被其他人獲取。
非對稱加密最常用的就是RSA演算法,RSA演算法是由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的,取了他們姓的第一個字母來命名。RSA演算法的原理就不講了。密鑰長度為768的RSA演算法有可能被破解,密鑰長度為1024的RSA演算法還沒有被破解,所以可以認為密鑰長度為1024的RSA演算法是比較安全的。但是RSA演算法的計算量大,一般只用於關鍵信息的加密,如密碼、對稱加密演算法的密鑰等。在我們的項目中,就使用RSA演算法對用戶密碼進行加密。具體的步驟如下:
1. 客戶端向伺服器申請密鑰;
2. 伺服器接收到客戶端的申請以後,生成一對密鑰,將公鑰發給客戶端,私鑰自己保存;
3. 客戶端接收到公鑰以後,使用公鑰對密碼加密,然後將密文發給伺服器;
4. 伺服器接收到密文以後,使用私鑰解密,判斷是否是正確的密碼。
下麵是關鍵代碼。
生成密鑰和加密、解密的代碼:
/** * 生成公鑰和私鑰 * @throws NoSuchAlgorithmException * */ public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException{ HashMap<String, Object> map = new HashMap<String, Object>(); KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(1024); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); map.put("public", publicKey); map.put("private", privateKey); return map; } /** * 使用模和指數生成RSA公鑰 * 註意:【此代碼用了預設補位方式,為RSA/None/PKCS1Padding,不同JDK預設的補位方式可能不同,如Android預設是RSA * /None/NoPadding】 * * @param modulus * 模 * @param exponent * 指數 * @return */ public static RSAPublicKey getPublicKey(String modulus, String exponent) { try { BigInteger b1 = new BigInteger(modulus); BigInteger b2 = new BigInteger(exponent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2); return (RSAPublicKey) keyFactory.generatePublic(keySpec); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 使用模和指數生成RSA私鑰 * 註意:【此代碼用了預設補位方式,為RSA/None/PKCS1Padding,不同JDK預設的補位方式可能不同,如Android預設是RSA * /None/NoPadding】 * * @param modulus * 模 * @param exponent * 指數 * @return */ public static RSAPrivateKey getPrivateKey(String modulus, String exponent) { try { BigInteger b1 = new BigInteger(modulus); BigInteger b2 = new BigInteger(exponent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2); return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 公鑰加密 * * @param data * @param publicKey * @return * @throws Exception */ public static String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 模長 int key_len = publicKey.getModulus().bitLength() / 8; // 加密數據長度 <= 模長-11 String[] datas = splitString(data, key_len - 11); String mi = ""; //如果明文長度大於模長-11則要分組加密 for (String s : datas) { mi += bcd2Str(cipher.doFinal(s.getBytes())); } return mi; } /** * 私鑰解密 * * @param data * @param privateKey * @return * @throws Exception */ public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); //模長 int key_len = privateKey.getModulus().bitLength() / 8; byte[] bytes = data.getBytes(); byte[] bcd = ASCII_To_BCD(bytes, bytes.length); System.err.println(bcd.length); //如果密文長度大於模長則要分組解密 String ming = ""; byte[][] arrays = splitArray(bcd, key_len); for(byte[] arr : arrays){ ming += new String(cipher.doFinal(arr)); } return ming; }
伺服器收到客戶端的請求時,生成一對密鑰:
HashMap<String, Object> mymap = RSAUtils.getKeys(); // 生成公鑰和私鑰 RSAPublicKey publicKey = (RSAPublicKey) mymap.get("public"); RSAPrivateKey privateKey = (RSAPrivateKey) mymap.get("private"); // 模 String modulus = publicKey.getModulus().toString(); // 公鑰指數 String public_exponent = publicKey.getPublicExponent().toString(); // 私鑰指數 String private_exponent = privateKey.getPrivateExponent().toString(); // 使用模和指數生成公鑰和私鑰 RSAPublicKey pubKey = RSAUtils.getPublicKey(modulus, public_exponent); RSAPrivateKey priKey = RSAUtils.getPrivateKey(modulus, private_exponent);
將其中的模和私鑰指數發給客戶端,客戶端收到以後,使用getPublicKey(String modulus, String exponent)生成公鑰,使用公鑰對密碼加密,然後發給伺服器。伺服器收到密文以後,使用decryptByPrivateKey(String data, RSAPrivateKey privateKey)解密,獲得密碼明文,然後就可以判斷密碼是否正確。