۱۳۹۰/۰۸/۲۱

رمزنگاري فايل‌هاي PDF با استفاده از كليد عمومي توسط iTextSharp


دو نوع رمزنگاري را مي‌توان توسط iTextSharp به PDF توليدي و يا موجود، اعمال كرد:
الف) رمزنگاري با استفاده از كلمه عبور
ب) رمزنگاري توسط كليد عمومي

الف) رمزنگاري با استفاده از كلمه عبور
در اينجا امكان تنظيم read password و edit password به كمك متد SetEncryption شيء pdfWrite وجود دارد. همچنين مي‌توان مشخص كرد كه مثلا آيا كاربر مي‌تواند فايل PDF را چاپ كند يا خير (PdfWriter.ALLOW_PRINTING).
ذكر read password اختياري است؛ اما جهت اعمال permissions حتما نياز است تا edit password ذكر گردد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

                var readPassword = Encoding.UTF8.GetBytes("123");//it can be null.
                var editPassword = Encoding.UTF8.GetBytes("456");
                int permissions = PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY;
                pdfWriter.SetEncryption(readPassword, editPassword, permissions, PdfWriter.STRENGTH128BITS);

                pdfDoc.Open();

                pdfDoc.Add(new Phrase("tst 0"));
                pdfDoc.NewPage();
                pdfDoc.Add(new Phrase("tst 1"));
            }

            Process.Start("TestEnc.pdf");
        }
    }
}


اگر read password ذكر شود، كاربران براي مشاهده محتويات فايل نياز خواهند داشت تا كلمه‌ي عبور مرتبط را وارد نمايند:


اين روش آنچنان امنيتي ندارد. هستند برنامه‌هايي كه اين نوع فايل‌ها را «آني» به نمونه‌ي غير رمزنگاري شده تبديل مي‌كنند (حتي نيازي هم ندارند كه از شما كلمه‌ي عبوري را سؤال كنند). بنابراين اگر كاربران شما آنچنان حرفه‌اي نيستند، اين روش خوب است؛ در غيراينصورت از آن صرفنظر كنيد.


ب) رمزنگاري توسط كليد عمومي
اين روش نسبت به حالت الف بسيار پيشرفته‌تر بوده و امنيت قابل توجهي هم دارد و «نيستند» برنامه‌هايي كه بتوانند اين فايل‌ها را بدون داشتن اطلاعات كافي، به سادگي رمزگشايي كنند.

براي شروع به كار با public key encryption نياز است يك فايل PFX يا Personal Information Exchange داشته باشيم. يا مي‌توان اين نوع فايل‌ها را از CA's يا Certificate Authorities خريد، كه بسيار هم نيكو يا اينكه مي‌توان فعلا براي آزمايش، نمونه‌ي self signed اين‌ها را هم تهيه كرد. مثلا با استفاده از اين برنامه.


در ادامه نياز خواهيم داشت تا اطلاعات اين فايل PFX را جهت استفاده توسط iTextSharp استخراج كنيم. كلاس‌هاي زير اينكار را انجام مي‌دهند و نهايتا كليدهاي عمومي و خصوصي ذخيره شده در فايل PFX را بازگشت خواهند داد:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
    /// <summary>
    /// A Personal Information Exchange File Info
    /// </summary>
    public class PfxData
    {
        /// <summary>
        /// Represents an X509 certificate
        /// </summary>
        public X509Certificate[] X509PrivateKeys { set; get; }

        /// <summary>
        /// Certificate's public key
        /// </summary>
        public ICipherParameters PublicKey { set; get; }
    }
}

