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

2013年8月25日 星期日

[C#] XSD 驗證 XML 實作方式

XSD 為定義 XML 檔案格式的  Schema 文件,用來記載 XML 檔案的結構描述。網路傳輸 XML 資料很常見,因此驗證格式是否正確也是一塊很重要的原件。
C# 微軟官方有提供相關的範例給開發者閱讀,小弟也閱讀幾篇不錯部落格分享的實作,修改而成,分享給大家參考。

class SchemaValidation
    {
        private static bool isValid = true;
 
        ///
        /// 提供 驗證 XML 是否符合 XSD 格式
        ///
        ///XML File Path
        ///XSD File Path
        ///
        public static bool ValidationSchemaNow(string xmlstring, string schemastring)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(xmlstring);
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ValidationType = ValidationType.Schema;
            settings.Schemas.Add("urn:bookstore-schema", schemastring);
            settings.ValidationEventHandler += new ValidationEventHandler(MyValidationEventHandler);
 
            XmlNodeReader nodeReader = new XmlNodeReader(doc);
            XmlReader xr = XmlReader.Create(nodeReader, settings);
            try
            {
                while (xr.Read()) ;
            }
            catch (Exception e)
            {
                isValid = false;
                WriteErrorLogs("Validation Warning:   " + e.Message);
            }
            finally
            {
                xr.Close();
            }
            return isValid;
        }
 
 
        ///
        /// 驗證處理 Handler
        ///
        private static void MyValidationEventHandler(object sender, ValidationEventArgs args)
        {
            if (args.Severity == XmlSeverityType.Warning)
            {
                WriteErrorLogs("Validation Warning:   " + args.Message);
            }
            else
            {
                WriteErrorLogs("Validation Error:   " + args.Message);
            }
            isValid = false;
        }
 
        ///
        /// 錯誤 log 紀錄
        ///
        private static void WriteErrorLogs(string p)
        {
            StreamWriter sw = null;
            try
            {
                sw = new StreamWriter(@"D:\xml\erro.txt", true, Encoding.UTF8);
                sw.WriteLine("[" + DateTime.Now.ToString() + "]" + p);
                sw.Flush();
                sw.Close();
            }
            catch { sw.Close(); }
        }
}
這個 Class 可以驗證傳入的 XML 格式是否符合 XSD 的定義。但如果 XSD 改變程式中 的 settings.Schemas.Add("urn:bookstore-schema", schemastring); 第一個 String 參數也要跟著抽換的 XSD 檔案而改變。而我就沒有將此另外做改寫。


而呼叫的程式碼如下:


static void Main(string[] args)
       {
           string xmlfilePath = @"D:\xml\bookSchema.xml";
           string xsdfilePath = @"D:\xml\books.xsd";
      
           //利用 XSD 驗證
           if (SchemaValidation.ValidationSchemaNow(xmlfilePath, xsdfilePath))
           {
               // do something
               Console.WriteLine("驗證成功");
           }
           else
           {
               Console.WriteLine("驗證失敗");
           }
           Console.Read();
       }

