سلسلة net. للمحترفين التشفير Encryption والتعمية Hashing في dot NET


رضوى العربي

التشفير (Encryption)

تشفير البيانات وفكها باستخدام النوع Aes

تَستعرِض الشيفرة التالية مثال طرفية توضيحي من شبكة مطوري مايكروسوفت (MSDN). يَشرَح هذا المثال طريقة تشفير سِلسِلة نصية ثُم فَكّ التشفير باِستخدَام الخوارزمية القياسية "معيار التشفير المُتقدِم Advanced Encryption Standard"، وتُسمَى اختصارًا AES.

يُوفِّر إطار عمل ‎.NET النوع Aes، والذي يُنْفِّذ خوارزمية معيار التشفير المُتقدِم AES. تَتكون كُلًا من شيفرتي التشفير وفَكُه مِن عدة خطوات مُشتَركة.

نحتاج عامةً لإِنشاء مَجْرى (stream) بيانات ستمُرّ عَبره البيانات المَطلوب تَشفيِرها أو فَكّ تَشفيِرها.

using (MemoryStream msEncrypt = new MemoryStream())

كذلك سنحتاج إلى تنشئة عملية تَحوِيل مَجْرى (stream transform) -إِمّا مُشفِر أو مُفكِّك للتشفير- من النوع ICryptoTransform بحيث تُطبَّق على البيانات أثناء مرُّورَها بالمَجْرى. يُستخدَم التابعين CreateEncryptor وCreateDecryptor لإنشاء المُشفِر ومُفكِّك التشفير على الترتيب.

ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

أخيرًا، نُنشِئ مَجْرى تَشفير من النوع CryptoStream يُوصِل عملية تَحوِيل المَجْرى (stream transform) بمَجْرى البيانات مع تحديد وَضْع التَوصِيل إِمّا للقراءة أو للكتابة.

using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))

مثلًا، في حالة التشفير، سنَكتُب البيانات المطلوب تشفيرها على مَجْرى التشفير CryptoStream. يتم تَّنْفيذ عملية تَحوِيل المَجْرى (stream transform) -مُشفِر في هذه الحالة- على البيانات، وتُكتَب النتيجة المُشفَّرة على المَجْرى المُمرَّر لمَجْرى التشفير بوَضْع الكتابة.

using (MemoryStream msEncrypt = new MemoryStream())
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
        swEncrypt.Write(plainText);
    encrypted = msEncrypt.ToArray();
}

في حالة فَكّ التشفير، سيَقرأ مَجْرى التشفير CryptoStream البيانات المطلوب فَكّ تشفيرها من المَجْرى المُمرَّر له بوَضْع القراءة. يتم تَّنْفيذ عملية تَحوِيل المَجْرى (stream transform) -مُفكِّك شَفرة في هذه الحالة- على البيانات. أخيرًا نقرأ البيانات بعد فَكّ التشفير من خلال مَجْرى التشفير.

using (MemoryStream msDecrypt = new MemoryStream(cipherText))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
    plaintext = srDecrypt.ReadToEnd();

الشيفرة بالكامل:

using System;
using System.IO;
using System.Security.Cryptography;
namespace Aes_Example
{
    class AesExample
    {
        public static void Main()
        {‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎
            try
            {
                string original = "Here is some data to encrypt!";
      // ‫أنشئ كائن من النوع Aes المُستخدَم لتوليد كلا من المفتاح ومتجه التهيئة 
                using (Aes myAes = Aes.Create())
                {
                    // قم بتشفير سلسلة نصية إلى مصفوفة بايتات
                    byte[] encrypted = EncryptStringToBytes_Aes(original,
                                                                myAes.Key,
                                                                myAes.IV);

                   // قم بفك تشفير مصفوفة بايتات إلى سلسلة نصية
                    string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);

                    Console.WriteLine("Original: {0}", original);
                    Console.WriteLine("Round Trip: {0}", roundtrip);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error: {0}", e.Message);
            }
        }
    }
}
static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
    // Check arguments.
    if (plainText == null || plainText.Length <= 0)
        throw new ArgumentNullException("plainText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");
    if (IV == null || IV.Length <= 0)
        throw new ArgumentNullException("IV");

    byte[] encrypted;
    // ‫أنشئ كائن من النوع Aes باستخدام المفتاح ومتجه التهيئة المحددين 
    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Key = Key;
        aesAlg.IV = IV;

        // أنشئ مُشفر والذي سيستخدم كمحول للمجرى
        ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        // انشئ المجاري المستخدمة خلال عملية التشفير
        using (MemoryStream msEncrypt = new MemoryStream())
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                swEncrypt.Write(plainText);
            encrypted = msEncrypt.ToArray();
        }
    }
    // أعد مصفوفة البايتات المشفرة المُنشأة من مجرى الذاكرة
    return encrypted;
}
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
{
    // تحقق من الوسائط
    if (cipherText == null || cipherText.Length <= 0)
        throw new ArgumentNullException("cipherText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");
    if (IV == null || IV.Length <= 0)
        throw new ArgumentNullException("IV");

    string plaintext = null;
    // ‫أنشئ كائن من النوع Aes باستخدام المفتاح ومتجه التهيئة المحددين 
    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Key = Key;
        aesAlg.IV = IV;

        // أنشئ مفكك الشفرة الذي سيستخدم كمحول للمجرى
        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

        // انشئ المجاري المستخدمة خلال عملية فك التشفير
        using (MemoryStream msDecrypt = new MemoryStream(cipherText))
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            using (StreamReader srDecrypt = new StreamReader(csDecrypt))
            // ‫قم بقراءة البايتات من مجرى فك التشفير وأسْندها إلى متغير من النوع string
            plaintext = srDecrypt.ReadToEnd();
    }

    return plaintext;
}

