10-07-1395، 12:18 ب.ظ
اخیرا در یک پروژه پایتون نیاز به رمزنگاری داشتم، بخاطر همین جستجو و تحقیق کردم و یکسری تابع سردستی برای رمزنگاری نوشتم.
نظر به اینکه رمزنگاری مقولهء پیچیده و حساسی هست و توش اشتباه خیلی راحت پیش میاد و ضمنا خیلی از منابعی که با سرچ گوگل پیدا میکنید اطلاعات کامل و دقیقی ندارن یا حتی بعضا اشتباهاتی توشون هست، گفتم این توابع رو اینجا بذارم تا هم اگر کسی خواست بدونه در پایتون چطور میشه رمزنگاری حرفه ای انجام داد استفاده کنه و هم اینکه این کدها بتونن بررسی بیشتر توسط بقیه بشن تا اگر پیشنهادی انتقادی ایده ای بهبودی چیزی هست یا ممکنه اشتباهی درشون باشه مشخص بشه.
توضیحات: رمزنگاری از نوع AES128(CBC) + HMAC
خیلی از رمزنگاری ها که شما در نت پیدا میکنید دارای MAC نیستن و این ویژگی رو بنده به نمونه کدهایی که پیدا کردم اضافه کردم. وظیفهء HMAC حفاظت از داده ها در برابر دستکاری یا تغییر تصادفیه. رمزنگاری AES128 (در حالت CBC) به تنهایی Confidentiality (محرمانگی) رو تامین میکنه، اما از تغییرات تصادفی یا کورکورانهء داده های رمز شده جلوگیری نمیکنه (Integrity و Authenticity رو تامین نمیکنه). بنابراین یک رمزنگاری کامل اونه که دارای MAC هم باشه. HMAC این الگوریتم از SHA256 استفاده کرده که البته خروجی اون به 224 بیت Truncate شده. این رو بخاطر کاهش حجم دیتا انجام دادم چون داده های بنده قرار بود مدام بین سرور و کلاینت ها رد و بدل بشن. البته الان که فکر میکنم این کار ضرورتی نداشت :لبخند: اما بهرحال از نظر امنیتی تاجاییکه میدونم مشکلی ایجاد نمیکنه!
راستی تابع str2key برای تبدیل یک رشته به کلید باینری 128 بیتی مورد نیاز برای رمزنگاری AES128 استفاده میشه.
عملا ما یک رشته رو بعنوان کلید به توابع encrypt و decrypt پاس میکنیم. البته ابتدا این توابع رو طوری نوشته بودم که مستقیما کلید باینری 128 بیتی رو بهشون بدیم، ولی بعد دیدم نوشتن رشته های معمولی بعنوان کلید در کد ساده تره، بخاطر همین تابع str2key رو نوشتم. این تابع از رشته هش SHA256 میگیره و بعد خروجی اون رو (دقت کنید خروجی باینری و نه هگز) که 256 بیت است به 128 بیت Truncate میکنه (قبلا در crypto.stackexchange.com در این باره (Truncate کردن خروجی هش) سوال و تحقیق کرده بودم و مطمئن بودم که از نظر امنیتی مشکل جدی نداره).
و اما خب ما این کلیدها رو چطوری باید انتخاب کنیم. خیلی مهمه! چون امنیت رمزنگاری شما هیچوقت از کیفیت کلید شما نمیتونه فراتر بره. مثلا اگر از پسورد abc123 بعنوان کلید استفاده کنید، امنیت خیلی پایین خواهد بود، چون اینطور پسوردها خیلی ضعیف هستن.
نحوهء محاسبهء قدرت پسورد انتخاب شده (البته دقیق بخوایم بگیم اینطور رشته های طولانی و قوی رندوم رو که بعنوان کلید استفاده میشن معمولا پسورد نمی نامند و شاید همون کلید بگیم بهتر باشه) بدین صورت هست که شما باید یک رشتهء رندوم انتخاب کنید و تعداد حالتهای ممکن برای اون رو محاسبه کنید که از یک حداقلی کمتر نباشه. حداقل امنیت درست و حسابی 80 بیت است (البته برای کاربردهایی که خیلی حساس نیستن و نیاز به تامین امنیت برای مدت طولانی (سالها) ندارن)، اما توصیه میشه بیشتر از این باشه و چه بهتر که به همون 128 بیت برسه و با حداکثر امنیت AES128 مچ بشه.
بطور مثال فرض کنید من رشته ای رندوم به این صورت انتخاب میکنم: حروف بزرگ و کوچک انگلیسی+اعداد+علامتهای خاص روی کیبورد. فرض میکنیم طول این رشته 18 کاراکتر باشه.
حالا محاسبهء امنیت این کلید بر حسب بیت به این شکل است: حروف کوچک انگلیسی 26 عدد+حروف بزرگ 26 عدد+اعداد 10 عدد+علامتهای خاص 20 عدد (حدودی یک مقدار کمترش رو گرفتم)=82
حالا 82 رو به توان 18 میرسونیم تا تعداد حالتهای ممکن مشخص بشه: 2.8x10^34
خب اگر یخورده توان های 2 رو محاسبه کنید میبینید که این عدد یک چیزی بین 2 به توان 114 و 2 به توان 115 است، پس یک کلید با امنیت کافی (در کاربردهای عادی که خیلی حساس نیستن و نیاز به محرمانگی طولانی مدت ندارن) محسوب میشه. امنیتش بر حسب بیت تقریبا همون 114 بیت است.
البته اگر دیتای شما واقعا حساس است بطوریکه ممکنه هکرهای قوی با انگیزه و منابع مالی و امکانات قوی تا سالها دنبال رمزگشایی داده های شما باشن، توصیه میکنم امنیت رو به 128 بیت برسونید. ضمنا در این مورد انتخاب کلید قوی تر از 128 بیت فایده ای نداره، چون الگوریتم رمزنگاری ما امنیتش تا 128 است و نه بیشتر. اگر اطلاعاتی که میخوان رمز بشن از نوع فوق محرمانه هستند، مثلا اسناد حکومتی و مربوط به امنیت ملی کشور هستن که باید برای بیش از 10 سال بشه از امنیت رمزنگاری اونها مطمئن بود، اونوقت توصیه میشه از AES256 استفاده بشه. ولی بنده موارد استفاده AES256 رو خیلی کم و انحصاری میبینم و در 99.99% کارهای برنامه نویسان عادی فکر نمیکنم چنین نیازی هیچوقت پیش بیاد، و از اون طرف AES256 از AES128 به میزان قابل توجهی پردازش بیشتری طلب میکنه و کندتره و حجم خروجی اون هم خیلی وقتا بیشتر میشه، بخاطر همین از پیاده سازی و استفاده این الگوریتم صرفنظر کردم. همین AES128 هم در حال حاضر امنیت بسیار زیادی داره و تا سالها امید نمیره حتی قوی ترین کشورها بتونن اون رو بشکنن (مگر اینکه در ظرف چند سال آینده رایانه های کوانتمی قوی ساخته بشن که چنین چیزی با توجه به تحقیقاتی که در این زمینه داشتم و اطلاعاتی که دارم بعید بنظر میاد). باید اینو بدونید وقتی در خیلی از کاربردها از AES256 صحبت میکنن، بیشتر جنبهء تبلیغات و غلو داره تا اینکه در عمل مزیت بحساب آمدنی باشه. این بخاطر مسائل و پارامترهای متعددی هست که در رمزنگاری دخیل هستند. ضمنا خیلی وقتا سیستمهایی که ادعا میکنن از رمزنگاری های خیلی قوی استفاده میکنن، در بخشهای دیگری دچار ضعف هستند که باعث میشه اون رمزنگاری قوی عملا اهمیت و فایده ای نداشته باشه! تقریبا همیشه امنیت سیستمها در بخشهای دیگری خدشه دار و شکسته میشه و خیلی کم به این ربط داره که ما بین رمزنگاری 128 بیت و 256 بیت کدام رو انتخاب کرده ایم. یکی از مسائل حساس دیگر اینه که شما یک کلید قوی با امنیت مناسب تولید کنید، که این کار معمولا توسط توابع CSPRNG انجام میشه.
ضمنا بنده این توابع رو سردستی واسه پروژهء خودم نوشتم، نه بصورت یک کتابخانهء کامل و اصولی از نظر مهندسی نرم افزار و برنامه نویسی، و اینکه اینجا گذاشتم بیشتر برای بدست آوردن ایده و اطلاعات کلی و اینکه از روش کد خودتون رو بنویسید، همونطور که بنده این کدها رو با سرهم کردن و بعضی تغییرات و بهبودها در کدهایی که در نت پیدا کردم ساختم. بهرحال اصول و امنیتش بنظرم درسته.
اوه راستی یادم رفتم بگم! شما ابتدا باید ماجول pycrypto رو برای پایتون خودتون نصب کنید، چون پایتون بصورت پیشفرض این ماجول رو نداره. بنده اول سرچ کردم و مطالبی خوندم مثلا نوشته بودن چطور این ماجول رو تحت لینوکس یا ویندوز کامپایل کنیم، و بنظرم رسید کار طولانی و سختی باشه، ولی دست آخر نسخهء کامپایل شده و آمادهء این ماجول برای ویندوز رو پیدا و دانلود کردم که براحتی نصب شد. ضمنا دقت کنید ظاهرا هر نسخه از پایتون نسخهء کامپایل مخصوص خودش رو میخواد.
نظر به اینکه رمزنگاری مقولهء پیچیده و حساسی هست و توش اشتباه خیلی راحت پیش میاد و ضمنا خیلی از منابعی که با سرچ گوگل پیدا میکنید اطلاعات کامل و دقیقی ندارن یا حتی بعضا اشتباهاتی توشون هست، گفتم این توابع رو اینجا بذارم تا هم اگر کسی خواست بدونه در پایتون چطور میشه رمزنگاری حرفه ای انجام داد استفاده کنه و هم اینکه این کدها بتونن بررسی بیشتر توسط بقیه بشن تا اگر پیشنهادی انتقادی ایده ای بهبودی چیزی هست یا ممکنه اشتباهی درشون باشه مشخص بشه.
کد:
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Hash import SHA256, HMAC
BLOCK_SIZE=16
pad=lambda s: s+((BLOCK_SIZE-len(s)%BLOCK_SIZE)*chr(BLOCK_SIZE-len(s)%BLOCK_SIZE)).encode()
unpad = lambda s : s[:-ord(s[len(s)-1:])]
def str2key(str):
return SHA256.new(str.encode()).digest()[:16]
def encrypt(data, key):
if not len(data):
print('no data to encrypt!')
return None
if not len(key):
print('no encryption key!')
return None
key=str2key(key)
data=pad(data)
iv=Random.new().read(BLOCK_SIZE)
aes=AES.new(key, AES.MODE_CBC, iv)
ct=iv+aes.encrypt(data)
key2=SHA256.new(key).digest()
return HMAC.new(key2, ct, SHA256).digest()[:28]+ct
def decrypt(data, key):
if not len(data):
print('no data to decrypt!')
return None
if not len(key):
print('no decryption key!')
return None
key=str2key(key)
hmac1=data[:28]
ct=data[28:]
key2=SHA256.new(key).digest()
if HMAC.new(key2, ct, SHA256).digest()[:28]!=hmac1:
print('hmac verification failed!')
return None
iv=ct[:BLOCK_SIZE]
aes=AES.new(key, AES.MODE_CBC, iv)
return unpad(aes.decrypt(ct[BLOCK_SIZE:]))
توضیحات: رمزنگاری از نوع AES128(CBC) + HMAC
خیلی از رمزنگاری ها که شما در نت پیدا میکنید دارای MAC نیستن و این ویژگی رو بنده به نمونه کدهایی که پیدا کردم اضافه کردم. وظیفهء HMAC حفاظت از داده ها در برابر دستکاری یا تغییر تصادفیه. رمزنگاری AES128 (در حالت CBC) به تنهایی Confidentiality (محرمانگی) رو تامین میکنه، اما از تغییرات تصادفی یا کورکورانهء داده های رمز شده جلوگیری نمیکنه (Integrity و Authenticity رو تامین نمیکنه). بنابراین یک رمزنگاری کامل اونه که دارای MAC هم باشه. HMAC این الگوریتم از SHA256 استفاده کرده که البته خروجی اون به 224 بیت Truncate شده. این رو بخاطر کاهش حجم دیتا انجام دادم چون داده های بنده قرار بود مدام بین سرور و کلاینت ها رد و بدل بشن. البته الان که فکر میکنم این کار ضرورتی نداشت :لبخند: اما بهرحال از نظر امنیتی تاجاییکه میدونم مشکلی ایجاد نمیکنه!
راستی تابع str2key برای تبدیل یک رشته به کلید باینری 128 بیتی مورد نیاز برای رمزنگاری AES128 استفاده میشه.
عملا ما یک رشته رو بعنوان کلید به توابع encrypt و decrypt پاس میکنیم. البته ابتدا این توابع رو طوری نوشته بودم که مستقیما کلید باینری 128 بیتی رو بهشون بدیم، ولی بعد دیدم نوشتن رشته های معمولی بعنوان کلید در کد ساده تره، بخاطر همین تابع str2key رو نوشتم. این تابع از رشته هش SHA256 میگیره و بعد خروجی اون رو (دقت کنید خروجی باینری و نه هگز) که 256 بیت است به 128 بیت Truncate میکنه (قبلا در crypto.stackexchange.com در این باره (Truncate کردن خروجی هش) سوال و تحقیق کرده بودم و مطمئن بودم که از نظر امنیتی مشکل جدی نداره).
و اما خب ما این کلیدها رو چطوری باید انتخاب کنیم. خیلی مهمه! چون امنیت رمزنگاری شما هیچوقت از کیفیت کلید شما نمیتونه فراتر بره. مثلا اگر از پسورد abc123 بعنوان کلید استفاده کنید، امنیت خیلی پایین خواهد بود، چون اینطور پسوردها خیلی ضعیف هستن.
نحوهء محاسبهء قدرت پسورد انتخاب شده (البته دقیق بخوایم بگیم اینطور رشته های طولانی و قوی رندوم رو که بعنوان کلید استفاده میشن معمولا پسورد نمی نامند و شاید همون کلید بگیم بهتر باشه) بدین صورت هست که شما باید یک رشتهء رندوم انتخاب کنید و تعداد حالتهای ممکن برای اون رو محاسبه کنید که از یک حداقلی کمتر نباشه. حداقل امنیت درست و حسابی 80 بیت است (البته برای کاربردهایی که خیلی حساس نیستن و نیاز به تامین امنیت برای مدت طولانی (سالها) ندارن)، اما توصیه میشه بیشتر از این باشه و چه بهتر که به همون 128 بیت برسه و با حداکثر امنیت AES128 مچ بشه.
بطور مثال فرض کنید من رشته ای رندوم به این صورت انتخاب میکنم: حروف بزرگ و کوچک انگلیسی+اعداد+علامتهای خاص روی کیبورد. فرض میکنیم طول این رشته 18 کاراکتر باشه.
حالا محاسبهء امنیت این کلید بر حسب بیت به این شکل است: حروف کوچک انگلیسی 26 عدد+حروف بزرگ 26 عدد+اعداد 10 عدد+علامتهای خاص 20 عدد (حدودی یک مقدار کمترش رو گرفتم)=82
حالا 82 رو به توان 18 میرسونیم تا تعداد حالتهای ممکن مشخص بشه: 2.8x10^34
خب اگر یخورده توان های 2 رو محاسبه کنید میبینید که این عدد یک چیزی بین 2 به توان 114 و 2 به توان 115 است، پس یک کلید با امنیت کافی (در کاربردهای عادی که خیلی حساس نیستن و نیاز به محرمانگی طولانی مدت ندارن) محسوب میشه. امنیتش بر حسب بیت تقریبا همون 114 بیت است.
البته اگر دیتای شما واقعا حساس است بطوریکه ممکنه هکرهای قوی با انگیزه و منابع مالی و امکانات قوی تا سالها دنبال رمزگشایی داده های شما باشن، توصیه میکنم امنیت رو به 128 بیت برسونید. ضمنا در این مورد انتخاب کلید قوی تر از 128 بیت فایده ای نداره، چون الگوریتم رمزنگاری ما امنیتش تا 128 است و نه بیشتر. اگر اطلاعاتی که میخوان رمز بشن از نوع فوق محرمانه هستند، مثلا اسناد حکومتی و مربوط به امنیت ملی کشور هستن که باید برای بیش از 10 سال بشه از امنیت رمزنگاری اونها مطمئن بود، اونوقت توصیه میشه از AES256 استفاده بشه. ولی بنده موارد استفاده AES256 رو خیلی کم و انحصاری میبینم و در 99.99% کارهای برنامه نویسان عادی فکر نمیکنم چنین نیازی هیچوقت پیش بیاد، و از اون طرف AES256 از AES128 به میزان قابل توجهی پردازش بیشتری طلب میکنه و کندتره و حجم خروجی اون هم خیلی وقتا بیشتر میشه، بخاطر همین از پیاده سازی و استفاده این الگوریتم صرفنظر کردم. همین AES128 هم در حال حاضر امنیت بسیار زیادی داره و تا سالها امید نمیره حتی قوی ترین کشورها بتونن اون رو بشکنن (مگر اینکه در ظرف چند سال آینده رایانه های کوانتمی قوی ساخته بشن که چنین چیزی با توجه به تحقیقاتی که در این زمینه داشتم و اطلاعاتی که دارم بعید بنظر میاد). باید اینو بدونید وقتی در خیلی از کاربردها از AES256 صحبت میکنن، بیشتر جنبهء تبلیغات و غلو داره تا اینکه در عمل مزیت بحساب آمدنی باشه. این بخاطر مسائل و پارامترهای متعددی هست که در رمزنگاری دخیل هستند. ضمنا خیلی وقتا سیستمهایی که ادعا میکنن از رمزنگاری های خیلی قوی استفاده میکنن، در بخشهای دیگری دچار ضعف هستند که باعث میشه اون رمزنگاری قوی عملا اهمیت و فایده ای نداشته باشه! تقریبا همیشه امنیت سیستمها در بخشهای دیگری خدشه دار و شکسته میشه و خیلی کم به این ربط داره که ما بین رمزنگاری 128 بیت و 256 بیت کدام رو انتخاب کرده ایم. یکی از مسائل حساس دیگر اینه که شما یک کلید قوی با امنیت مناسب تولید کنید، که این کار معمولا توسط توابع CSPRNG انجام میشه.
ضمنا بنده این توابع رو سردستی واسه پروژهء خودم نوشتم، نه بصورت یک کتابخانهء کامل و اصولی از نظر مهندسی نرم افزار و برنامه نویسی، و اینکه اینجا گذاشتم بیشتر برای بدست آوردن ایده و اطلاعات کلی و اینکه از روش کد خودتون رو بنویسید، همونطور که بنده این کدها رو با سرهم کردن و بعضی تغییرات و بهبودها در کدهایی که در نت پیدا کردم ساختم. بهرحال اصول و امنیتش بنظرم درسته.
اوه راستی یادم رفتم بگم! شما ابتدا باید ماجول pycrypto رو برای پایتون خودتون نصب کنید، چون پایتون بصورت پیشفرض این ماجول رو نداره. بنده اول سرچ کردم و مطالبی خوندم مثلا نوشته بودن چطور این ماجول رو تحت لینوکس یا ویندوز کامپایل کنیم، و بنظرم رسید کار طولانی و سختی باشه، ولی دست آخر نسخهء کامپایل شده و آمادهء این ماجول برای ویندوز رو پیدا و دانلود کردم که براحتی نصب شد. ضمنا دقت کنید ظاهرا هر نسخه از پایتون نسخهء کامپایل مخصوص خودش رو میخواد.