[C#] PGP 加解密、數位簽章總結 (Bouncy Castle、OpenPGP Library)

連續發三篇 PGP 加解密、數位簽章、數位認證的實作,主要是市面上軟體對於資料的來源、以及資料的保密性越來越重視。因此這類型的技術使用會一直出現在 IT 軟體開發身邊。

在 API 使用上,除了前面所講的 Bouncy Castle API 以外,還有一套 API 非常好用,只可惜是付費軟體。
 這套名稱 openpgp library for .net ,它的使用方法更簡單,幾乎都把 加密、解密、數位簽章、數位認證 等主要 mehtod 都打包成 API,開發者只需直接呼叫即可。

這個軟體官網 ( http://www.didisoft.com/net-openpgp/ ),上面有提供很清楚的使用說明,從利用他的 API 建立 Public、Private Key 開始,到這個產品提供的 Main Function 使用說明,都有很清楚的範例。甚至這家公司有提供 Android openpgp library,可見得手機傳輸訊息的保密性也是一塊很重要的議題很值得深入研究。

最後小弟提供我在研究 PGP 相關議題的文章以及參考的部落格,希望對於大家學習這塊領域的東西有所幫助。
數位簽章部分

[C#] Bouncy Castle C# API(三) PGP 數位簽章 以及 數位認證 ( Sign and Verify )

 PGP 數位簽章以及數位認證 ( Sign and Verify ),是市面上常見用來判斷資訊是否源自正確的來源方式之一。 Bouncy Castle 也提供了這項功能。
      最常見的數位簽章認證流程通常為 資料來源方 利用自己的 Private Key 對傳送的檔案 or 資訊做數位簽章動作,證明此資訊由自己發送。接收方 則利用 資料來源方的 Public Key 來做數位認證動作,證實來源無誤。 
         而本篇文章是講如何先用  Bouncy Castle API 利用 Private Key 對檔案做數位簽章,接著在用 Public key 做數位認證動作。

1. 此部分一樣需要官方範例中 PgpExampleUtilities.cs 檔案 ( bccrypto-net-1.7-src\csharp\crypto\test\src\openpgp\ examples),加入專案中。此Class 提供取出private 以及 public key 資訊的 method 
2. Bouncy Castle  Private Key 檔案數位簽章 SignFile method 實作如下:

private static void SignFile(
            string fileName,        //預作簽章的檔案名稱及位置
            Stream keyIn,       // Private key 的 File Stream
            Stream outputStream,    //簽章後的檔案 File Stream
            char[] pass,        // private Key 的 password
            bool armor,         //用途不明?? 範例預設true
            bool compress       //用途不明?? 範例預設true
)
        {
            if (armor)
            {
                outputStream = new ArmoredOutputStream(outputStream);
            }
            PgpSecretKey pgpSec = PgpExampleUtilities.ReadSecretKey(keyIn);
            PgpPrivateKey pgpPrivKey = pgpSec.ExtractPrivateKey(pass);
            PgpSignatureGenerator sGen = new PgpSignatureGenerator(pgpSec.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
            sGen.InitSign(PgpSignature.BinaryDocument, pgpPrivKey);
            foreach (string userId in pgpSec.PublicKey.GetUserIds())
            {
                PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator();
                spGen.SetSignerUserId(false, userId);
                sGen.SetHashedSubpackets(spGen.Generate());
                // Just the first one!
                break;
            }
            Stream cOut = outputStream;
            PgpCompressedDataGenerator cGen = null;
            if (compress)
            {
                cGen = new PgpCompressedDataGenerator(CompressionAlgorithmTag.ZLib);
                cOut = cGen.Open(cOut);
            }
            BcpgOutputStream bOut = new BcpgOutputStream(cOut);
            sGen.GenerateOnePassVersion(false).Encode(bOut);
            FileInfo file = new FileInfo(fileName);
            PgpLiteralDataGenerator lGen = new PgpLiteralDataGenerator();
            Stream lOut = lGen.Open(bOut, PgpLiteralData.Binary, file);
            FileStream fIn = file.OpenRead();
            int ch = 0;
            while ((ch = fIn.ReadByte()) >= 0)
            {
                lOut.WriteByte((byte)ch);
                sGen.Update((byte)ch);
            }
            fIn.Close();
            lGen.Close();
            sGen.Generate().Encode(bOut);
            if (cGen != null)
            {
                cGen.Close();
            }
            if (armor)
            {
                outputStream.Close();
            }
        }
3. Bouncy Castle  Public Key 檔案數認證 VerifyFile method 實作如下:


private static void VerifyFile(
            Stream inputStream,     //準備做數位認證檔案的 File Stream 
            Stream keyIn,       // Public Key 的 File Stream
            string outputFileName   // 將數位簽章清除後產生未簽章之原始黨
    )
        {
            inputStream = PgpUtilities.GetDecoderStream(inputStream);
            PgpObjectFactory pgpFact = new PgpObjectFactory(inputStream);
            PgpCompressedData c1 = (PgpCompressedData)pgpFact.NextPgpObject();
            pgpFact = new PgpObjectFactory(c1.GetDataStream());
            PgpOnePassSignatureList p1 = (PgpOnePassSignatureList)pgpFact.NextPgpObject();
            PgpOnePassSignature ops = p1[0];
            PgpLiteralData p2 = (PgpLiteralData)pgpFact.NextPgpObject();
            Stream dIn = p2.GetInputStream();
            PgpPublicKeyRingBundle pgpRing = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
            PgpPublicKey key = pgpRing.GetPublicKey(ops.KeyId);
            //add
            Stream fileOutput = File.Create(outputFileName);
            ops.InitVerify(key);
            int ch;
            while ((ch = dIn.ReadByte()) >= 0)
            {
                ops.Update((byte)ch);
                fileOutput.WriteByte((byte)ch);
            }
            fileOutput.Close();
 
            PgpSignatureList p3 = (PgpSignatureList)pgpFact.NextPgpObject();
            PgpSignature firstSig = p3[0];
            if (ops.Verify(firstSig))
            {
                Console.Out.WriteLine("signature verified.");
            }
            else
            {
                Console.Out.WriteLine("signature verification failed.");
            }
        }

我們有了數位簽章 以及數位認證兩個 method 後,接著就列出簡單的程式實作,對 A檔案 作數位簽章產生 B檔案, 解著在對 B檔案 做是為認證,成功後產生 C檔案。而理論上 A C檔案內容應該會一致。其實作程式如下:


static void Main(string[] args)
 {
          //private2 對檔案做數位簽章
            string fileName = @"D:/BC/a.txt";
            Stream signkeyIn = File.OpenRead(@"D:/BC/priv.asc");
            Stream signOutputStream = File.Create(@"D:/BC/b.txt");
            char[] signPass = "123456".ToCharArray();
            bool signArmor = true;
            bool compress = true;
            try
            {
                SignFile(fileName, signkeyIn, signOutputStream, signPass, signArmor, compress);
                Console.WriteLine("簽章成功");
            }
            catch (Exception e)
            {
                Console.WriteLine("簽章失敗" + e.Message);
            }
            finally
            {
                signkeyIn.Close();
                signOutputStream.Close();
            }
 
           //public2 對檔案做數位認證
            Stream inputStream = File.OpenRead(@"D:/BC/b.txt");
            Stream keyIn = File.OpenRead(@"D:/BC/pub.asc");
            string outputFileName = @"D/BC/c.xml";
            try
            {
                VerifyFile(inputStream, keyIn, outputFileName);
                Console.WriteLine("認證OK");
            }
            catch (Exception e)
            {
                Console.WriteLine("認證失敗" + e.Message);
            }
           Console.Read();
}
這樣我們就完成一個簡單利用  Bouncy Castle 實作的的數位簽章、數位認證的範例了。

[C#] Bouncy Castle C# API(二) PGP 加解密(Encrypt AND Decrypt) 呼叫使用

上一篇文章解說過如何用 Bouncy Castle 產生 Public 及 Private key, 接著要繼續解說市面上最常見的加解密模式。雙方傳送 資訊 or 檔案,為了確保資料的機密性。我們都會用接收方的 publica Key 對檔案進行加密,接收方收到檔案後再利用自己的 priavte Key做解密的動作。這是最常見的方法。
因此我透過修改 Bouncy Castle 官方範例,實作出以上的情境的程式。
1.  先官方範例中(bccrypto-net-1.7-src\csharp\crypto\test\src\openpgp\examples) 複製 PgpExampleUtilities.cs 檔案,加入專案中。此Class 提供取出 private 以及 public key 資訊的 method 。
2.  Bouncy Castle 檔案 Public key 加密 EncryptFile method 實作如下

//外部呼叫的 method
public static void EncryptFile(
     string outputFileName,//加密後輸出檔案名稱位置
     string inputFileName, //欲加密檔案名稱位置
     string encKeyFileName,//提供加密的 public key 檔名及位置
     bool armor,           //不明???,範例預設為true
     bool withIntegrityCheck//不明???,範例預設為false
     )
 {
     PgpPublicKey encKey = PgpExampleUtilities.ReadPublicKey(encKeyFileName);
 
     using (Stream output = File.Create(outputFileName))
     {
         EncryptFile(output, inputFileName, encKey, armor, withIntegrityCheck);
     }
 }
 
//內部的實作參照官方範例
 private static void EncryptFile(
     Stream outputStream,
     string fileName,
     PgpPublicKey encKey,
     bool armor,
     bool withIntegrityCheck)
 {
     if (armor)
     {
         outputStream = new ArmoredOutputStream(outputStream);
     }
 
     try
     {
         byte[] bytes = PgpExampleUtilities.CompressFile(fileName, CompressionAlgorithmTag.Zip);
 
         PgpEncryptedDataGenerator encGen = new PgpEncryptedDataGenerator(
             SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
         encGen.AddMethod(encKey);
 
         Stream cOut = encGen.Open(outputStream, bytes.Length);
 
         cOut.Write(bytes, 0, bytes.Length);
         cOut.Close();
 
         if (armor)
         {
             outputStream.Close();
         }
     }
     catch (PgpException e)
     {
         Console.Error.WriteLine(e);
 
         Exception underlyingException = e.InnerException;
         if (underlyingException != null)
         {
             Console.Error.WriteLine(underlyingException.Message);
             Console.Error.WriteLine(underlyingException.StackTrace);
         }
     }
 }

3.  Bouncy Castle 檔案 Private key 解密 DecryptFile method 實作如下:


//外部呼叫解密 mehtod
public static void DecryptFile(
    string inputFileName,  //欲解密之檔案名稱及位置
    string keyFileName,    //解密 Private key 位置
    char[] passwd,         //Private key password
    string defaultFileName //解密後檔案名稱及位置
)
{
    using (Stream input = File.OpenRead(inputFileName),
           keyIn = File.OpenRead(keyFileName))
    {
        DecryptFile(input, keyIn, passwd, defaultFileName);
    }
}
 
//內部解密實作參照官方範例
private static void DecryptFile(
    Stream inputStream,
    Stream keyIn,
    char[] passwd,
    string defaultFileName)
{
    inputStream = PgpUtilities.GetDecoderStream(inputStream);
 
    try
    {
        PgpObjectFactory pgpF = new PgpObjectFactory(inputStream);
        PgpEncryptedDataList enc;
 
        PgpObject o = pgpF.NextPgpObject();
        //
        // the first object might be a PGP marker packet.
        //
        if (o is PgpEncryptedDataList)
        {
            enc = (PgpEncryptedDataList)o;
        }
        else
        {
            enc = (PgpEncryptedDataList)pgpF.NextPgpObject();
        }
 
        //
        // find the secret key
        //
        PgpPrivateKey sKey = null;
        PgpPublicKeyEncryptedData pbe = null;
        PgpSecretKeyRingBundle pgpSec = new PgpSecretKeyRingBundle(
            PgpUtilities.GetDecoderStream(keyIn));
 
        foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
        {
            sKey = PgpExampleUtilities.FindSecretKey(pgpSec, pked.KeyId, passwd);
 
            if (sKey != null)
            {
                pbe = pked;
                break;
            }
        }
 
        if (sKey == null)
        {
            throw new ArgumentException("secret key for message not found.");
        }
 
        Stream clear = pbe.GetDataStream(sKey);
 
        PgpObjectFactory plainFact = new PgpObjectFactory(clear);
 
        PgpObject message = plainFact.NextPgpObject();
 
        if (message is PgpCompressedData)
        {
            PgpCompressedData cData = (PgpCompressedData)message;
            PgpObjectFactory pgpFact = new PgpObjectFactory(cData.GetDataStream());
 
            message = pgpFact.NextPgpObject();
        }
 
        if (message is PgpLiteralData)
        {
            PgpLiteralData ld = (PgpLiteralData)message;
 
            string outFileName = ld.FileName;
            //if (outFileName.Length == 0)
            //{
            outFileName = defaultFileName;
            //}
 
            Stream fOut = File.Create(outFileName);
            Stream unc = ld.GetInputStream();
            Streams.PipeAll(unc, fOut);
            fOut.Close();
        }
        else if (message is PgpOnePassSignatureList)
        {
            throw new PgpException("encrypted message contains a signed message - not literal data.");
        }
        else
        {
            throw new PgpException("message is not a simple encrypted file - type unknown.");
        }
 
        if (pbe.IsIntegrityProtected())
        {
            if (!pbe.Verify())
            {
                Console.Error.WriteLine("message failed integrity check");
            }
            else
            {
                Console.Error.WriteLine("message integrity check passed");
            }
        }
        else
        {
            Console.Error.WriteLine("no message integrity check");
        }
    }
    catch (PgpException e)
    {
        Console.Error.WriteLine(e);
 
        Exception underlyingException = e.InnerException;
        if (underlyingException != null)
        {
            Console.Error.WriteLine(underlyingException.Message);
            Console.Error.WriteLine(underlyingException.StackTrace);
        }
    }
}

這樣我們就實作完加密解密的 Method ,接著我們實驗一個範例。範例如下:
我們有個檔案 a.txt 利用 Public key 對檔案加密,產生出 b.txt 。接著我們再用 b.txt 利用 Private Key 作解密,產生 c.txt 。  
其實作程式如下:

static void Main(string[] args)
 {
            //Public 1 檔案加密
            string encryptFileName = @"D:/BC/b.txt";
            string inputFileName = @"D:/BC/a.txt";
            string encKeyFileName = @"D:/BC/pub1.asc";
            bool armor = true;
            bool withIntegrityCheck = false;
            try
            {
                EncryptFile(encryptFileName, inputFileName,
                       encKeyFileName, armor, withIntegrityCheck);
                Console.WriteLine("加密成功");
 
            }
            catch (Exception e)
            {
                Console.WriteLine("加密失敗" + e.Message);
            }
 
            //private1 檔案解密
            string decryptEncryptFileName = @"D:/BC/b.txt";
            string keyFileName = @"D:/BC/priv1.asc";
            char[] passwd = "123456".ToCharArray();
            string defaultFileName = @"D:/BC/c.txt";
            try
            {
                DecryptFile(decryptEncryptFileName, keyFileName, 
                        passwd, defaultFileName);
                Console.WriteLine("解密成功");
            }
            catch (Exception e)
            {
                Console.WriteLine("解密失敗" + e.Message);
            }
            Console.Read();
}

執行結果我們分別來看 a b c .xml 檔案內容。
a.txt:
19834209
02/02/2002


b.txt:
-----BEGIN PGP MESSAGE-----
Version: BCPG C# v1.7.4937.24077
 
hIwDQtEQm+xjLocBA/0Vpz+RVTS8uMTr/RTJdvEcNZlcAHIPsPj6RrJouZkySjcO
6DkHVjS1gVW6RxcjDy/pPnfF/R7R3LUMF9ibh0dQjEu3stuPI3UXz7nEwJ2yhQ04
+vGEJYLmw185WkOcvUGiiVc/ZkU0AkYHolmz4zFfLAHbNJcPt1SSjhNHDSwchskz
bu7bsgAg9aM/+E7Z5mWJgeePnX9guh9Ucl6psv6/ZYq45WtYGOMeeNB2trkcwa8k
hh2R
=oAx4
-----END PGP MESSAGE-----


c.txt:
19834209
02/02/2002

a.xml 內容與 c.xml 內容是一致的,證明解密成功。以上是簡單的PGP接解密實作範例。

[C#]Bouncy Castle C# API(一) 產生 PGP 的 Public 及 Private key 方法

Bouncy Castle C# API  提供免費的 PGP(Pretty Good Privacy) 加密、數位簽章、數位認證等功能。
我們就從最一開始如何產生  Bouncy Castle 公私鑰( Public 、 Private  key)並保存成檔案開始說起 。
本文是利用 Bouncy Castle官網 下載的 bccrypto-net-1.7-bin.zin 以及  bccrypto-net-1.7-src.zip 進行示範,src.zip 壓縮檔裡面包含官網的範例程式可提供給各位參考。
而此篇就是利用 官網範例 RsaKeyRingGenerator 改寫而成。
1.首先在專案的 Reference 下 滑鼠點選又見 add Reference ,點選 Browers 選擇  bccrypto-net-1.7-bin 解壓縮的資料夾下 BouncyCastle.Crypto.dll 按下確定。這樣即可使用 Bouncy Castle 的 API 了。
2. 接下來利用  bccrypto-net-1.7-src RsaKeyRingGenerator Example 撰寫的 Method 即可完成這次的功能。
3. 接著我們撰寫產生 PGP publica key 以及 private key 的檔案 method  ,程式如下:

 
   private static void ExportKeyPair(
       Stream secretOut,
       Stream publicOut,
       AsymmetricKeyParameter publicKey,
       AsymmetricKeyParameter privateKey,
       string identity,
       char[] passPhrase,
       bool armor)
   {
       if (armor)
       {
           secretOut = new ArmoredOutputStream(secretOut);
       }
       PgpSecretKey secretKey = new PgpSecretKey(
           PgpSignature.DefaultCertification,
           PublicKeyAlgorithmTag.RsaGeneral,
           publicKey,
           privateKey,
           DateTime.UtcNow,
           identity,
           SymmetricKeyAlgorithmTag.Cast5,
           passPhrase,
           null,
           null,
           new SecureRandom()
           );
       secretKey.Encode(secretOut);
       if (armor)
       {
           secretOut.Close();
           publicOut = new ArmoredOutputStream(publicOut);
       }
       PgpPublicKey key = secretKey.PublicKey;
       key.Encode(publicOut);
       if (armor)
       {
           publicOut.Close();
       }
   }
傳入參數依序如下
(1) Private key 的 FileStream ,
(2) Public key 的 FileStream, 
(3) 由  Bouncy Castle  產生的 publuic Key , 
(4) 由 Bouncy Castle產生的 private key , 
(5) 使用者名稱 String , 
(6) armor 不明.......範例設為true

4. 產生 Public Private Key 以及呼叫 ExportKeyPair 程式寫法如下:
static void Main(string[] args)
{
           //RSA密鑰產生器
           IAsymmetricCipherKeyPairGenerator kpg = GeneratorUtilities.GetKeyPairGenerator("RSA");
           //Key 構造使用參數        
           kpg.Init(new RsaKeyGenerationParameters(
                  BigInteger.ValueOf(0x10001), new SecureRandom(),
           1024,// key 的長度
            25));
           AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();
           char[] password = "123456".ToCharArray(); //私鑰的密碼
           Stream out1, out2;
           out1 = File.Create(@"D:\BC\priv.asc");//私鑰放置位置          
           out2 = File.Create(@"D:\BC\pub.asc"); //公鑰放置位置
           ExportKeyPair(out1, out2, kp.Public,                 
           kp.Private, "Jama", password, true);
}

這樣即可簡單產生一組 RSA 的公私鑰檔案 在 D:\BC 目錄下,方便以後專案使用了。