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

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

        行動裝置越來越普及,透過網路傳輸資料會越來越普遍,因此傳輸資料的保密性也是很重要的議題。在 OO 的概念下,將傳輸的資料寫成 Class ,傳遞 Object 已經非常普及,但要如何讓攔截你傳輸資料的人,無法很簡單的猜測你傳遞資料的 資料結構? 最常見的作法就是將 Object 序列化(Serialization ),在對序列化的資料做一層加密的保護,如此要解析傳遞資料內容就不是那麼容易了。

         而小弟最近剛好也類似情境的案例,將序列化加解密的技術整理整理分享給大家。其中加密的方法是使用 .NET提供了 RSACryptoServiceProvider 類別,做簡單的加解密動作。

首先我們先準備一個準備被序列化的 Person Class ,Person 內涵 firstName、MiddleName、LastName 三個屬性。其程式碼如下:



   [Serializable]
    public class Person
    {
        public string FirstName { get; set; }
        public string MiddleName{ get; set; }
        public string LastName { get; set; }

        public Person(string first, string middle, string last)
        {
            this.FirstName = first;
            this.MiddleName = middle;
            this.LastName = last;
        }

        public override string ToString() 
        {
            return "Person:" + FirstName + "," + MiddleName + "," + LastName;
        }
    }

其中 Class 上方要 [Serializable] 的 tag ,註明此 Class 的 Object 可以被序列化。否則在序列化過程會有 Exception 出現。

接著我們進入序列化加密的主題。流程為先將 Person Object 序列化成 Byte[],接著對  Byte[] 做 RSA 加密動作,己完成動作。

        
        ///         /// Object  序列化後 做 RSA 加密
        ///         /// 欲加密的Object 
        /// RSA public key
        /// 
        public static byte[] EncryptObject(object OriginalObject, string clientPubkey)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                //物件轉為Byte[]
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(memory, OriginalObject);
                memory.Seek(0, SeekOrigin.Begin);
                var encrypt = RSAEncrypt(memory.ToArray(), clientPubkey);//加密
                return encrypt;
            }
        }

EncryptObject 這個 Mehtod 傳入欲加密的 Object 以及 RSACryptoServiceProvider 生出的 public Key String,就會回傳加密的 byte[]。

其中 RSAEncrypt 的Method 程式如下:
        public static byte[] RSAEncrypt(byte[] DataToEncrypt, string clientPubkey)
        {
            try
            {
                byte[] encryptedData;
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    RSA.FromXmlString(clientPubkey);
                    ArrayList arrEncrypteToTxt = new ArrayList();
                    if (DataToEncrypt.Length > 117)
                    {
                        encryptedData = encryptedDataMethod(RSA, DataToEncrypt, arrEncrypteToTxt);
                    }
                    else
                    {
                        encryptedData = RSA.Encrypt(DataToEncrypt, false);
                    }
                }
                return encryptedData;
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return null;
            }

        }

RSAEncrypt method 主要為 RSA 加密的動作,但你應該發現這邊加密拆成兩個區塊,主要因為 RSA 加密的 method 有size的限制,若array size超過一定的值,內建的加密程式會產生 Exception,因此我上網找到分段加密的 mehtod encryptedDataMethod,可以解決 size太大的問題。


        /// RSA分段加密
        ///         /// RSA物件
        /// 要加密的位元組
        /// 存放每組加密後的字串陣列
        /// 組合後加密後的位元組
        private static byte[] encryptedDataMethod(RSACryptoServiceProvider rsa, byte[] orgData, ArrayList arrEncrypteToTxt)
        {
            //先宣告要回傳的位元組,預設值null
            byte[] encryptedData = null;

            try
            {
                //加密後位元組再轉成64位數編碼的字串
                string strEncrypteToTxt = "";
                //暫存要加密的位元組容器
                byte[] temp = null;
                //加密後的位元組
                byte[] tempEncrypedData = null;
                //若傳進來的資料位元組長度大於117
                if (orgData.Length > 117)
                {
                    //temp先預設長度為117
                    temp = new byte[117];
                    //再把傳進來的位元組從0-117跑迴圈,一個一個賦予temp每個位元值
                    for (int i = 0; i < 117; i++)
                    {
                        temp[i] = orgData[i];
                    }
                }
                else
                {
                    //若傳進來的資料位元組長度介於117之內,則直接指派給temp
                    temp = orgData;
                }
                //執行加密
                tempEncrypedData = rsa.Encrypt(temp, false);
                //將加密後的位元組再轉成64編碼的字串
                strEncrypteToTxt = Convert.ToBase64String(tempEncrypedData);
                //將64編碼的字串add到陣列
                arrEncrypteToTxt.Add(strEncrypteToTxt);

                //再判斷一次,若傳進來的資料位元組長度 > 117
                if (orgData.Length > 117)
                {
                    int j = 0;
                    //宣告新的資料位元組長度=傳進來的資料位元長度 - 117
                    byte[] again = new byte[orgData.Length - 117];
                    //傳進來的資料位元長度,跑迴圈,從117位址開始,一個一個賦予again每個位元值
                    for (int i = 117; i < orgData.Length; i++)
                    {
                        again[j] = orgData[i];
                        j++;
                    }
                    //遞迴呼叫,把rsa參數,新的資料位元組參數,存放每組資料的陣列當參數傳遞
                    return encryptedDataMethod(rsa, again, arrEncrypteToTxt);
                }
                else
                {
                    //若傳進來的資料位元組 < 117,表示資料已經可以不用分段
                    //此時傳進來的存放每組資料的陣列計算個數,再乘以128 (因一次加密最多只接受128位元組)
                    encryptedData = new byte[arrEncrypteToTxt.Count * 128];
                    //宣告加密位元組,預設null
                    Byte[] btFromBase64 = null;
                    int k = 0;
                    //跑陣列回圈,每個陣列位元存放每個分段加密後的64編碼字串
                    foreach (string strArr in arrEncrypteToTxt)
                    {
                        //再把字串從64位元編碼轉換為8位元的位元組
                        btFromBase64 = Convert.FromBase64String(strArr);
                        //將位元組資料複製到目的地位元組陣列encryptedData,並指定從目的地陣列k*128的位置貼上
                        btFromBase64.CopyTo(encryptedData, k * 128);
                        k++;
                    }

                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            //最後回傳分段加密資料的位元組
            return encryptedData;
            //The End
        }

這樣我們就可以完整的將任何的 Object 順利序列化機加了。