最近作的一個項目,服務器爲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. 客戶端接收到公鑰之後,使用公鑰對密碼加密,而後將密文發給服務器;spa
4. 服務器接收到密文之後,使用私鑰解密,判斷是不是正確的密碼。code
下面是關鍵代碼。blog
生成密鑰和加密、解密的代碼:
/** * 生成公鑰和私鑰 * @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)解密,得到密碼明文,而後就能夠判斷密碼是否正確。