.Net Framework AES SymmetricAlgorithm Sample Code (include .Net Core)
前言
之前開發的Web網站被白箱工具掃出Parameter Tampering(參數竄改)的漏洞,直到最近去恆逸上CASE.Net課程才知道原來解法要對傳遞的關鍵參數先做加密,接收頁收到後做解密
然後拿解密後的值和資料庫的資料再比對驗證資料是否正確、完整。
我以前只有實作DB資料驗證,沒有加解密,難怪仍然被工具掃描出Parameter Tampering漏洞
※關鍵參數:例如訂單編號、折價卷序號,如果透過Web前端QueryString、表單hidden field來傳遞資料並且有新刪修動作至資料庫,參數有被駭客竄改的風險,而且一被竄改成功會造成商業上的損失(瞧瞧之前有人0元買到高鐵票的新聞),此時最好對關鍵參數加解密+接收端再和DB資料驗證確認資料完整性,不然就得把被敏感資料儲存在Session中傳遞,減少駭客接觸敏感資料的機率。
※至於有人會竄改QueryString來查詢、進入不該進入的畫面,這個則是驗證、授權相關問題
實作
本文程式碼適用.Net Framework 4.7.1以上、.Net Core 2.1.0以上
自己寫的類別 MyAesCryptography.cs ↓
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
/*引用這兩個命名空間*/
using System.Security.Cryptography;
using System.Text;
namespace Console_MyPrj
{
public class MyAesCryptography
{
/// <summary>
/// 驗證key和iv的長度(AES只有三種長度適用)
/// </summary>
/// <param name="key"></param>
/// <param name="iv"></param>
private static void Validate_KeyIV_Length(string key,string iv)
{
//驗證key和iv都必須為128bits或192bits或256bits
List<int> LegalSizes = new List<int>() { 128, 192, 256 };
int keyBitSize = Encoding.UTF8.GetBytes(key).Length * 8;
int ivBitSize = Encoding.UTF8.GetBytes(iv).Length * 8;
if (!LegalSizes.Contains(keyBitSize) || !LegalSizes.Contains(ivBitSize))
{
throw new Exception($@"key或iv的長度不在128bits、192bits、256bits其中一個,輸入的key bits:{keyBitSize},iv bits:{ivBitSize}");
}
}
/// <summary>
/// 加密後回傳base64String,相同明碼文字編碼後的base64String結果會相同(類似雜湊),除非變更key或iv
/// 如果key和iv忘記遺失的話,資料就解密不回來
/// base64String若使用在Url的話,Web端記得做UrlEncode
/// </summary>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <param name="plain_text"></param>
/// <returns></returns>
public static string Encrypt(string key,string iv,string plain_text)
{
Validate_KeyIV_Length(key,iv);
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;//非必須,但加了較安全
aes.Padding = PaddingMode.PKCS7;//非必須,但加了較安全
ICryptoTransform transform = aes.CreateEncryptor(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv));
byte[] bPlainText = Encoding.UTF8.GetBytes(plain_text);//明碼文字轉byte[]
byte[] outputData = transform.TransformFinalBlock(bPlainText, 0, bPlainText.Length);//加密
return Convert.ToBase64String(outputData);
}
/// <summary>
/// 解密後,回傳明碼文字
/// </summary>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <param name="base64String"></param>
/// <returns></returns>
public static string Decrypt(string key, string iv, string base64String)
{
Validate_KeyIV_Length(key,iv);
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;//非必須,但加了較安全
aes.Padding = PaddingMode.PKCS7;//非必須,但加了較安全
ICryptoTransform transform = aes.CreateDecryptor(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv));
byte[] bEnBase64String = null;
byte[] outputData = null;
try
{
bEnBase64String = Convert.FromBase64String(base64String);//有可能base64String格式錯誤
outputData = transform.TransformFinalBlock(bEnBase64String, 0, bEnBase64String.Length);//有可能解密出錯
}
catch (Exception ex)
{
//todo 寫Log
throw new Exception($@"解密出錯:{ex.Message}");
}
//解密成功
return Encoding.UTF8.GetString(outputData);
}
}
}
使用方式↓
using System;
using Console_MyPrj;
namespace ConsoleApp1PWDTest
{
class Program
{
/// <summary>
/// ↓自己決定,可以的話,最好使用 RNGCryptoServiceProvider 來產生key 和 IV
/// </summary>
private static string myKey = "1234567812345678";//必須至少16個字元,最大32個字元,因為使用AES演算法,可以的話,最好給32個字比較安全
private static string myIV = "1234567812345678";//必須至少16個字元,最大32個字元,因為使用AES演算法,可以的話,最好給32個字比較安全
static void Main(string[] args)
{
//原文
string plain_text = "Hello World!!";
//加密
string base64String = MyAesCryptography.Encrypt(myKey, myIV, plain_text);
Console.WriteLine($@"加密後:{base64String}");
//解密
string decrypt_text = MyAesCryptography.Decrypt(myKey, myIV, base64String);
Console.WriteLine($@"解密後:{decrypt_text}");
}
}
}
執行結果↓
※使用本文需留意
如果你每次加密的key與iv都是給相同值的話,那麼相同原始明文加密後產生出來的密文每次都會一樣
事實上在每次加密時,IV都要給不同值,讓每次加密後的密文值都不相同,這樣會比較安全
結語
會選擇對稱式演算法是因為加解密我只想固定同一把金鑰,然後對稱式演算法DES、RC2、Triple-DES、Rijndael、AES當中,又以AES演算法最強健
Rijndael是AES前身,由於KeySize和IV (BlockSize)長度關係,微軟官方建議使用AES替代Rijndael演算法,詳見→:Rijndael Class、The Differences Between Rijndael and AES
AES演算法使用方式參考微軟官網說明:Aes Class,本文程式碼我自認已經寫得很短很淺顯易懂XD
最後,本文只是個備忘錄,還沒經過白箱工具的考驗,說不定正式應用工作上又有其它安全性不足問題XD
2021.3.24 追記
加密v.s.雜湊 的不同
(和系統查詢比對資料有關↓)
相同原文在每次加密後,密文都不相同(密文無法比對)
相同原文在每次雜湊後,雜湊值都相同(雜湊值可以比對)=>會員登入密碼常使用這個
(和系統想要瀏覽資料有關↓)
加密需要金鑰,且可以透過解密得到原始明文。(加密可逆)
雜湊不需金鑰,無法逆向解出原始明文。(雜湊不可逆)
其它補充文章
亂數產生器:Random 與 RNGCryptoServiceProvider by Will保哥