1. 程式人生 > 實用技巧 >AES位元組陣列加密解密流程

AES位元組陣列加密解密流程

AES類時微軟MSDN中最常用的加密類,微軟官網也有例子,參考連結:https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.aes?view=netframework-4.8
但是這個例子並不好用,限制太多,通用性差,實際使用中,我遇到的更多情況需要是這樣:
1、輸入一個位元組陣列,經AES加密後,直接輸出加密後的位元組陣列。
2、輸入一個加密後的位元組陣列,經AES解密後,直接輸出原位元組陣列。

對於我這個十八流業餘愛好者來說,AES我是以用為主的,所以具體的AES是怎麼運算的,我其實並不關心,我更關心的是AES的處理流程。結果恰恰這一方面,網上的資訊差強人意,看了網上不少的帖子,但感覺都沒有說完整說透,而且很多帖子有錯誤。

因此,我自己繪製了一張此種方式下的流程圖:

按照此流程圖進行了核心程式碼的編寫,驗證方法AesCoreSingleTest既是依照此流程的產物,例項化類AesCoreSingle後呼叫此方法即可驗證。

至於類中的非同步方法EnOrDecryptFileAsync,則是專門用於檔案加解密的處理,此非同步方法參考自《C# 6.0學習筆記》(周家安 著)最後的示例,這本書寫得真棒。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.IO;
  4 using System.Linq;
  5 using
