تغییرات رمزنگاری اطلاعات در NET Core.
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هفت دقیقه

در NET Core. به ظاهر دیگر خبری از کلاس‌هایی مانند RNGCryptoServiceProvider برای تولید اعداد تصادفی و یا SHA256Managed (و تمام کلاس‌های Managed_) برای هش کردن اطلاعات نیست. در ادامه این موارد را بررسی کرده و با معادل‌های آن‌ها در NET Core. آشنا خواهیم شد.


تغییرات الگوریتم‌های هش کردن اطلاعات

با حذف و تغییرنام کلاس‌هایی مانند SHA256Managed (و تمام کلاس‌های Managed_) در NET Core.، معادل کدهایی مانند:
using (var sha256 = new SHA256Managed()) 
{ 
   // Crypto code here... 
}
به صورت ذیل درآمده‌است:
public static string GetHash(string text)
{
  using (var sha256 = SHA256.Create())
  {
   var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
   return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
  }
}
البته اگر از یک برنامه‌ی ASP.NET Core استفاده می‌کنید، اسمبلی‌های مرتبط آن به صورت پیش فرض به پروژه الحاق شده‌اند. اما اگر می‌خواهید یک کتابخانه‌ی جدید را ایجاد کنید، نیاز است وابستگی ذیل را نیز به فایل project.json آن اضافه نمائید:
 "dependencies": {
  "System.Security.Cryptography.Algorithms": "4.2.0"
},

به علاوه اگر نیاز به محاسبه‌ی هش حاصل از جمع چندین byte array را دارید، در اینجا می‌توان از الگوریتم‌های IncrementalHash به صورت ذیل استفاده کرد:
using (var md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5))
{
  md5.AppendData(byteArray1, 0, byteArray1.Length);
  md5.AppendData(byteArray2, 0, byteArray2.Length); 
  var hash = md5.GetHashAndReset();
}
این الگوریتم‌ها شامل MD5، SHA1، SHA256، SHA384 و SHA512 می‌شوند.


تولید اعداد تصادفی Thread safe در NET Core.

روش‌های زیادی برای تولید اعداد تصادفی در برنامه‌های دات نت وجود دارند؛ اما مشکل اکثر آن‌ها این است که thread safe نیستند و نباید از آن‌ها در برنامه‌های چند ریسمانی (مانند برنامه‌های وب)، به نحو متداولی استفاده کرد. در این بین تنها کلاسی که thread safe است، کلاس RNGCryptoServiceProvider می‌باشد؛ آن هم با یک شرط:
   private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider();
از کلاس آن باید تنها یک وهله‌ی static readonly در کل برنامه وجود داشته باشد (مطابق مستندات MSDN).
بنابراین اگر در کدهای خود چنین تعریفی را دارید:
 var rand = new RNGCryptoServiceProvider();
اشتباه است و باید اصلاح شود.
در NET Core. این کلاس به طور کامل حذف شده‌است و معادل جدید آن کلاس RandomNumberGenerator است که به صورت ذیل قابل استفاده است (و در عمل تفاوتی بین کدهای آن با کدهای RNGCryptoServiceProvider نیست):
    public interface IRandomNumberProvider
    {
        int Next();
        int Next(int max);
        int Next(int min, int max);
    }

    public class RandomNumberProvider : IRandomNumberProvider
    {
        private readonly RandomNumberGenerator _rand = RandomNumberGenerator.Create();

        public int Next()
        {
            var randb = new byte[4];
            _rand.GetBytes(randb);
            var value = BitConverter.ToInt32(randb, 0);
            if (value < 0) value = -value;
            return value;
        }

        public int Next(int max)
        {
            var randb = new byte[4];
            _rand.GetBytes(randb);
            var value = BitConverter.ToInt32(randb, 0);
            value = value % (max + 1); // % calculates remainder
            if (value < 0) value = -value;
            return value;
        }

        public int Next(int min, int max)
        {
            var value = Next(max - min) + min;
            return value;
        }
    }
