1. 程式人生 > >C#檔案加密和解密

C#檔案加密和解密

//

由於專案需要,要對檔案進行加密操作,所以對這個功能做了一點學習。不難,需要注意的地方也不多:

  1. 加密演算法和加密後的長度問題(這裡用的AES);
  2. 加密大檔案使用流的方式一點一點加密(防止一次性吃掉很多記憶體);
  3. 加密過程的回撥;
  4. 檔案結構;

截圖

加密:
加密

解密:
解密

選擇檔案:
選擇檔案

記憶體及CPU的使用:
記憶體及CPU的使用

一、演算法

首先,檔案要加密就一定要解密,所以一定要找個能解密的演算法,這個不用多說……其實加密解密演算法也算是C#裡自帶了,程式碼如下:

/// <summary>
/// 加密
/// </summary>
/// <param name="array">
要加密的 byte[] 陣列</param>
/// <param name="key"></param> /// <returns></returns> public static byte[] Encrypt(byte[] array, string key) { key = FmtPassword(key); byte[] keyArray = Encoding.UTF8.GetBytes(key); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateEncryptor(); byte
[] resultArray = cTransform.TransformFinalBlock(array, 0, array.Length); return resultArray; } /// <summary> /// 解密 /// </summary> /// <param name="array">要解密的 byte[] 陣列</param> /// <param name="key"></param> /// <returns></returns> public static byte[] Decrypt(byte[] array, string
key) { key = FmtPassword(key); byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateDecryptor(); byte[] resultArray = cTransform.TransformFinalBlock(array, 0, array.Length); return resultArray; }


所以需要自己把不夠長度的密碼填充一下(超出長度的也需要處理一下),避免出現這種錯誤

二、流

檔案加密的功能網上有很多,我發現有很多直接把檔案全部讀取出來,然後加密整個檔案。這種方法在處理大檔案會消耗非常大的記憶體,所以需要注意不能一次性把檔案全加密。

注1: 關於加密的部分,可以只加密檔案的一部分資料,這樣可以保證加密速度。

注2: 使用流讀取部分,迴圈加密的時候,AES加密後的內容長度會增加,比如要加密一個 byte[1024] 大小的內容,加密後會變成 byte[1024+16] ,這一點可以自己試一試。

三、進度

進度方面是很有必要的,在加密大檔案的時候,如果光等著沒有進度也顯得很尷尬,所以做一個委託,來把進度回調出來。

// 定義
public delegate void ProgressHandler(object sender, ProgressEventArgs e);

// 使用
progress?.Invoke(sender, new ProgressEventArgs(fsRead.Position, fsRead.Length));

四、檔案結構

加密檔案後的檔案結構是要自己重新做定義的,使自己的程式能讀懂,並且能成功解密即可。這裡貼一個檔案加密的方法:
裡面有很多我自己寫的方法,下面會給連結地址

/// <summary>
/// 檔案加密
/// </summary>
/// <param name="srcFile">原始檔</param>
/// <param name="dstFile">目標檔案</param>
/// <param name="password">加密密碼</param>
/// <param name="progress">回撥進度</param>
/// <param name="overwrite">是否覆蓋已有目標檔案</param>
/// <returns>
/// >0:操作成功(操作共計秒數)
/// -11:要加密的檔案不存在
/// -12:加密後的目標檔案已存在
/// -404:未知錯誤,操作失敗
/// </returns>
public static int Encrypt(string srcFile, string dstFile, string password, ProgressDelegate.ProgressHandler progress = null, object sender = null, bool overwrite = true)
{
    DateTime beginTime = DateTime.Now;
    if (!File.Exists(srcFile)) return -11; //要加密的檔案不存在
    if (File.Exists(dstFile) && !overwrite) return -12;//加密後的目標檔案已存在

    string fmtPwd = AesTool.FmtPassword(password);
    string pwdMd5 = MD5Tool.Encrypt(MD5Tool.Encrypt(fmtPwd));

    string md5 = FileTool.GetMD5(srcFile);
    using (FileStream fsRead = new FileStream(srcFile, FileMode.Open))
    {
        using (FileStream fsWrite = new FileStream(dstFile, FileMode.Create))
        {
            try
            {
                //寫入檔案型別標識和版本號
                byte[] filetypeandversion = Encoding.Default.GetBytes(FileType + FileVersion);
                fsWrite.Write(filetypeandversion, 0, filetypeandversion.Length);

                //檔案頭部資料定義
                List<byte[]> headdata = new List<byte[]>()
                    {
                        Encoding.Default.GetBytes(FileType),
                        Encoding.Default.GetBytes(md5),
                        Encoding.Default.GetBytes(AesTool.Encrypt(fmtPwd,AesTool.DefaultPassword)),
                        Encoding.Default.GetBytes(pwdMd5),
                        Encoding.Default.GetBytes(DateTime.Now.ToString())
                    };
                //寫入頭部資訊個數
                byte[] count = BitConverter.GetBytes(headdata.Count);
                fsWrite.Write(count, 0, count.Length);
                //寫入各部分長度
                for (int i = 0; i < headdata.Count; i++)
                {
                    byte[] length = BitConverter.GetBytes(headdata[i].Length);
                    fsWrite.Write(length, 0, length.Length);
                }
                //寫入各部分資料
                for (int i = 0; i < headdata.Count; i++)
                {
                    fsWrite.Write(headdata[i], 0, headdata[i].Length);
                }

                //寫入檔案源資料
                int readCount = 0;
                byte[] buffer = new byte[FileBuffer];
                while ((readCount = fsRead.Read(buffer, 0, buffer.Length)) > 0)
                {
                    if (readCount != buffer.Length)
                    {
                        byte[] temp = new byte[readCount];
                        Buffer.BlockCopy(buffer, 0, temp, 0, readCount);
                        byte[] enbyte = AesTool.Encrypt(temp, fmtPwd);
                        fsWrite.Write(enbyte, 0, enbyte.Length);
                    }
                    else
                    {
                        byte[] enbyte = AesTool.Encrypt(buffer, fmtPwd);
                        fsWrite.Write(enbyte, 0, enbyte.Length);
                    }
                    progress?.Invoke(sender, new ProgressEventArgs(fsRead.Position, fsRead.Length));
                }
                return (int)Math.Ceiling((DateTime.Now - beginTime).TotalSeconds);//操作成功
            }
            catch (Exception e) { }
        }
        //加密失敗後,刪除加密的檔案
        try { File.Delete(dstFile); } catch (Exception e) { }
    }
    return -404;//未知錯誤,操作失敗
}

五、原始碼

下載
或檢視 GitHub 中Oreo.FileMan