using System;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
    /// <summary>
    /// A Personal Information Exchange File Reader
    /// </summary>
    public class PfxReader
    {
        X509Certificate[] _chain;
        AsymmetricKeyParameter _asymmetricKeyParameter;
       
        /// <summary>
        /// Reads A Personal Information Exchange File.
        /// </summary>
        /// <param name="pfxPath">Certificate file's path</param>
        /// <param name="pfxPassword">Certificate file's password</param>
        public PfxData ReadCertificate(string pfxPath, string pfxPassword)
        {
            using (var stream = new FileStream(pfxPath, FileMode.Open, FileAccess.Read))
            {
                var pkcs12Store = new Pkcs12Store(stream, pfxPassword.ToCharArray());
                var alias = findThePublicKey(pkcs12Store);
                _asymmetricKeyParameter = pkcs12Store.GetKey(alias).Key;
                constructChain(pkcs12Store, alias);
                return new PfxData { X509PrivateKeys = _chain, PublicKey = _asymmetricKeyParameter };
            }
        }

        private void constructChain(Pkcs12Store pkcs12Store, string alias)
        {
            var certificateChains = pkcs12Store.GetCertificateChain(alias);
            _chain = new X509Certificate[certificateChains.Length];

            for (int k = 0; k < certificateChains.Length; ++k)
                _chain[k] = certificateChains[k].Certificate;
        }

        private static string findThePublicKey(Pkcs12Store pkcs12Store)
        {
            string alias = string.Empty;
            foreach (string entry in pkcs12Store.Aliases)
            {
                if (pkcs12Store.IsKeyEntry(entry) && pkcs12Store.GetKey(entry).Key.IsPrivate)
                {
                    alias = entry;
                    break;
                }
            }

            if (string.IsNullOrEmpty(alias))
                throw new NullReferenceException("Provided certificate is invalid.");

            return alias;
        }
    }
}


اكنون رمزنگاري فايل PDF توليدي توسط كليد عمومي، به سادگي چند سطر كد زير خواهد بود:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

                var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
                pdfWriter.SetEncryption(
                          certs: certs.X509PrivateKeys,
                          permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
                          encryptionType: PdfWriter.ENCRYPTION_AES_128);

                pdfDoc.Open();

                pdfDoc.Add(new Phrase("tst 0"));
                pdfDoc.NewPage();
                pdfDoc.Add(new Phrase("tst 1"));
            }

            Process.Start("Test.pdf");
        }
    }
}

پيش از فراخواني متد Open بايد تنظيمات رمزنگاري مشخص شوند. در اينجا ابتدا فايل PFX خوانده شده و كليدهاي عمومي و خصوصي آن استخراج مي‌شوند. سپس به متد SetEncryption جهت استفاده نهايي ارسال خواهند شد.

نحوه استفاده از اين نوع فايل‌هاي رمزنگاري شده:
اگر سعي در گشودن اين فايل رمزنگاري شده نمائيم با خطاي زير مواجه خواهيم شد:


كاربران براي اينكه بتوانند اين فايل‌هاي PDF را بار كنند نياز است تا فايل PFX شما را در سيستم خود نصب كنند. ويندوز فايل‌هاي PFX را مي‌شناسد و نصب آن‌ها با دوبار كليك بر روي فايل و چندبار كليك بر روي دكمه‌ي Next و وارد كردن كلمه عبور آن، به پايان مي‌رسد.

سؤال: آيا مي‌توان فايل‌هاي PDF موجود را هم به همين روش رمزنگاري كرد؟
بله. iTextSharp علاوه بر PdfWriter داراي PdfReader نيز مي‌باشد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            PdfReader reader = new PdfReader("TestDec.pdf");
            using (var stamper = new PdfStamper(reader, new FileStream("TestEnc.pdf", FileMode.Create)))
            {
                var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
                stamper.SetEncryption(
                         certs: certs.X509PrivateKeys,
                         permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
                         encryptionType: PdfWriter.ENCRYPTION_AES_128);
                stamper.Close();
            }

            Process.Start("TestEnc.pdf");
        }
    }
}


سؤال: آيا مي‌توان نصب كليد عمومي را خودكار كرد؟
سورس برنامه SelfCert كه معرفي شد، در دسترس است. اين برنامه قابليت انجام نصب خودكار مجوزها را دارد.