تُحدِّد الخوارزمية AES طريقة لتشفير البيانات الالكترونية. أُسِست عام 2001 بواسطة المعهد الوطني للمعايير والتقنية NIST بالولايات المتحدة الامريكية، ومازالت تُعدّ الخوارزمية القياسية للتشفير التَماثلي (symmetric encryption).

ملاحظات:

  • تَتوفَّر عِدة أوضاع تَشفير cipher mode ضِمْن خوارزمية AES. تَستطيع تَحديد وَضْع التشفير بإِسْناد إِحدى قيم التعداد CipherMode إلى الخاصية Mode. لا تَستخدِم أبدًا وَضْع التشفير Electronic codebook - ECB - مما يَعنِي عدم إِختيار CipherMode.ECB-؛ لأنه يُنتِج عملية تَحوِيل مَجْرى ضعيفة.
  • يجب أن تَستخدِم مُولِّد تَشفير عشوائي -أو اِستخدِم الشيفرة بالأسفل (إنشاء مفتاح من كلمة سرّ / سلسلة نصية إضافية (Salt) عشوائية)- لتنشئة مفتاح (Key) جيد غيْر ضعيف. يُفضَّل أيضًا أن يَكُون حجم المفتاح 256 بت. تستطيع تخصيص حَجم المفتاح مِن خلال الخاصية KeySize كما تُوفِّر الخاصية LegalKeySizes قائمة بالأحجام المُدعَّمة.
  • يُمكِنك اِستخدَام سِلسِلة نصية إضافية (salt) -كالمثال بالأسفل (إنشاء مفتاح من كلمة سرّ / سلسلة نصية إضافية (Salt) عشوائية)- لتهيئة مُتّجَه التهيئة (initialization vector - IV).

مثال آخر باستخدام خوارزمية AES

شيفرة التشفير:

public static string Encrypt(string cipherText)
{
    if (cipherText == null)
        return null;

    byte[] clearBytes = Encoding.Unicode.GetBytes(cipherText);
    using (Aes encryptor = Aes.Create())
    {
        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(CryptKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);

        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(clearBytes, 0, clearBytes.Length);
                cs.Close();
            }

            cipherText = Convert.ToBase64String(ms.ToArray());
        }
    }
    return cipherText;
}

شيفرة فَكّ التشفير:

public static string Decrypt(string cipherText)
{
    if (cipherText == null)
        return null;

    byte[] cipherBytes = Convert.FromBase64String(cipherText);

    using (Aes encryptor = Aes.Create())
    {
        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(CryptKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);

        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
            {
                cs.Write(cipherBytes, 0, cipherBytes.Length);
                cs.Close();
            }
            cipherText = Encoding.Unicode.GetString(ms.ToArray());
        }
    }

    return cipherText;
}

تُستخدَم كالتالي:

var textToEncrypt = "TestEncrypt";
var encrypted = Encrypt(textToEncrypt);
var decrypted = Decrypt(encrypted);

تشفير البيانات وفكها باستخدام النوع RijndaelManaged

يَتطلَّب فضاء الاسم System.Security.Cryptography

private class Encryption {