System.Security.Cryptography; 6 using System.Text; 7 using System.Threading; 8 using System.Threading.Tasks; 9 using System.Windows; 10 11 namespace AesSingleFile 12 { 13 class AesCoreSingle 14 { 15 /// <summary> 16 /// 使用使用者口令,生成符合AES標準的key和iv。 17 /// </summary>
18 /// <param name="password">使用者輸入的口令</param> 19 /// <returns>返回包含金鑰和向量的元組</returns> 20 private (byte[] Key, byte[] IV) GenerateKeyAndIV(string password) 21 { 22 byte[] key = new byte[32]; 23 byte[] iv = new byte[16]; 24 byte[] hash = default; 25 if (string.IsNullOrWhiteSpace(password)) 26 throw new ArgumentException("必須輸入口令!"); 27 using (SHA384 sha = SHA384.Create()) 28 { 29 byte[] buffer = Encoding.UTF8.GetBytes(password); 30 hash = sha.ComputeHash(buffer); 31 } 32 //用SHA384的原因:生成的384位雜湊值正好被分成兩段使用。(32+16)*8=384。 33 Array.Copy(hash, 0, key, 0, 32);//生成256位金鑰(32*8=256) 34 Array.Copy(hash, 32, iv, 0, 16);//生成128位向量(16*8=128) 35 return (Key: key, IV: iv); 36 } 37 38 public byte[] EncryptByte(byte[] buffer, string password) 39 { 40 byte[] encrypted; 41 using (Aes aes = Aes.Create()) 42 { 43 //設定金鑰和向量 44 (aes.Key, aes.IV) = GenerateKeyAndIV(password); 45 //設定運算模式和填充模式 46 aes.Mode = CipherMode.CBC;//預設 47 aes.Padding = PaddingMode.PKCS7;//預設 48 //建立加密器物件(加解密方法不同處僅僅這一句話) 49 var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); 50 using (MemoryStream msEncrypt = new MemoryStream()) 51 { 52 using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))//選擇Write模式 53 { 54 csEncrypt.Write(buffer, 0, buffer.Length);//對原陣列加密並寫入流中 55 csEncrypt.FlushFinalBlock();//使用Write模式需要此句,但Read模式必須要有。 56 encrypted = msEncrypt.ToArray();//從流中寫入陣列(加密之後,陣列變長,詳見方法AesCoreSingleTest內容) 57 } 58 } 59 } 60 return encrypted; 61 } 62 public byte[] DecryptByte(byte[] buffer, string password) 63 { 64 byte[] decrypted; 65 using (Aes aes = Aes.Create()) 66 { 67 //設定金鑰和向量 68 (aes.Key, aes.IV) = GenerateKeyAndIV(password); 69 //設定運算模式和填充模式 70 aes.Mode = CipherMode.CBC;//預設 71 aes.Padding = PaddingMode.PKCS7;//預設 72 //建立解密器物件(加解密方法不同處僅僅這一句話) 73 var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); 74 using (MemoryStream msDecrypt = new MemoryStream(buffer)) 75 { 76 using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))//選擇Read模式 77 { 78 byte[] buffer_T = new byte[buffer.Length];/*--s1:建立臨時陣列,用於包含可用位元組+無用位元組--*/ 79 80 int i = csDecrypt.Read(buffer_T, 0, buffer.Length);/*--s2:對加密陣列進行解密,並通過i確定實際多少位元組可用--*/ 81 82 //csDecrypt.FlushFinalBlock();//使用Read模式不能有此句,但write模式必須要有。 83 84 decrypted = new byte[i];/*--s3:建立只容納可用位元組的陣列--*/ 85 86 Array.Copy(buffer_T, 0, decrypted, 0, i);/*--s4:從bufferT拷貝出可用位元組到decrypted--*/ 87 } 88 } 89 return decrypted; 90 } 91 } 92 public byte[] EnOrDecryptByte(byte[] buffer, string password, ActionDirection direction) 93 { 94 if (buffer == null) 95 throw new ArgumentNullException("buffer為空"); 96 if (password == null || password == "") 97 throw new ArgumentNullException("password為空"); 98 if (direction == ActionDirection.EnCrypt) 99 return EncryptByte(buffer, password); 100 else 101 return DecryptByte(buffer, password); 102 } 103 public enum ActionDirection//該列舉說明是加密還是解密 104 { 105 EnCrypt,//加密 106 DeCrypt//解密 107 } 108 public static void AesCoreSingleTest(string s_in, string password)//驗證加密解密模組正確性方法 109 { 110 byte[] buffer = Encoding.UTF8.GetBytes(s_in); 111 AesCoreSingle aesCore = new AesCoreSingle(); 112 byte[] buffer_ed = aesCore.EncryptByte(buffer, password); 113 byte[] buffer_ed2 = aesCore.DecryptByte(buffer_ed, password); 114 string s = Encoding.UTF8.GetString(buffer_ed2); 115 string s2 = "下列字串\n" + s + '\n' + $"原buffer長度 → {buffer.Length}, 加密後buffer_ed長度 → {buffer_ed.Length}, 解密後buffer_ed2長度 → {buffer_ed2.Length}"; 116 MessageBox.Show(s2); 117 /* 字串在加密前後的變化(預設CipherMode.CBC運算模式, PaddingMode.PKCS7填充模式) 118 * 1、如果陣列長度為16的倍數,則加密後的陣列長度=原長度+16 119 如對於下列字串 120 D:\User\Documents\Administrator - DOpus Config - 2020-06-301.ocb 121 使用UTF8編碼轉化為位元組陣列後, 122 原buffer → 64, 加密後buffer_ed → 80, 解密後buffer_ed2 → 64 123 * 2、如果陣列長度不為16的倍數,則加密後的陣列長度=16倍數向上取整 124 如對於下列字串 125 D:\User\Documents\cc_20200630_113921.reg 126 使用UTF8編碼轉化為位元組陣列後 127 原buffer → 40, 加密後buffer_ed → 48, 解密後buffer_ed2 → 40 128 參考文獻: 129 1-《AES補位填充PaddingMode.Zeros模式》http://blog.chinaunix.net/uid-29641438-id-5786927.html 130 2-《關於PKCS5Padding與PKCS7Padding的區別》https://www.cnblogs.com/midea0978/articles/1437257.html 131 3-《AES-128 ECB 加密有感》http://blog.sina.com.cn/s/blog_60cf051301015orf.html 132 */ 133 } 134 135 /***---宣告CancellationTokenSource物件--***/ 136 private CancellationTokenSource cts;//using System.Threading;引用 137 public Task EnOrDecryptFileAsync(Stream inStream, long inStream_Seek, Stream outStream, long outStream_Seek, string password, ActionDirection direction, IProgress<int> progress) 138 { 139 /***---例項化CancellationTokenSource物件--***/ 140 cts?.Dispose();//cts為空,不動作,cts不為空,執行Dispose。 141 cts = new CancellationTokenSource(); 142 143 Task mytask = new Task( 144 () => 145 { 146 EnOrDecryptFile(inStream, inStream_Seek, outStream, outStream_Seek, password, direction, progress); 147 }, cts.Token, TaskCreationOptions.LongRunning); 148 mytask.Start(); 149 return mytask; 150 } 151 public void EnOrDecryptFile(Stream inStream, long inStream_Seek, Stream outStream, long outStream_Seek, string password, ActionDirection direction, IProgress<int> progress) 152 { 153 if (inStream == null || outStream == null) 154 throw new ArgumentException("輸入流與輸出流是必須的"); 155 //--調整流的位置(通常是為了避開檔案頭部分) 156 inStream.Seek(inStream_Seek, SeekOrigin.Begin); 157 outStream.Seek(outStream_Seek, SeekOrigin.Begin); 158 //用於記錄處理進度 159 long total_Length = inStream.Length - inStream_Seek; 160 long totalread_Length = 0; 161 //初始化報告進度 162 progress.Report(0); 163 164 using (Aes aes = Aes.Create()) 165 { 166 //設定金鑰和向量 167 (aes.Key, aes.IV) = GenerateKeyAndIV(password); 168 //建立加密器解密器物件(加解密方法不同處僅僅這一句話) 169 ICryptoTransform cryptor; 170 if (direction == ActionDirection.EnCrypt) 171 cryptor = aes.CreateEncryptor(aes.Key, aes.IV); 172 else 173 cryptor = aes.CreateDecryptor(aes.Key, aes.IV); 174 using (CryptoStream cstream = new CryptoStream(outStream, cryptor, CryptoStreamMode.Write)) 175 { 176 byte[] buffer = new byte[512 * 1024];//每次讀取512kb的資料 177 int readLen = 0; 178 while ((readLen = inStream.Read(buffer, 0, buffer.Length)) != 0) 179 { 180 // 向加密流寫入資料 181 cstream.Write(buffer, 0, readLen); 182 totalread_Length += readLen; 183 //彙報處理進度 184 if (progress != null) 185 { 186 long per = 100 * totalread_Length / total_Length; 187 progress.Report(Convert.ToInt32(per)); 188 } 189 } 190 } 191 } 192 } 193 } 194 }