در اینجا نیز یک وهله از کلاس RandomNumberGenerator را ایجاد کرده‌ایم، اما استاتیک نیست. علت اینجا است که چون برنامه‌های ASP.NET Core به همراه یک IoC Container توکار هستند، می‌توان این کلاس را با طول عمر singleton معرفی کرد که همان کار را انجام می‌دهد:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.TryAddSingleton<IRandomNumberProvider, RandomNumberProvider>();
و پس از این تنظیم، می‌توان سرویس IRandomNumberProvider را در تمام قسمت‌های برنامه، با کمک تزریق وابستگی‌های آن در سازنده‌ی کلاس‌ها، استفاده کرد و نکته‌ی مهم آن thread safe بودن آن جهت کاربردهای چند ریسمانی است. بنابراین دیگر در برنامه‌های وب خود از new Random استفاده نکنید.


نیاز به الگوریتم‌های رمزنگاری متقارن قوی و معادل بهتر آن‌ها در ASP.NET Core

ASP.NET Core به همراه یکسری API جدید است به نام data protection APIs که روش‌هایی را برای پیاده سازی بهتر الگوریتم‌های هش کردن اطلاعات و رمزنگاری اطلاعات، ارائه می‌دهند و برای مثال ASP.NET Core Identity و یا حتی Anti forgery token آن، در پشت صحنه دقیقا از همین API برای انجام کارهای رمزنگاری اطلاعات استفاده می‌کنند.
برای مثال اگر بخواهید کتابخانه‌ای را طراحی کرده و در آن از الگوریتم AES استفاده نمائید، نیاز است تنظیم اضافه‌تری را جهت دریافت کلید عملیات نیز اضافه کنید. اما با استفاده از data protection APIs نیازی به اینکار نیست و مدیریت ایجاد، نگهداری و انقضای این کلید به صورت خودکار توسط سیستم data protection انجام می‌شود. کلیدهای این سیستم موقتی هستند و طول عمری محدود دارند. بنابراین باتوجه به این موضوع، روش مناسبی هستند برای تولید توکن‌های Anti forgery و یا تولید محتوای رمزنگاری شده‌ی کوکی‌ها. بنابراین نباید از آن جهت ذخیره سازی اطلاعات ماندگار در بانک‌های اطلاعاتی استفاده کرد.
فعال سازی این سیستم نیازی به تنظیمات اضافه‌تری در ASP.NET Core ندارد و جزو پیش فرض‌های آن است. در کدهای ذیل، نمونه‌ای از استفاده‌ی از این سیستم را ملاحظه می‌کنید:
    public interface IProtectionProvider
    {
        string Decrypt(string inputText);
        string Encrypt(string inputText);
    }

namespace Providers
{
    public class ProtectionProvider : IProtectionProvider
    {
        private readonly IDataProtector _dataProtector;

        public ProtectionProvider(IDataProtectionProvider dataProtectionProvider)
        {
            _dataProtector = dataProtectionProvider.CreateProtector(typeof(ProtectionProvider).FullName);
        }

        public string Decrypt(string inputText)
        {
                var inputBytes = Convert.FromBase64String(inputText);
                var bytes = _dataProtector.Unprotect(inputBytes);
                return Encoding.UTF8.GetString(bytes);
        }

        public string Encrypt(string inputText)
        {
            var inputBytes = Encoding.UTF8.GetBytes(inputText);
            var bytes = _dataProtector.Protect(inputBytes);
            return Convert.ToBase64String(bytes);
        }
    }
}
کار با تزریق IDataProtectionProvider در سازنده‌ی کلاس شروع می‌شود. سرویس آن به صورت پیش فرض توسط ASP.NET Core در اختیار برنامه قرار می‌گیرد و نیازی به تنظیمات اضافه‌تری ندارد. پس از آن باید یک محافظت کننده‌ی جدید را با فراخوانی متد CreateProtector آن ایجاد کرد و در آخر کار با آن به سادگی فراخوانی متدهای Unprotect و Protect است که ملاحظه می‌کنید.
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.TryAddSingleton<IProtectionProvider, ProtectionProvider>();
پس از طراحی این سرویس جدید نیز می‌توان وابستگی‌های آن‌را به نحو فوق به سیستم معرفی کرد و از سرویس IProtectionProvider آن در تمام قسمت‌های برنامه (جهت کارهای کوتاه مدت رمزنگاری اطلاعات) استفاده نمود.

