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 目錄下,方便以後專案使用了。