2013年11月6日 星期三

[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 順利序列化機加了。

沒有留言:

張貼留言