آموزش استفادهء ساده از رمزنگاری متقارن (AES128-CBC)

اخیرا برای پروژهء خودم دنبال کتابخانهء رمزنگاری مناسبی بودم. البته در PHP اکستنشن mcrypt وجود داره، ولی ممکنه روی بعضی سرورها و یا نسخه های قدیمی تر نصب یا فعال نباشه. بنابراین دنبال پیاده سازیهایی خالص در PHP بودم که نیاز به اکستنشن نداشته باشن.

از بین چند مورد یکی رو پیدا کردم که بنظرم از همه بهتره: http://phpseclib.sourceforge.net

مزایاش نسبت به بقیهء موارد:
- بجای فقط یک الگوریتم رمزنگاری یه پروژهء و کتابخانهء نسبتا گسترده هست که شامل الگوریتم ها و پروتکل های مختلفی میشه (مثلا حتی پروتکل SSH رو داره).
- بر اساس بنچمارک و ادعای خودش سریعترین پیاده سازی رو در بین بقیهء پیاده سازیهای PHP داره.
- اگر mcrypt در دسترس باشه از mcrypt استفاده میکنه (که سرعتش بهرحال بالاتره)، وگرنه از پیاده سازی داخلی خودش استفاده میکنه. و همینطور برای بقیهء الگوریتم ها؛ بطور مثال اگر تابع hash در دسترس باشه از اون برای الگوریتم هش استفاده میکنه، وگرنه از پیاده سازی داخلی خودش.

شما میتونید این پروژه رو دانلود کنید (البته مشمول تحریم هست) یا کلاسها و الگوریتم هایی رو که لازم دارید از SVN اش دریافت کنید (خوشبختانه به این بخش دسترسی هست و نیازی به هیلترشکن ندارید). اگر وارد باشید میفهمید هرچیزی چیه و چطور باید ازش استفاده کرده. بعضی کلاسها به کلاسها و فایلهای دیگری وابسته هستن.

اما من برای پروژهء خودم چنتا الگوریتم و کلاسها و تابعی رو که برای رمزنگاری بازگشت‌پذیر متقارن لازم داشتم در یک فایل ادغام کردم. فکر میکنم به این شکل برای شما و آموزش این تاپیک هم خیلی بهتره و ساده تر میشه؛ بنابراین یک فایل رو که الان از ادغام چند فایل دیگر این پروژهء درست کردم ضمیمهء این پست میکنم و مثالها با استفاده از اون فایل انجام میشه. اسم فایل رو گذاشتم crypto.php. دانلود فایل zip
البته ناگفته نماند یه چنتا تغییر برای این کار و دو سه تا تابع کوچک هم از خودم اضافه کردم.
بطور مثال متدهای IvEncrypt و IvDecrypt رو خودم به کلاس Crypt_AES اضافه کردم.
البته بابت امنیت تغییراتی که بنده ایجاد کردم و توابع اضافه شده چندان نگران نباشید چون هستهء کار با الگوریتم های اصلی خود کتابخانه انجام میشه و توابع بنده بیشتر جنبهء خلاصه سازی و ساده سازی مراحل رو دارن. مثلا بجای اینکه چند مرحله و چند خط کد بنویسیم که احتمالا بیشتر افراد سردرنمیارن چی هست و چطوری چکار میکنه، کافیه از یک خط و یک تابع استفاده کنیم.

خب یه مثال از رمزنگاری متقارن با استفاده از الگوریتم AES با طول کلید 128 بیت و در مد CBC:

<?php

require_once 'crypto.php';

$plaintext='a secret string!';

$aes = new Crypt_AES();//default: 128 bits - CBC mode

$key=random_bytes(16);

$aes->setKey($key);

$ciphertext=$aes->IvEncrypt($plaintext);

echo 'Plaintext: ', $plaintext, '<br>';
echo 'key: ', bin2hex($key), '<br>';
echo 'chiphertext: ', bin2hex($ciphertext), '<br>';

$plaintext2=$aes->IvDecrypt($ciphertext);

echo 'Plaintext2: ', $plaintext2, '<br>';

?>

خروجی برنامه چیزی مشابه این خواهد بود:

Plaintext: a secret string!
key: 94fe096d309b482af01812a037561d21
chiphertext: 833d1a1a7dd843f93561baeb448c69885501856804fb8deac24177ba18bdf0ff6c6ec6d1a1690c86bc8b78ed2c46fa30
Plaintext2: a secret string!

Plaintext متن اصلی ما بوده که میخواستیم رمز کنیم.
key کلید رمزنگاری هست که بصورت رندوم توسط تابع random_bytes ایجاد شده و حاوی 16 بایت (معادل 128 بیت) با مقدار تصادفی هست.
وقتی ما کلید یک الگوریتم رمزنگاری رو بصورت مستقیم تعیین میکنیم، مثلا اگر 128 بیت باشه، باید 128 بیت یا 16 بایت بهش بدیم. تعیین مستقیم کلید با پسورد فرق داره. بعد نمونه کد برای استفاده از متد مربوط به تعیین پسورد رو خواهیم دید. وقتی بجای کلید پسورد میدیم، پسورد توسط الگوریتم مخصوصی بصورت داخلی تبدیل به یک کلید (با طول 128 بیت، 256 بیت و غیره) میشه و این کلید هست که برای رمزنگاری استفاده میشه.
در خیلی کاربردها از پسورد استفاده میشه، اما در خیلی جاها هم نیازی به تعیین دستی پسورد نیست و برنامه خودش بصورت خودکار یک کلید تصادفی تولید میکنه (مثل کد مثال ما). امنیت کلید معمولا خیلی بیشتر از پسورد هست، چون پسورد توسط انسان انتخاب میشه و معمولا طول و آنتروپی کمی داره و میتونه بیش از حد ضعیف باشه، اما مثلا یک کلید 128 بیتی که رندوم تولید میشه معادل یک پسورد 16 کاراکتری کاملا رندوم هست که امنیت خیلی بالایی داره.
chiphertext خروجی رمز ما هست که میتونیم با خیال راحت از اینکه کسی نمیتونه اون رو رمزگشایی کنه منتقل یا ذخیرش کنیم. البته chiphertext خروجی از الگوریتم ما در فرمت باینری هست، ولی چون ما میخواستیم اون رو در مرورگر توسط دستور echo نشان بدیم، با استفاده از تابع bin2hex اون رو تبدیل به نمایش متنی در مبنای 16 کردیم. بخاطر همین تعداد بایتهای متن نمایش داده شده دوبرابر chiphertext اصلی شده، چون در نمایش هگز، مقدار هر بایت با دو کاراکتر (هر کاراکتر از 0 تا F) نمایش داده میشه.

از تبدیل به هگز میشه برای درج متن رمزی در جاهایی مثل صفحهء HTML (مثلا در یک hidden input)، ذخیره در کوکی، یا درج در پارامترهای URL استفاده کرد. به این شکل خروجی برنامه هم خوانا خواهد بود و هم از ایجاد مشکل با فرمت و کاراکترهای مجاز (مثلا در URL نمیتونیم هر بایت/کاراکتری رو همینطوری درج کنیم) جلوگیری میشه.

نکته: بجای تبدیل به هگز از تبدیل به Base64 هم میشه استفاده کرد که حجمش کمتر هم هست، ولی هگز از نظر خوانایی بهتره و موقع تست و دیباگ هم کمک میکنه.

میتونید مقدار ciphertext رو در جاهای دیگری مثل فایل یا داخل سشن همونطور تبدیل نشده و به فرمت باینری هم ذخیره کنید. چون فایل یا سشن (یا یک ستون با نوع دادهء مناسب در دیتابیس) قابلیت ذخیرهء رشته های باینری رو هم دارن.
در خط آخر برنامه ciphertext رو با استفاده از متد IvDecrypt دوباره به متن اولیه برگردوندیم و نمایش دادیم تا از صحت عمل رمزکردن و رمزگشایی اطمینان حاصل کنیم.

دقت کنید چون ما تمام این کارها رو در یک درخواست انجام دادیم و قبلا یک نمونه از کلاس Crypt_AES رو داشتیم و کلید هم قبلا ست شده بود، برای Decrypt کردن نیاز به انجام عمل دیگری نداشتیم؛ در غیر اینصورت باید مراحل ایجاد کلاس و بعد هم ست کردن کلید صحیح رو (که حتما کلید رمزنگاری رو قبلا در جایی ذخیره کردیم) انجام میدادیم.