    private const string SecretKey = "topSecretKeyusedforEncryptions";

    private const string SecretIv = "secretVectorHere";

    public string Encrypt(string data) {
        return string.IsNullOrEmpty(data) ? data :
        Convert.ToBase64String(
            this.EncryptStringToBytesAes(data, this.GetCryptographyKey(), this.GetCryptographyIv())
        );
    }

    public string Decrypt(string data) {
        return string.IsNullOrEmpty(data) ? data :
        this.DecryptStringFromBytesAes(Convert.FromBase64String(data), this.GetCryptographyKey(), this.GetCryptographyIv());
    }

    private byte[] GetCryptographyKey() {
        return Encoding.ASCII.GetBytes(SecretKey.Replace('e', '!'));
    }

    private byte[] GetCryptographyIv() {
        return Encoding.ASCII.GetBytes(SecretIv.Replace('r', '!'));
    }
}    
private byte[] EncryptStringToBytesAes(string plainText, byte[] key, byte[] iv) {
    MemoryStream encrypt;
    RijndaelManaged aesAlg = null;

    try {

        aesAlg = new RijndaelManaged {
            Key = key,
            IV = iv
        };
        var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        encrypt = new MemoryStream();

        using (var csEncrypt = new CryptoStream(encrypt, encryptor, CryptoStreamMode.Write))
            using (var swEncrypt = new StreamWriter(csEncrypt))
            swEncrypt.Write(plainText);
    } finally {
        aesAlg?.Clear();
    }
    return encrypt.ToArray();
}
private string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv) {

    RijndaelManaged aesAlg = null;
    string plaintext;

    try {
        aesAlg = new RijndaelManaged {
            Key = key,
            IV = iv
        };
        var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

        using (var msDecrypt = new MemoryStream(cipherText)) 
            using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 
            using (var srDecrypt = new StreamReader(csDecrypt))
            plaintext = srDecrypt.ReadToEnd();

    } finally {
        aesAlg?.Clear();
    }
    return plaintext;
}

يُستخدَم كالتالي:

var textToEncrypt = "hello World";
//-> zBmW+FUxOvdbpOGm9Ss/vQ==
var encrypted = new Encryption().Encrypt(textToEncrypt); 
//-> hello World
var decrypted = new Encryption().Decrypt(encrypted); 

تنبيه: يُنفِّذ النوع Rijndael النسخة الأقدم من خوارزمية AES، ولذلك ينبغي استعمال النوع Aes الذي يُنفِّذ النسخة الحديثة.

إنشاء مفتاح من كلمة سر / سلسلة نصية إضافية (Salt) عشوائية

تَستعرِض الشيفرة التالية مثال طرفية توضيحي من شبكة مطوري مايكروسوفت (MSDN). يَشرح هذا المثال كيفية تنشئة مفتاح (key) آمِن اعتمادًا على كلمة سرّ مُحدَّدة من قِبَل المُستخدِم، بالإضافة إلى طريقة تنشئة سِلسِلة نصية إضافية (salt أو يدعى غفل باللغة العربية، انظر كتاب «علم التعمية واستخراج المعمى عند العرب») عشوائية باِستخدَام مُولِّد تشفير عشوائي.

using System;
using System.Security.Cryptography;
using System.Text;

public class PasswordDerivedBytesExample
{
    public static void Main(String[] args)
    {
        // اجلب كلمة السر من المستخدم
        Console.WriteLine("Enter a password to produce a key:");

        byte[] pwd = Encoding.Unicode.GetBytes(Console.ReadLine());
        byte[] salt = CreateRandomSalt(7);

        // TripleDESCryptoServiceProvider أنشئ كائنًا من النوع
        TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();

        try
        {
            Console.WriteLine("Creating a key with PasswordDeriveBytes...");

  // ‫أنشئ كائنًا من النوع PasswordDeriveBytes ثم أنشئ مفتاح لخوارزمية TripleDES
            // من كلمة سر وسلسلة نصية إضافية
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(pwd, salt);

            // ‫أنشئ المفتاح وأسنده إلى الخاصية Key الموجودة بكائن موفر خدمة التشفير
            tdes.Key = pdb.CryptDeriveKey("TripleDES", "SHA1", 192, tdes.IV);

            Console.WriteLine("Operation complete.");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        finally
        {
            ClearBytes(pwd);
            ClearBytes(salt);
            tdes.Clear();
        }
        Console.ReadLine();
    }
}
/// توليد غفل (سلسلة نصية إضافية) بالطول المحدد
public static byte[] CreateRandomSalt(int length)
{
    byte[] randBytes;
    if (length >= 1)
        randBytes = new byte[length];
    else
        randBytes = new byte[1];

    // RNGCryptoServiceProvider إنشاء كائن من النوع
    RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
    // املأ المخزن بالبايتات العشوائية
    rand.GetBytes(randBytes);

    return randBytes;
}
/// امسح البايتات من المخزن لكي لا تُقرَأ مستقبلًا من الذاكرة
public static void ClearBytes(byte[] buffer)
{
    if (buffer == null)
        throw new ArgumentNullException("buffer");

    // اضبط قيمة كل بايت إلى القيمة 0
    for (int x = 0; x < buffer.Length; x++)
    {
        buffer[x] = 0;
    }
}

ملاحظات:

