[C#.NET] DES / AES 字串及檔案 加解密
維基對這兩種演算法的說明
http://zh.wikipedia.org/zh-hant/%E8%B3%87%E6%96%99%E5%8A%A0%E5%AF%86%E6%A8%99%E6%BA%96
在.NET裡
DES 演算法會用到 DSACryptoServiceProvider 類別
AES 演算法
2.0:RijndaelManaged 類別 (AES又叫Rijndael)
3.5:除了原本的 RijndaelManaged 類別 還多了 AesCryptoServiceProvider 類別,這兩個類別產生的結果相同。
AES與DES這兩種加密演算法都需要KEY與IV,AES/DES類別都有以下兩種屬性,必需要依照規則丟固定長度的Bit
- LegalBlockSizes是表示IV的容許長度,單位Bit
- LegalKeySizes表示KEY的容許長度,單位Bit
為了方便講解,我整理出下表:
PS.1 Byte=8 Bit, 所以 128 Bit = 16 Byte, 256 Bit = 32 Byte
所以我們在餵KEY跟IV的時候,就會有長度限制的問題要注意
接下來我們來看範例
下段比較值得注意的是編碼的部份,
1.下段是用Encoding.UTF8.GetBytes(source)來取得原始資料的Byte,所以解密的時候也要用Encoding.UTF8
2.另外加密後的Byte Convert String是用Convert.ToBase64String所以解密的時候也要用相對應的方式來處理
總之,要對應就對了
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("12345678");
byte[] iv = Encoding.ASCII.GetBytes("87654321");
byte[] dataByteArray = Encoding.UTF8.GetBytes(source);
des.Key = key;
des.IV = iv;
string encrypt = "";
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(dataByteArray, 0, dataByteArray.Length);
cs.FlushFinalBlock();
encrypt = Convert.ToBase64String(ms.ToArray());
}
return encrypt;
}
private string desDecryptBase64(string encrypt)
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("12345678");
byte[] iv = Encoding.ASCII.GetBytes("87654321");
des.Key = key;
des.IV = iv;
byte[] dataByteArray = Convert.FromBase64String(encrypt);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(dataByteArray, 0, dataByteArray.Length);
cs.FlushFinalBlock();
return Encoding.UTF8.GetString(ms.ToArray());
}
}
}
執行結果如下:
再來看另外一資資料格式輸出,加密過程都一樣,只是資料產出的方式不一樣,也就是說,你可以依個人喜好選用不同的資料輸出樣式,參考之前寫過的,http://www.dotblogs.com.tw/yc421206/archive/2009/08/11/9984.aspx
{
StringBuilder sb = new StringBuilder();
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("12345678");
byte[] iv = Encoding.ASCII.GetBytes("87654321");
byte[] dataByteArray = Encoding.UTF8.GetBytes(source);
des.Key = key;
des.IV = iv;
string encrypt = "";
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(dataByteArray, 0, dataByteArray.Length);
cs.FlushFinalBlock();
//輸出資料
foreach (byte b in ms.ToArray())
{
sb.AppendFormat("{0:X2}", b);
}
encrypt = sb.ToString();
}
return encrypt;
}
private string desDecrypt(string encrypt)
{
byte[] dataByteArray = new byte[encrypt.Length / 2];
for (int x = 0; x < encrypt.Length / 2; x++)
{
int i = (Convert.ToInt32(encrypt.Substring(x * 2, 2), 16));
dataByteArray[x] = (byte)i;
}
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("12345678");
byte[] iv = Encoding.ASCII.GetBytes("87654321");
des.Key = key;
des.IV = iv;
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(dataByteArray, 0, dataByteArray.Length);
cs.FlushFinalBlock();
return Encoding.UTF8.GetString(ms.ToArray());
}
}
加密字串跟Base64有很大的不同
接下來看看怎麼處理檔案,先準備好要處理的檔案
{
if (string.IsNullOrEmpty(sourceFile) || string.IsNullOrEmpty(encryptFile))
{
return;
}
if (!File.Exists(sourceFile))
{
return;
}
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("12345678");
byte[] iv = Encoding.ASCII.GetBytes("87654321");
des.Key = key;
des.IV = iv;
using (FileStream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
using (FileStream encryptStream = new FileStream(encryptFile, FileMode.Create, FileAccess.Write))
{
//檔案加密
byte[] dataByteArray = new byte[sourceStream.Length];
sourceStream.Read(dataByteArray, 0, dataByteArray.Length);
using (CryptoStream cs = new CryptoStream(encryptStream, des.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(dataByteArray, 0, dataByteArray.Length);
cs.FlushFinalBlock();
}
}
}
private void desDecryptFile(string encryptFile, string decryptFile)
{
if (string.IsNullOrEmpty(encryptFile) || string.IsNullOrEmpty(decryptFile))
{
return;
}
if (!File.Exists(encryptFile))
{
return;
}
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] key = Encoding.ASCII.GetBytes("12345678");
byte[] iv = Encoding.ASCII.GetBytes("87654321");
des.Key = key;
des.IV = iv;
using (FileStream encryptStream = new FileStream(encryptFile, FileMode.Open, FileAccess.Read))
using (FileStream decryptStream = new FileStream(decryptFile, FileMode.Create, FileAccess.Write))
{
byte[] dataByteArray = new byte[encryptStream.Length];
encryptStream.Read(dataByteArray, 0, dataByteArray.Length);
using (CryptoStream cs = new CryptoStream(decryptStream, des.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(dataByteArray, 0, dataByteArray.Length);
cs.FlushFinalBlock();
}
}
}
檔案加密的效果
解密後的效果,基本上要跟source.txt一樣
若你看到這裡還不太懂,只要記住一點不管加解密什麼都是處理Byte就對了。
Q:加密的KEY或IV能用中文嗎?
A:當然可以呀,只要能符合長度就可以
Q:每次都要計算byte的長度,很煩人,怎麼辦?
A:可以利用以下類別產生符合規格的Byte
Rfc2898DeriveBytes 類別 / PasswordDeriveBytes 類別:不管你打再長的密碼都會經過 Salt 亂數處理,以符合規則
{
byte[] salt = new byte[] { 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xF1 };
byte[] key = Encoding.UTF8.GetBytes("我跟你說你不要跟別人說,你若跟別人說,不要跟別人說是我叫你不要跟別人說");
Rfc2898DeriveBytes rfcKey = new Rfc2898DeriveBytes(key, salt, 8);
Rfc2898DeriveBytes rfcIv = new Rfc2898DeriveBytes("0987654321", salt, 8);
byte[] keyData = rfcKey.GetBytes(8);
byte[] IVData = rfcIv.GetBytes(8);
}
RNGCryptoServiceProvider 類別:用它來幫你產生符合規則的長度
{
byte[] randBytes;
if (length >= 1)
{
randBytes = new byte[length];
}
else
{
randBytes = new byte[8];
}
RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
rand.GetBytes(randBytes);
return randBytes;
}
Q:為什麼沒有AES的範例?
A:AES的用法跟DES一模一樣,在這裡就不多佔篇幅,AES比DES支援的長度還要長,所以它有比較多的選擇來處理KEY/IV,比如
SHA256CryptoServiceProvider 類別:將資料雜湊後產生32 Byte的資料
MD5CryptoServiceProvider 類別:將資料雜湊後產生16 Byte的資料
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();
AesCryptoServiceProvider provider = new AesCryptoServiceProvider();
byte[] keyData = sha256.ComputeHash(Encoding.UTF8.GetBytes("我跟你說你不要跟別人說,你若跟別人說,不要跟別人說是我叫你不要跟別人說"));
byte[] IVData = md5.ComputeHash(Encoding.UTF8.GetBytes("我跟你說你不要跟別人說,你若跟別人說,不要跟別人說是我叫你不要跟別人說"));
provider.Key = keyData;
provider.IV = IVData;
}
這樣一來也就能處理掉長度的問題
Q:有更簡單的方法嗎?
A:當時可以,AES/DES初始化的時候就會自己產生KEY跟IV,只要將它產生的KEY/IV記下就好了
{
//var provider = new DESCryptoServiceProvider();
var provider = new AesCryptoServiceProvider();
var key = Convert.ToBase64String(provider.Key);
var iv = Convert.ToBase64String(provider.IV);
}
後記:
這篇主要是在介紹怎麼使用AES/DES,所以程式碼寫的很亂,重覆的地方相當的多,瞭解了AES跟DES的來龍去脈後,就能寫出屬於自己的加解密類別,當然,這部份重構就得靠自己來了。
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET