تالار گفتمان nCIS.ir

نسخه‌ی کامل: رمزنگاری AES در پایتون
شما در حال مشاهده نسخه آرشیو هستید. برای مشاهده نسخه کامل کلیک کنید.
اخیرا در یک پروژه پایتون نیاز به رمزنگاری داشتم، بخاطر همین جستجو و تحقیق کردم و یکسری تابع سردستی برای رمزنگاری نوشتم.
نظر به اینکه رمزنگاری مقولهء پیچیده و حساسی هست و توش اشتباه خیلی راحت پیش میاد و ضمنا خیلی از منابعی که با سرچ گوگل پیدا میکنید اطلاعات کامل و دقیقی ندارن یا حتی بعضا اشتباهاتی توشون هست، گفتم این توابع رو اینجا بذارم تا هم اگر کسی خواست بدونه در پایتون چطور میشه رمزنگاری حرفه ای انجام داد استفاده کنه و هم اینکه این کدها بتونن بررسی بیشتر توسط بقیه بشن تا اگر پیشنهادی انتقادی ایده ای بهبودی چیزی هست یا ممکنه اشتباهی درشون باشه مشخص بشه.

کد:
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 رو برای پایتون خودتون نصب کنید، چون پایتون بصورت پیشفرض این ماجول رو نداره. بنده اول سرچ کردم و مطالبی خوندم مثلا نوشته بودن چطور این ماجول رو تحت لینوکس یا ویندوز کامپایل کنیم، و بنظرم رسید کار طولانی و سختی باشه، ولی دست آخر نسخهء کامپایل شده و آمادهء این ماجول برای ویندوز رو پیدا و دانلود کردم که براحتی نصب شد. ضمنا دقت کنید ظاهرا هر نسخه از پایتون نسخهء کامپایل مخصوص خودش رو میخواد.
رمزنگاری علم کاملا گسترده و پیچیده ایست. تاحدی که از نظر سطح علمی شباهت با ریاضی و فیزیک داره (جالب اینکه از هردوی این علوم هم درش استفاده گسترده ای میشه؛ بخصوص ریاضیات).
بنده هم که از این چیزها سردرمیارم چون مدت دو سال حداقل یکسره در حال مطالعه و تحقیق و یادگیری در این زمینه بودم و بهش علاقمند بودم.
رمزنگاری علم بسیار حساس و مهمی در دنیای امروز است و در خیلی کشورها الگوریتم های رمزنگاری قوی جزیی از مقوله های جنگ افزاری طبقه بندی میشن و حتی روی صادرات اونها هم بعضا محدودیت هایی وجود داره.
اکثریت رمزنگاری هایی که برنامه نویسان عادی یا افراد متخصصان طراحی و استفاده یا معرفی میکنن، از ضعف های جدی رنج میبرن و بعضا امنیت اونها در حد واقعا کمی هست (در مقایسه با حداقل و حداکثر امنیتی که در دنیای امروز مطرح هست و میشه دست یافت)، و این مسئله بخاطر پیچیدگی و ظرافت زیاد این مقوله است و اینکه اشتباه کردن در اون راحت و زیاد پیش میاد و وقتی اشتباهی هم رخ میده از ظواهر به هیچ وجه نمیشه متوجه اون اشتباه و عدم امنیت شد (بخصوص برای افرادی که متخصص این رشته نیستن). در باگ های عادی و غیرامنیتی و غیررمزنگاری، برنامه نویس از روی مشکلاتی که در کارکرد برنامه رخ میدن متوجه وجود باگ و مجبور به رفع اونها میشه، ولی در امنیت و بخصوص در فیلد رمزنگاری، باگی مشاهده نمیشه و در کارکرد عادی برنامه اختلالی دیده نمیشه و حتی ممکنه تا سالها یا برای مدت نامحدودی هیچکس متوجه عدم امنیت برنامه هم نشه، و حتی در تست های نفوذ و با استفاده از برنامه های اسکنر امنیتی هم نمیشه متوجه این اشتباهات و حفره ها شد.
الان من خودم این حرفا رو که داشتم میزدم یاد یه نکته ای افتادم که در این توابع رعایت نکردم، البته چون اون نکته در برنامهء بنده مطرح نبود و گفتم که این کدها رو سردستی و با عجله برای کاربرد مورد خودم نوشتم.
البته این نکته از موارد ریز هست و تنها در بعضی موارد و شرایط خاص جدی میشه و حتی در بعضی از کتابخانه های رمزنگاری هم رعایت نشده (بخصوص کتابخانه هایی که خیلی جدید نیستن)، ولی بهرحال سعی میکنم نسخهء جدیدی از این کدها رو که این نکته رو هم پوشش دادن ارائه کنم.
خب توابع رمزنگاری رو بهبود دادم:

from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Hash import SHA256, HMAC
from hmac import compare_digest

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()[:BLOCK_SIZE]

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()+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[:32]
 ct=data[32:]
 
 key2=SHA256.new(key).digest()
 
 if not compare_digest(HMAC.new(key2, ct, SHA256).digest(), 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:]))

بهبودها:
- خروجی کامل hmac-sha256 استفاده میشه (دیگه truncate نمیشه).
- از تابع compare_digest که پایتون در ماجول hmac در اختیار گذاشته برای مقایسه صحت hmac استفاده میشه.
توضیح اینکه این تابع طوری مقایسه میکنه که الگوریتم استفاده شده constant time است و زمان صرف شده برای مقایسه بستگی به چگونگی تفاوت مقدار ورودی ها نداره، و بنابراین امکان حمله با استفاده از آنالیزهای زمانی رو از بین میبره.
این هم فایل نصب ماجول pycrypto روی ویندوز که البته برای پایتون نسخهء 3.3 است.