چون ما موقع ایجاد نمونه از کلاس Crypt_AES پارامتری رو تعیین نکردیم و بعدش هم تنظیمی انجام ندادیم، بصورت پیشفرض از کلید و طول بلاک 128 بیتی و مد CBC استفاده میشه. مد CBC باید با یک IV رندوم استفاده بشه که متدهایی که بنده اضافه کردم این کار رو بصورت خودکار برای شما انجام میدن.
نقش IV این هست که اگر دیتای یکسانی رو چند بار با کلید یکسانی رمز کنید، متن رمز شده در هر بار کاملا متفاوت خواهد بود و نفوذگر نمیتونه از روی متن رمزی به یکسان بودن دیتا و کلید پی ببره.
ضمنا طول رمز خروجی این الگوریتم همیشه ضریبی از طول بلاک اون هست. یعنی مثلا 256 بیت (32 بایت) برابر دو بلاک 128 بیتی، 384 بیت (48 بایت) برابر سه بلاک، و غیره. هر بلاک 128 بیت یا 16 بایت میشه. چون ما یک بلاک IV رو هم به ابتدای رمز اضافه میکنیم، حدقل طول خروجی، دو بلاک معادل 32 بایت خواهد بود، حتی اگر رشته ای به طول یک کاراکتر رو رمز کنیم. طول پسورد تاثیری روی طول خروجی نداره و پسورد همیشه به یک کلید 128 بیتی تبدیل میشه (البته در AES128 که ما استفاده کردیم).
اینکه از روی طول خروجی نمیشه طول دقیق متن اولیه رو فهمید خودش در خیلی جاها یک مزیت امنیتی محسوب میشه یا حتی یک ویژگی لازمه.

ما در اینجا از AES128 استفاده کردیم. اما میتونستیم از طول کلید و طول بلاک 256 بیت هم استفاده کنیم، اما 128 هم برای کاربردهای معمولی کاملا امن محسوب میشه (حداقل تا 20 سال آینده کسی به کرک شدنش فکر نمیکنه؛ ضمنا اگر از پسورد استفاده کنید بهرحال معمولا کلید شما حتی از 128 بیت هم ضعیفتره).

راستی چون صحبت از تبدیل خروجی باینری به هگز کردم باید روش تبدیل معکوس یعنی از هگز به باینری رو هم نشون بدم، چون باید اول این کار رو انجام بدید تا بتونید رشته رو مجددا رمزگشایی کنید:

<?php

require_once 'crypto.php';

$plaintext='a secret string!';

$aes = new Crypt_AES();

$key=random_bytes(16);

$aes->setKey($key);

$hex_ciphertext=bin2hex($aes->IvEncrypt($plaintext));

$ciphertext=pack('H*', $hex_ciphertext);

$plaintext2=$aes->IvDecrypt($ciphertext);

echo 'Plaintext2: ', $plaintext2, '<br>';

?>

همونطور که میبینید این کار با استفاده از تابع pack براحتی انجام میشه.

یه نمونه هم از استفاده از یک پسورد ثابت بجای یک کلید رندوم:

<?php

require_once 'crypto.php';

$plaintext='a secret string!';

$aes = new Crypt_AES();

$aes->setPassword('MyPassword');

echo 'Plaintext: ', $plaintext, '<br>';

echo 'key derived from password: ', bin2hex($aes->key), '<br>';

$ciphertext=$aes->IvEncrypt($plaintext);

echo 'chiphertext: ', bin2hex($ciphertext), '<br>';

$plaintext2=$aes->IvDecrypt($ciphertext);

echo 'Plaintext2: ', $plaintext2, '<br>';

?>

نمونه خروجی:

Plaintext: a secret string!
key drived from password: b92f1896edb7e14e2bd1a4b0471fb0aec057a460
chiphertext: d8151f1b9e415f4cc2993aacb193d96368db40be4c319ba9b1469517c69ed9bf9473ee344d84b34e81ed37a1f85462cb
Plaintext2: a secret string!