مستندات مفصل این API را در اینجا می‌توانید مطالعه کنید.


معادل الگوریتم Rijndael در NET Core.

همانطور که عنوان شد، طول عمر کلیدهای data protection API محدود است و به همین جهت برای کارهایی چون تولید توکن‌ها، رمزنگاری کوئری استرینگ‌ها و یا کوکی‌های کوتاه مدت، بسیار مناسب است. اما اگر نیاز به ذخیره سازی طولانی مدت اطلاعات رمزنگاری شده وجود داشته باشد، یکی از الگوریتم‌های مناسب اینکار، الگوریتم AES است.
الگوریتم Rijndael نگارش کامل دات نت، اینبار نام اصلی آن یا AES را در NET Core. پیدا کرده‌است و نمونه‌ای از نحوه‌ی استفاده‌ی از آن، جهت رمزنگاری و رمزگشایی اطلاعات، به صورت ذیل است:
public string Decrypt(string inputText, string key, string salt)
{
    var inputBytes = Convert.FromBase64String(inputText);
    var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt));
 
    using (var ms = new MemoryStream())
    {
        var alg = Aes.Create();
 
        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);
 
        using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write))
        {
            cs.Write(inputBytes, 0, inputBytes.Length);
        }
        return Encoding.UTF8.GetString(ms.ToArray());
    }
}
 
