2013年11月6日 星期三

[C#] Object Serialization RSA Encrypt & Decrypt 物件序列化加解密 (下)

物件序列化加解密(上)已經將Object 做完序列化加密的動作,接著接收端的程式要將加密的資料先進行解密的動作,在做反序列化,即可還原 Object 繼續使用。

        /// 將加密的的資料,解密後還原 Object 原Class
        /// Class type
        /// 序列化加密資料
        /// private key
        public static T DecryptObject(byte[] EncryptData, string privkey)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                var decrypt = RSADecrypt(EncryptData, privkey);
                memory.Write(decrypt, 0, decrypt.Length);
                memory.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(memory);
            }
        }

接著 DecryptObject  用來將加密的資料解密後,反序列換並且轉成原本 Object Classs Type 的 Mehtod 。最後有呼叫範例提供給各位參考。

其中 RSADecrypt 為 RSA 解密的 Mehtod。

 public static byte[] RSADecrypt(byte[] DataToDecrypt, string privkey)
        {
            try
            {
                byte[] decryptedData;
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    RSA.FromXmlString(privkey);
                    int DecryptedSize = 0;
                    if (DataToDecrypt.Length > 128)
                    {
                        ArrayList arrDecrypteToTxt = new ArrayList();
                        decryptedData = DecryptedDataMethod(RSA, DataToDecrypt, arrDecrypteToTxt, DecryptedSize);
                    }
                    else
                    {
                        decryptedData = RSA.Decrypt(DataToDecrypt, false);
                    }
                }
                return decryptedData;
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.ToString());

                return null;
            }

        }
因為解密也有Size的問題,所以 array size 超過某一定大小要分段解密。

 
        /// RSA分段解密
        /// RSA物件
        /// 要解密的位元組
        /// 存放每組解密後的字串陣列
        /// 存放解密後的位元組資料長度
        private static byte[] DecryptedDataMethod(RSACryptoServiceProvider rsa, byte[] orgData, ArrayList arrDecrypteToTxt, int DecryptedSize)
        {
            byte[] DecryptedData = null;
            try
            {
                //解密後位元組再轉成64位數編碼的字串
                string strDecrypteToTxt = "";
                //暫存要解密的位元組容器
                byte[] temp = null;
                //解密後的位元組
                byte[] tempDecryptedData = null;
                //若要解密的資料位元長度>128
                if (orgData.Length > 128)
                {
                    temp = new byte[128];
                    //再把傳進來的位元組從0-128跑迴圈,一個一個賦予temp每個位元值
                    for (int i = 0; i < 128; i++)
                    {
                        temp[i] = orgData[i];
                    }
                }
                else
                {
                    temp = orgData;
                }
                //tempDecryptedData存放解密後的位元組資料
                tempDecryptedData = rsa.Decrypt(temp, false);
                //解密後位元組資料長度
                DecryptedSize += tempDecryptedData.Length;
                //再把tempDecryptedData位元組資料轉成64編碼字串
                strDecrypteToTxt = Convert.ToBase64String(tempDecryptedData);
                //把字串add到陣列裡
                arrDecrypteToTxt.Add(strDecrypteToTxt);

                //再判斷一次傳進來要解密的位元組資料長度
                if (orgData.Length > 128)
                {
                    //again存放要解密的位元組資料長度-128
                    byte[] again = new byte[orgData.Length - 128];
                    int j = 0;
                    //傳進來要解密的位元組資料從128位址開始跑迴圈
                    for (int i = 128; i < orgData.Length; i++)
                    {
                        //把傳進來要解密的位元組資料指派給again
                        again[j] = orgData[i];
                        j++;
                    }
                    //遞回呼叫DecryptedDataMethod函式,傳入rsa,要再解密位元組資料,存放每組解密資料轉成64編碼的字串陣列
                    return DecryptedDataMethod(rsa, again, arrDecrypteToTxt, DecryptedSize);
                }
                else
                {
                    //若傳進來要解密的位元組長度小於128,代表不用遞回解密了
                    //建立一個新的位元組物件,長度是(存放每組解密資料轉成64編碼的字串陣列的位元數)*117, 117是代表解密出來的位元組長度,是固定的
                    //DecryptedData = new byte[arrDecrypteToTxt.Count * 117];
                    DecryptedData = new byte[DecryptedSize];
                    //宣告解密位元組,預設null
                    Byte[] btFromBase64 = null;


                    int p = 1;//計算目前位置倍數
                    int currentAddr = 0;//目前存放資料的位元位置
                    int k = 0;
                    double dk = 0;
                    dk = Math.Floor((double)(DecryptedSize / 117));//解密後位元組資料長度/117,無條件捨去求整數部分
                    k = (int)dk;
                    int n = 0;//與k比較用
                    //跑陣列回圈,每個陣列位元存放每個分段加密後的64編碼字串
                    foreach (string strArr in arrDecrypteToTxt)
                    {
                        if (n < k)
                        {
                            //再把字串從64位元編碼轉換為8位元的位元組
                            btFromBase64 = Convert.FromBase64String(strArr);
                            //將位元組資料複製到目的地位元組陣列eDecryptedData,並指定從目的地陣列n*117的位置貼上
                            btFromBase64.CopyTo(DecryptedData, n * 117);
                            currentAddr = p * 117;//目前資料已存到的位置
                            p++;
                            n++;
                        }
                        else
                        {
                            //再把字串從64位元編碼轉換為8位元的位元組
                            btFromBase64 = Convert.FromBase64String(strArr);
                            //將位元組資料複製到目的地位元組陣列eDecryptedData,並指定從目的地陣列currentAddr的位置貼上
                            btFromBase64.CopyTo(DecryptedData, currentAddr);
                        }

                    }
                }

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            //最後回傳分段加密資料的位元組
            return DecryptedData;
            //The End
        }
 
分享分段加解密的網友很貼心了下了註解XD

有了以上的程式,我們就可以把整個 Object 序列化->加密 ->解密->反序列化 的情境完成。
以下有一個範例程式,就那上篇的 Person Class 做示範。

 
static void Main(string[] args)
        {
            //生產 RSA Key
            CspParameters csp = new CspParameters();
            csp.KeyContainerName = "FredJama";
            csp.Flags = CspProviderFlags.UseMachineKeyStore;
            csp.ProviderType = 1;
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024, csp);
            string PrivateKey = rsa.ToXmlString(true);
            string PublicKey = rsa.ToXmlString(false);
            
            Person person = new Person("Fred", "DJ", "Jama");
            byte[] encryptDate = null;
            Person decryptPerson = null;
            Console.WriteLine("原始內容"+person.ToString());

            //序列化加密
            encryptDate = EncryptObject(person, PublicKey);
            string encrypdateString = ByteListToString(encryptDate);
            Console.WriteLine("加密內容:"+ encrypdateString);

            //解密後反序列化
            decryptPerson = DecryptObject(encryptDate, PrivateKey);
            Console.WriteLine("解密內容:" + decryptPerson.ToString());
            Console.ReadLine();
        }

此段測試程式前半段是簡單生出一個 rsa key 用來加解密。執行結果如下圖:



參考資料:

http://www.dotblogs.com.tw/yc421206/archive/2012/06/25/73041.aspx
http://www.dotblogs.com.tw/yc421206/archive/2012/06/26/73057.aspx
http://www.dotblogs.com.tw/yc421206/archive/2012/06/25/73041.aspx
RSA 分段加密
http://www.wretch.cc/blog/yanan222/12339145

沒有留言:

張貼留言