最近在作的項目,須要帳戶密碼登陸,採用 AES 加密算法,從認識到了解,以及後續的擴展學習。html
首先,明確幾個加解密的重要概念java
密鑰react
在明文轉換爲密文或將密文轉換爲明文的算法中輸入的參數。密鑰分爲對稱密鑰(私用密鑰)與非對稱密鑰(公共密鑰)。git
密鑰加密算法
發送和接收數據的雙方,使用相同的或對稱的密鑰對明文進行加密解密運算的加密方法。c#
加密向量數組
一般採用密鑰長度的隨機文本塊對純文本進行異或運算,而後再對其進行加密,產生一個加密文本塊。而後,將前面產生的密文塊做爲一個初始化向量對下一個純文本塊進行異或運算。安全
對稱加密算法中,若是隻有一個密鑰來加密數據的話,明文中的相同文字就會也會被加密成相同的密文,這樣密文和明文就有徹底相同的結構,容易破解。若是給一個初始化向量,第一個明文使用初始化向量混合並加密,第二個明文用第一個明文的加密後的密文與第二個明文混合加密,這樣加密出來的密文的結構則徹底與明文不一樣,更加安全可靠。網絡
綜上,加密之因此安全,並不是不知道加解密算法,而是加密的密鑰和向量是絕對的隱藏,加密向量能夠增長加密算法的強度(區塊加密)。架構
那麼,什麼是 AES 加密呢?
Advanced Encryption Standard(高級加密標準),在密碼學中又叫 Rijndael 加密法,是美國聯邦政府採用的一種區塊加密標準。
Rijndael 密碼的設計力求知足如下3條標準:
當前的大多數分組密碼,其輪函數是Feistel結構。Rijndael 輪函數是由3個不一樣的可逆均勻變換組成。
具體信息參見:AES是個什麼鬼?
本文後續代碼基礎:AES加密,密鑰位數不足轉換成byte[]後填充(byte)0,加密向量16位,加密模式CBC,填充模式PKCS5Padding,字符集是UTF-8,輸出是HEX格式
其中,PKCS5Padding(Java)與 PKCS7(C#)是能夠互相加解密的,這是正好匹配的狀況。
可是,常常會存在合做雙方平臺不一致、跨語言接口對接致使數據不一致的問題,能夠採用ikvm工具將.jar包轉換爲.dll,則在.Net平臺直接引用並調用方法便可
/// <summary> /// AES加密 /// </summary> /// <param name="_pwd">明文密碼</param> /// <param name="_key">加密密鑰</param> /// <param name="_iv">加密向量</param> /// <returns></returns> public static string AESEncrypt(string _pwd, string _key, string _iv, int _keyLen, int _ivLen) { byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(_pwd); using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider()) { aesProvider.Mode = System.Security.Cryptography.CipherMode.CBC; aesProvider.Padding = System.Security.Cryptography.PaddingMode.PKCS7; aesProvider.Key = GetAesKey(_key, _keyLen); aesProvider.IV = GetAesVector(_iv, _ivLen); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream( ms, aesProvider.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(toEncryptArray, 0, toEncryptArray.Length); cs.FlushFinalBlock(); cs.Close(); } string resultStr = ByteArrayToHexString(ms.ToArray()); // Convert.ToBase64String ms.Close(); return resultStr; } } } /// <summary> /// AES解密 /// </summary> /// <param name="_pwd">暗文密碼</param> /// <param name="_key">密鑰</param> /// <param name="_iv">向量</param> /// <returns></returns> public static string AESDecrypt(string _pwd, string _key, string _iv, int _keyLen, int _ivLen) { byte[] toDecryptArray = HexStringToByteArray(_pwd); using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider()) { aesProvider.Mode = System.Security.Cryptography.CipherMode.CBC; aesProvider.Padding = System.Security.Cryptography.PaddingMode.PKCS7; aesProvider.Key = GetAesKey(_key, _keyLen); aesProvider.IV = GetAesVector(_iv, _ivLen); using (MemoryStream ms = new MemoryStream(toDecryptArray)) { byte[] decryptBytes = new byte[toDecryptArray.Length]; string resultStr = string.Empty; using (CryptoStream cs = new CryptoStream( ms, aesProvider.CreateDecryptor(), CryptoStreamMode.Read)) { if (cs.Read(decryptBytes, 0, decryptBytes.Length) > 0) { resultStr = Encoding.UTF8.GetString(decryptBytes); } cs.Close(); } ms.Close(); return resultStr; } } } /// </summary> /// 獲取AES密鑰 /// </summary> /// <param name="_key">Aes密鑰字符串</param> /// <param name="_length">默認長度16字節</param> /// <returns>Aes密鑰</returns> public static byte[] GetAesKey(string _key, int _length) { byte[] resBytes = new byte[_length]; byte[] keyBytes = UTF8Encoding.UTF8.GetBytes(_key); int lenTmp = keyBytes.Length; if (lenTmp <= _length) { Array.Copy(keyBytes, resBytes, lenTmp); } else { Array.Copy(keyBytes, resBytes, _length); } return resBytes; } /// <summary> /// 獲取AES向量 /// </summary> /// <param name="_iv">Aes向量字符串</param> /// <param name="_length">默認長度16字節</param> /// <returns>Aes向量</returns> public static byte[] GetAesVector(string _iv, int _length) { byte[] resBytes = new byte[_length]; if (string.IsNullOrWhiteSpace(_iv)) { // _iv爲空,返回全0字節數組 return resBytes; } else { byte[] ivBytes = UTF8Encoding.UTF8.GetBytes(_iv); int lenTmp = ivBytes.Length; if (lenTmp <= _length) { Array.Copy(ivBytes, resBytes, lenTmp); } else { Array.Copy(ivBytes, resBytes, _length); } return resBytes; } } /// <summary> /// 將一個byte數組轉換成一個格式化的16進制字符串 /// </summary> /// <param name="data">byte數組</param> /// <returns>格式化的16進制字符串</returns> public static string ByteArrayToHexString(byte[] data) { StringBuilder sb = new StringBuilder(data.Length * 3); foreach (byte b in data) { //16進制數字 sb.Append(Convert.ToString(b, 16).PadLeft(2, '0')); } return sb.ToString().ToUpper(); } /// <summary> /// 將一個格式化的16進制字符串轉換成一個byte數組 /// </summary> /// <param name="?"></param> /// <returns></returns> public static byte[] HexStringToByteArray(string str) { str = str.Replace(" ", ""); byte[] buffer = new byte[str.Length / 2]; for (int i = 0; i < str.Length; i += 2) { buffer[i / 2] = (byte)Convert.ToByte(str.Substring(i, 2), 16); } return buffer; }
測試發現一個解密的bug,會出現解密結果 "288008\0\0\0\0\0\0\0\0\0\0" 的狀況,遂推薦以下方法
using (ICryptoTransform transform = aesProvider.CreateDecryptor()) { byte[] plainText = transform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length); return Encoding.UTF8.GetString(plainText); }
或直接在返回時進行替換處理: return resultStr.Replace("\0", "");
注意,上述代碼加密結果是 HEX格式。若是採用 Base64格式,需分別以下改動:
加密-AESEncrypt:Convert.ToBase64String() 解密-AESDecrypt:Convert.FromBase64String()
除上述加解密方法外,還能夠採用以下方式:RijndaelManaged
public static string AesEnc(string key, string content) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] contentBytes = Encoding.UTF8.GetBytes(content); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyBytes; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransForm = rDel.CreateEncryptor(); byte[] result = cTransForm.TransformFinalBlock(contentBytes, 0, contentBytes.Length); return Convert.ToBase64String(result, 0, result.Length); } public static string AesDecr(string key, string content) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] contentBytes = Encoding.UTF8.GetBytes(content); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyBytes; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransForm = rDel.CreateDecryptor(); byte[] result = cTransForm.TransformFinalBlock(contentBytes, 0, contentBytes.Length); return Encoding.UTF8.GetString(result); }
具體參見:AES 加解密;
學習 MD5 以前,再明確單向加密的概念。
單向加密
單向散列算法,經過散列生成固定長度,不可逆且防衝突,經常使用於消息摘要、密鑰加密等確保數據的完整性。
Message-Digest Algorithm 5(消息摘要算法),加密單向不可逆,即沒法根據密文推導出明文。計算機安全領域普遍使用的一種散列函數,用以提供消息的完整性保護。
特色:
主要用途:
MD5 以512位分組來處理輸入的信息,且每一分組又被劃分爲16個32位子分組,通過了一系列的處理後,算法的輸出由四個32位分組組成,將這四個32位分組級聯後將生成一個128位散列值。
using System.Security.Cryptography; using System.IO; using System.Web; using System.Web.Security; /// <summary> /// MD5加密 /// 信息摘要算法,不可逆 /// </summary> /// <param name="myStr">待加密字符串</param> public static string MD5Encrypt(string myStr) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] myBytes = System.Text.Encoding.UTF8.GetBytes(myStr); byte[] hashBytes = md5.ComputeHash(myBytes); md5.Clear();//資源釋放 StringBuilder _sb_ = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) { // "x2"表示加密結果爲32位,"x3"爲48位,"x4"爲64位 // X/x 分別表示結果大小寫 _sb_.Append(hashBytes[i].ToString("x2")); } return _sb_.ToString(); } /// <summary> /// MD5加密方法 /// 默認結果32位,若要結果16位則.SubString()截取便可 /// </summary> public static string Get32MD5(string myStr) { string md5Res = ""; md5Res = FormsAuthentication.HashPasswordForStoringInConfigFile(myStr, "MD5"); return (md5Res); } /// <summary> /// MD5加密 /// </summary> public static string MD5(string stringToHash) { MD5 md5csp = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] emailBytes = Encoding.UTF8.GetBytes(stringToHash); byte[] hashedEmailBytes = md5csp.ComputeHash(emailBytes); md5csp.Clear(); string md5 = BitConverter.ToString(hashedEmailBytes).Replace("-", ""); return md5.ToLower(); }
在使用 MD5 加密中可能遇到的問題,參見:http://blog.csdn.net/zhao1468465728/article/details/47025645;
SHA-0和SHA-1已被攻破,SHA-3應用較少。主要使用SHA-2,多指:SHA256
SHA-1
/// <summary> /// SHA1加密 /// </summary> public static string SHA1(string data) { SHA1 sha = new SHA1CryptoServiceProvider(); byte[] temp1 = Encoding.UTF8.GetBytes(data); byte[] temp2 = sha.ComputeHash(temp1); sha.Clear(); ; string sig = BitConverter.ToString(temp2).Replace("-", ""); return sig.ToLower(); }
SHA-256
/// <summary> /// SHA256加密 /// </summary> public static string SHA256Encrypt(string data) { try { byte[] bytes = Encoding.UTF8.GetBytes(data); byte[] hash = SHA256Managed.Create().ComputeHash(bytes); StringBuilder builder = new StringBuilder(); for (int i = 0; i < hash.Length; i++) { builder.Append(hash[i].ToString("X2")); } return builder.ToString(); } catch (Exception ex) { return ""; } } /// <summary> /// SHA256加密 /// </summary> public static string SHA256(string data) { byte[] SHA256Data = Encoding.UTF8.GetBytes(data); SHA256Managed Sha256 = new SHA256Managed(); byte[] bytes = Sha256.ComputeHash(SHA256Data); Sha256.Clear(); //Replace掉「-」後長度爲64,視需求而定 return BitConverter.ToString(bytes).Replace("-", ""); }
使用一個 56 位的密鑰以及附加的 8 位奇偶校驗位,產生最大 64 位的分組大小。
待加密文本塊分紅兩半,使用子密鑰對其中一半應用循環功能,而後將輸出與另外一半進行「異或」運算;接着交換這兩半、迭代操做,但最後一個循環不交換。
該加密方法瞭解便可。
學習 RSA 以前,再明確 2 個概念。前面的 MD5 是不可逆,而 可逆加密分爲對稱加密和非對稱加密:
對稱加密
發送方和接收方採用相同的密鑰進行加密和解密。
非對稱加密
加密和解密使用不一樣的密鑰的一類加密算法。一般有兩個密鑰A和B,用密鑰A加密數據獲得的密文,只有密鑰B能夠進行解密操做(即便密鑰A也沒法解密),相反,使用了密鑰B加密數據獲得的密文,只有密鑰A能夠解密。即存在一對公鑰和私鑰(徹底不一樣但徹底匹配),用公鑰加密的信息只能用對應的私鑰進行解密,反之亦然(一般加密是用公鑰,解密是用私鑰)。
在實際狀況中,一般採用方式:非對稱加密算法管理對稱算法的密鑰,而後用對稱加密算法加密數據,集成兩類加密算法的優勢
前面的 DES 和 AES 是對稱加密方法,下面要介紹的 RSA 則是非對稱加密方法。除RSA外:
其中,ECC計算量小、處理速度快,存儲空間佔用小,抗攻擊性強,帶寬要求低(IC卡、無線網領域)
非對稱加密,目前最有影響力的支持變長密鑰的公共密鑰算法,第一個能夠同時用於加密和數字簽名的算法。
主要用於:(1)數據加密 (2)數字簽名(只有非對稱加密算法能夠)
缺點:大數計算速度慢,密鑰生成麻煩。
務必明確,RSA 的加密體制和簽名體制是不一樣的算法:
具體算法細節參見:RSA的公鑰和私鑰到底哪一個纔是用來加密和哪一個用來解密?@劉巍然-學酥 的講解。
下面是RSA加解密的簡單實現,學習之。
/// <summary> /// RSA加密的密鑰結構 /// </summary> public class RSAKey { // 公鑰 public string PublicKey { get; set; } // 私鑰 public string PrivateKey { get; set; } } /// <summary> /// 全局靜態變量,保存密鑰對 /// </summary> private static RSAKey RSA_KEY = new RSAKey(); /// <summary> /// 生成RSA密鑰對 /// </summary> public static void GenerateRASKey() { RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(); RSA_KEY = new RSAKey() { PublicKey = rsaProvider.ToXmlString(false), PrivateKey = rsaProvider.ToXmlString(true) }; } /// <summary> /// 獲取加密公鑰 /// </summary> private static string GetPublicKey() { string publicKey = RSA_KEY.PublicKey; return publicKey; } /// <summary> /// 獲取解密私鑰 /// </summary> private static string GetPrivateKey() { string privateKey = RSA_KEY.PrivateKey; return privateKey; } /// <summary> /// RSA加密 /// </summary> /// <param name="data">待加密數據</param> public static string RSAEncrypt(string data) { try { //獲取公鑰 string publicKey = GetPublicKey(); //建立RSA對象並載入公鑰 RSACryptoServiceProvider rsaPublic = new RSACryptoServiceProvider(); rsaPublic.FromXmlString(publicKey); //數據加密 byte[] bytesPublic = rsaPublic.Encrypt(Encoding.UTF8.GetBytes(data), false); return Convert.ToBase64String(bytesPublic); //使用Base64將byte轉換爲string } catch (Exception ex) { return ""; } } /// <summary> /// RSA解密 /// </summary> /// <param name="data">待解密數據</param> /// <returns></returns> public static string RSADecrypt(string data) { try { //獲取公鑰 string privateKey = GetPrivateKey(); //建立RSA對象並載入私鑰 RSACryptoServiceProvider rsaPrivate = new RSACryptoServiceProvider(); rsaPrivate.FromXmlString(privateKey); //數據解密 byte[] bytesPrivate = rsaPrivate.Decrypt( Convert.FromBase64String(data), false);//使用Base64將string轉換爲byte return Encoding.UTF8.GetString(bytesPrivate); } catch (Exception ex) { return ""; } }
主調用方式
// RSA EncryptOperation.GenerateRASKey(); string 密文 = EncryptOperation.RSAEncrypt(明文); string 明文De = EncryptOperation.RSADecrypt(密文);
也能夠將密鑰對保存在 PublicKey.xml 和 PrivateKey.xml 文件中,PublicKey.xml 中只包含公鑰,PrivateKey.xml 中包含公鑰和密鑰。密鑰的獲取經過讀取xml文件實現。
//將密鑰寫入指定路徑 File.WriteAllText(privateKeyPath, privateKey); File.WriteAllText(publicKeyPath, publicKey); //獲取密鑰對 string publicKey = File.ReadAllText(publicKeyPath); string privateKey = File.ReadAllText(privateKeyPath);
在實際中,可能會遇到如下幾個比較特殊的問題:
(1)私鑰加密、公鑰解密的要求
在C#中,支持公鑰加密私鑰解密,但不能逆向使用(Java是能夠的)。
若是有該特殊需求,可經過第三方的加解密組件 BouncyCastle 來實現:
具體參見:利用第三方dll實現私鑰做RSA加密 和 C#使用BouncyCastle來實現私鑰加密,公鑰解密的方法
(2)C# 的RSA公鑰是XML,如何與 Java 的RSA公鑰匹配
涉及公鑰和私鑰在C#和Java之間的相互轉換,共 4 個方法:
using System; using System.Collections.Generic; using System.Text; using System; using System.Xml; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; /// <summary> /// RSA密鑰格式轉換 /// </summary> public class RSAKeyConvert { /// <summary> /// RSA私鑰格式轉換,java->.net /// </summary> /// <param name="privateKey">java生成的RSA私鑰</param> /// <returns></returns> public static string RSAPrivateKeyJava2DotNet(string privateKey) { var privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey)); return string.Format( "<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>", Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned())); } /// <summary> /// RSA私鑰格式轉換,.net->java /// </summary> /// <param name="privateKey">.net生成的私鑰</param> /// <returns></returns> public static string RSAPrivateKeyDotNet2Java(string privateKey) { XmlDocument doc = new XmlDocument(); doc.LoadXml(privateKey); BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText)); BigInteger exp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText)); BigInteger d = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("D")[0].InnerText)); BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("P")[0].InnerText)); BigInteger q = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Q")[0].InnerText)); BigInteger dp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DP")[0].InnerText)); BigInteger dq = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DQ")[0].InnerText)); BigInteger qinv = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("InverseQ")[0].InnerText)); RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(m, exp, d, p, q, dp, dq, qinv); PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam); byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetEncoded(); return Convert.ToBase64String(serializedPrivateBytes); } /// <summary> /// RSA公鑰格式轉換,java->.net /// </summary> /// <param name="publicKey">java生成的公鑰</param> /// <returns></returns> public static string RSAPublicKeyJava2DotNet(string publicKey) { RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey)); return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>", Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()), Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned())); } /// <summary> /// RSA公鑰格式轉換,.net->java /// </summary> /// <param name="publicKey">.net生成的公鑰</param> /// <returns></returns> public static string RSAPublicKeyDotNet2Java(string publicKey) { XmlDocument doc = new XmlDocument(); doc.LoadXml(publicKey); BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText)); BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText)); RsaKeyParameters pub = new RsaKeyParameters(false, m, p); SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub); byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded(); return Convert.ToBase64String(serializedPublicBytes); } }
具體參見:C#的RSA密鑰XML格式與Java的其密鑰格式匹配
擴展應用參見:SHA256WithRSA
下面擴展學習數字簽名和數字證書、以及與上述加密算法之間的關係
數字簽名
Digital Signature,公鑰數字簽名/電子簽章,私鑰簽名、公鑰驗證簽名。
數字證書
Certificate Authority,數字證書是一個經證書受權中心數字簽名的包含公開密鑰擁有者信息以及公開密鑰的文件。
最簡單的證書包含一個公開密鑰、名稱以及證書受權中心的數字簽名。
關係理解