public string Encrypt(string inputText, string key, string salt)
{
 
    var inputBytes = Encoding.UTF8.GetBytes(inputText);
    var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt));
    using (var ms = new MemoryStream())
    {
        var alg = Aes.Create();
 
        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);
 
        using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write))
        {
            cs.Write(inputBytes, 0, inputBytes.Length);
        }
        return Convert.ToBase64String(ms.ToArray());
    }
}
همانطور که در کدهای فوق نیز مشخص است، این روش نیاز به قید صریح key و salt را دارد. اما روش استفاده‌ی از data protection APIs مدیریت key و salt را خودکار کرده‌است؛ آن هم با طول عمر کوتاه. در این حالت دیگر نیازی نیست تا جفت کلیدی را که احتمالا هیچگاه در طول عمر برنامه تغییری نمی‌کنند، از مصرف کننده‌ی کتابخانه‌ی خود دریافت کنید و یا حتی نام خودتان را به عنوان کلید در کدها به صورت hard coded قرار دهید!
  • #
    ‫۶ سال و ۵ ماه قبل، چهارشنبه ۲۹ فروردین ۱۳۹۷، ساعت ۱۸:۵۶
    خلاصه نکات این مطلب در برنامه‌های ASP.NET Core

    ابتدا بسته‌ی نیوگت DNTCommon.Web.Core را نصب کنید:
    PM> Install-Package DNTCommon.Web.Core
    سپس مثالی از IProtectionProviderService آن‌را در اینجا می‌توانید مشاهده کنید.   
  • #
    ‫۵ سال و ۸ ماه قبل، چهارشنبه ۵ دی ۱۳۹۷، ساعت ۲۰:۱۲
    سلام. من میخوام از کلیدهای  data protection API  در کلاسهای استاتیک استفاده کنم که با کد زیر این کار رو انجام دادم ولی مشکلم اینه که به دلایلی مجبورم یک مقدار ثابت رو دوبار پشت سر هم رمز کنم ولی دفعه دوم که رمز میشه با یک کلید دیگه رمز میکنه. عمر کلید رو چگونه میتونم یکم طولانی‌تر کنم مثلا به اندازه طول عمر session
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddDataProtection();
    var services = serviceCollection.BuildServiceProvider();
    var protectionProvider = ActivatorUtilities.CreateInstance<ProtectionProvider>(services);
    string result = protectionProvider.Encrypt(Str);

      • #
        ‫۵ سال و ۸ ماه قبل، شنبه ۸ دی ۱۳۹۷، ساعت ۱۳:۵۸
        این موردی که شما معرفی کرده اید مربوط به سوال من نیست. من در یک تابع دوبار یک کلمه را از طریق تابع encrypt رمز میکنم که نتیجه هردو فراخوانی متفاوت است درصورتیکه نمیخواهم چنین اتفاقی بیافتد. این چنین حالتی را برای مواقع خاصی لازم دارم. 
        • #
          ‫۵ سال و ۸ ماه قبل، شنبه ۸ دی ۱۳۹۷، ساعت ۱۴:۴۱
          - روش IDataProtectionProvider از الگوریتم ویژه‌ای برای تولید عبارت رمزنگاری شده استفاده می‌کند که ممکن است نتیجه‌ی نهایی تولیدی آن با دفعه‌ی قبل فراخوانی آن یکسان نباشد (نتیجه‌ی رمزنگاری آن فقط شامل عبارت رمزنگاری شده نیست و اطلاعات اضافه‌تری را مخصوص این الگویتم دارد). اما هر دو حالت قابل رمزگشایی هستند و در نهایت استفاده کننده مشکلی را مشاهده نخواهد کرد. یعنی مهم نیست که حاصل ویژه‌ی رمزنگاری آن در دوبار فراخوانی پشت سر هم آن یکی نیست. مهم این است که هر دو مورد به علت داشتن اطلاعات ویژه‌ی اضافه‌تری که در عبارت رمزنگاری شده وجود دارد، توسط متد Unprotect آن قابل رمزگشایی هستند.
          - اگر نیاز به خروجی رمزنگاری شده‌ی همیشه یکسانی را دارید، از قسمت «معادل الگوریتم Rijndael در NET Core.» استفاده کنید.
  • #
    ‫۳ سال قبل، پنجشنبه ۲۱ مرداد ۱۴۰۰، ساعت ۱۲:۱۳
    یک نکته‌ی تکمیلی
    RandomNumberGenerator ای که در این مطلب بحث شد، از NET Core 3.1. به بعد ساده‌تر شده و به همراه متد GetInt32 نیز هست:
    public int GetRandomNumber() => RandomNumberGenerator.GetInt32(1, 1000000);
    • #
      ‫۲ سال و ۶ ماه قبل، جمعه ۲۹ بهمن ۱۴۰۰، ساعت ۱۴:۳۸
      یک نکته: RandomNumberGenerator ای که پیشتر متد ساده کننده‌ی GetInt32 را دریافت کرده بود، GetBytes آن هم به دات نت 6 اضافه شده‌است:
      byte[] randomBytes = RandomNumberGenerator.GetBytes(100);
  • #
    ‫۲ سال و ۷ ماه قبل، شنبه ۲ بهمن ۱۴۰۰، ساعت ۱۷:۰۲
    یک نکته‌ی تکمیلی: ساده شدن روش کار با الگوریتم‌های هش از دات نت 5 به بعد

    همانطور که در این مطلب مشاهده کردید، برای محاسبه‌ی هش SHA256، روش توصیه شده به صورت زیر درآمده‌است:
    using System.Security.Cryptography;
    byte[] data = default; // Some data
    using (SHA256 hash = SHA256.Create())
    {
        byte[] digest = hash.ComputeHash(data);
    }
    این مورد در دات نت 5 به صورت زیر خلاصه شده‌است:
    using System.Security.Cryptography;
    byte[] data = default; // Some data
    byte[] digest = SHA256.HashData(data);
    مزیت این روش نه فقط ساده شدن آن و یا حذف نیاز به using است، بلکه کاهش تعداد تخصیص‌های حافظه‌ی آن نیز هست. یک چنین API ای در دات نت 6 برای HMAC نیز تهیه شده‌است. همچنین برای نمونه الگوریتم هش کردن PBKDF2 در دات نت 6 به صورت زیر ساده شده‌است:
    using System.Security.Cryptography;
    byte[] salt = RandomNumberGenerator.GetBytes(32);
    byte[] prk = Rfc2898DeriveBytes.Pbkdf2(
        userPassword,
        salt,
        iterations: 200_000,
        HashAlgorithmName.SHA256,
        outputLength: 32);