ما در این مثال کلید تولید شده بر اساس پسورد رو هم نشون دادیم.
چون پسورد ثابت هست، وقتی صفحه رو رفرش کنید کلید عوض نمیشه (چون کلید از پسورد مشتق میشه)، ولی مشاهده میکنید که متن رمزی در هربار کاملا تغییر میکنه؛ این نتیجهء استفاده از یک IV رندوم هست که البته این بخش از کار در داخل تابع IvEncrypt انجام میشه.
توجه کنید که IV بصورت رمزنشده در ابتدای متن رمز شده اضافه میشه. IV چیزی نیست که نیازی باشه بصورت محرمانه نگه داشته بشه، و برای رمزگشایی هم بهش نیاز دارید، بنابراین اون رو به ابتدای متن رمزی اضافه میکنیم تا همه جا همراهش باشه (معمولا این کار رو میکنن، اما میشه اون رو بصورت جداگانه هم ذخیره کرد).
امنیت متن رمزی شما بر محرمانه باقی موندن پسورد و کلید استواره.

—————–

خب یک ضعفی که در رمزنگاری تنها وجود داره اینه که اگر کسی متن رمزی رو دستکاری کنه، این دستکاری شدن آشکار نمیشه. در بعضی کاربردها این مسئله مهم نیست اما در بعضی کاربردها هم مهمه.
بنابراین بنده توابع جدیدی رو به کلاس رمزنگاری اضافه کردم که از HMAC هم استفاده میکنن. استفاده از HMAC باعث میشه کوچکترین تغییری در دیتای رمزشده آشکارسازی بشه.
اول یک مثال از دستکاری ای رو نشون میدم که توسط توابع قبلی آشکار نمیشه:

<?php

require_once 'crypto.php';

$plaintext="a secret string!";

$aes = new Crypt_AES();

$key=random_bytes(16);

$aes->setKey($key);

$ciphertext=$aes->IvEncrypt($plaintext);

echo 'Plaintext: ', $plaintext, '<br>';
echo 'key: ', bin2hex($key), '<br>';
echo 'chiphertext: ', bin2hex($ciphertext), '<br>';

$ciphertext[14]=97;
$ciphertext[6]=100;

$plaintext2=$aes->IvDecrypt($ciphertext);

echo 'Plaintext2: ', var_dump($plaintext2);

?>

همونطور که میبینید ما دو بایت از دیتای رمز شده رو دستکاری کردیم، و این باعث میشه موقع رمزگشایی رشتهء اولیه بصورت دقیق و دست نخورده حاصل نشه. برنامهء ما متوجه این دستکاری نمیشه.

حالا این کد فرقش اینه که از توابع دارای HMAC استفاده میکنه:

<?php

require_once 'crypto.php';

$plaintext="a secret string!";

$aes = new Crypt_AES();

$key=random_bytes(16);

$aes->setKey($key);

$ciphertext=$aes->IvEncryptHmac($plaintext);

echo 'Plaintext: ', $plaintext, '<br>';
echo 'key: ', bin2hex($key), '<br>';
echo 'chiphertext: ', bin2hex($ciphertext), '<br>';

$ciphertext[14]=97;
$ciphertext[6]=100;

$plaintext2=$aes->IvDecryptHmac($ciphertext);

echo 'Plaintext2: ', var_dump($plaintext2);

?>

همونطور که در نتیجهء اجرای این کد مشاهده میکنید، درصورتیکه دیتای رمز تغییر کرده باشه، متد IvDecryptHmac مقدار false رو برمیگردونه و به این شکل میتونیم بفهمیم که دیتای رمزی صحیح نیست.

بعنوان توضیحات تکمیلی باید بگم که هزینهء استفاده از HMAC اینه که طول خروجی رمز ما به میزان 20 بایت یا 160 بیت اضافه میشه. ضمنا HMAC استفاده شده از تابع هش SHA1 استفاده میکنه (از انواع دیگر الگوریتمهای هش هم میشه استفاده کرد)، و بخاطر همین خروجی 160 بیت هست چون طول خروجی تابع SHA1 برابر 160 بیت است.

1 دیدگاه در “آموزش استفادهء ساده از رمزنگاری متقارن (AES128-CBC)

  1. بازپینگ: علم خوره

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>