  • تَستقبِل الدالة PasswordDeriveBytes المَبنية مُسبَقًا (built-in) كلمة سرّ، وتَستخدِم خوارزمية PBKDF1 القياسية لتولِّيد المفتاح. تَقوم هذه الدالة بشكل افتراضي بمائة تكرار أثناء تولِّيد المفتاح؛ وذلك لتُبطئ من فاعلية هَجمَات القوى الغاشمة (brute force attacks). بالإضافة إلى ذلك، يُعزز اِستخدَام السِلسِلة النصية الإضافية (salt) المُولَّدة عشوائيًا من قوة المفتاح.
  • تَستخدِم الدالة CryptDeriveKey خوارزمية التَقطيع (hashing) المُمرَّرة إليها -تم اختيار SHA1 بالمثال- لتَحوِيل المفتاح المُولَّد من الدالة PasswordDeriveBytes إلى مفتاح مُتوافِق مع خوارزمية التشفير المُمرَّرة إليها -تم اختيار TripleDES بالمثال-، يمكنك أيضًا تخصيص كلًا من حجم المفتاح والقيمة المبدئية لمُتٍّجَه التهيئة (initialization vector - IV) بتمريرهما كمُعامِلين للدالة. في المثال بالأعلى، مُرِّرت القيمة 192 بايت كما اُستخدِم مُوفِّر خدمة التشفير من النوع TripleDESCryptoServiceProvider لتهيئة مُتّجَه التهيئة.
  • عندما تحتاج إلى مفتاح قوي مُولَّد عشوائيًا لاِستخدَامُه لتشفير كمية ضخمة من البيانات، اِستخدِم هذه الطريقة لتولِّيده مِن مجرد كلمة سرّ. يُمكِن أيضًا اِستخدَام هذه الطريقة لتوليد كلمات سر لعِدّة مُستخدِمين للولوج إلى نفس البيانات.
  • للأسف، لا تُدعِّم الدالة CryptDeriveKey خوارزمية AES حاليًا (تَحقَّق هنا). يُمكِن التحايل على ذلك بالاعتماد على حَاوِي خوارزمية TripleDES، مع أنه سيُؤدِي إلى تَبَعيّة لهذه الخوارزمية وسيُقصِرك على مُستَوَى حمايتها، فمثلًا لن تستطيع تَوليد مفاتيح بأحجام أكبر من تلك المُدعَّمة بخوارزمية TripleDES على الرغم من دَعََّم تلك الأحجام بخوارزمية AES.

دوال التعمية (Hashing)

توليد تدقيق المجموع (checksum) لملف باستخدام خوارزمية SHA1

تَتطلَّب فضاء الاسم System.Security.Cryptography

public string GetSha1Hash(string filePath)
{
    using (FileStream fs = File.OpenRead(filePath))
    {
        SHA1 sha = new SHA1Managed();
        return BitConverter.ToString(sha.ComputeHash(fs));
    }
}

توليد القيمة المعماة (hash أو المقطعة) لسِلسِلة نصية

public static string TextToHash(string text)
{
    var sh = SHA1.Create();
    var hash = new StringBuilder();
    byte[] bytes = Encoding.UTF8.GetBytes(text);
    byte[] b = sh.ComputeHash(bytes);
    foreach (byte a in b)
    {
        var h = a.ToString("x2");
        hash.Append(h);
    }
    return hash.ToString();
}

ملحوظة: الشيفرة بالكامل موجودة بـمستودع mahdiabasi/SHA1Tool في GitHub.

ترجمة -وبتصرف- للفصلين Encryption / Cryptography و Work with SHA1 in C#‎ من كتاب ‎.NET Framework Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن