Mekaeil
Mekaeil
خواندن ۸ دقیقه·۱ سال پیش

Encryption و Decryption در لاراول

در این مقاله در رابطه با Encryption و Decryption در لاراول صحبت میکنیم و اینکه چطور یک مقدار توسط لاراول Encrypt می‌شود، در نهایت در مورد نحوه Encrypt و Decrypt فایلها و انتقال آنها به سرور دیگری بحث میکنیم.

چرا ما به Encryption داده نیاز داریم

همانطور که میدانید ما برای حفظ ایمنی وبسایت و امنیت اکانتهای کاربران حتما باید رمز عبورهای کاربران را در دیتابیس به صورت هش شده ذخیره کنیم و اگر در وبسایتی ثبت نام کردید و رمز عبور را برای شما ایمیل کرد اولین کاری که میکنید این باشه که اکانتتون رو حذف کنید و از سرویسهاش استفاده نکنید چونکه این وبسایت هیچ کدام از نکات امنیتی و حریم خصوصی کاربران را رعایت نمیکند.

همچنین اگر وبسایت ما خدماتی را به کاربران ارایه می‌دهد و اطلاعاتی همچون مشخصات فردی کاربر را ذخیره می‌کنیم و در کنار آنها فایلهایی همچون کارت ملی و آواتار کاربر را ذخیره میکنیم پس بهتر است فایلها و اطلاعات کاربر را Encrypt کنیم که اگر در آینده به هر دلیلی دیتابیس و وبسایت ما هک شد این اطلاعات در اختیار هکرها قرار داده نشود.

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

چگونه داده‌ها را Encrypt کنیم

رمز عبور را به صورت زیر و هش شده در دیتابیس ذخیره کنیم.

bcrypt($password)

اما یک سوالی که مطرح می‌شود این است که وقتی من یک عبارتی مثلا اسم Mekaeil را Encrypt کنم در هر زمانی یک هش ثابت برای من ایجاد می‌شود؟ این موضوع را در Tinker لاراول تست میکنیم که تصویر آن را به صورت زیر مشاهده می‌کنید:

پس در واقع اگر از متد bcrypt برای هش کردن رمز عبور یا هر چیزی استفاده کنیم مطابق تصویر فوق اینکار انجام می‌شود که در هیچ حالتی مشابه نیست چونکه در هر سری یک salt به صورت رندم تولید می‌شود.

برای مطالعه بیشتر مقاله bcrypt - wikipedia را مطالعه کنید.

خب سوال بعدی که مطرح می‌شود این است حالا که در هر سری مقدار هش شده متفاوت است چطور هنگام ورود به اکانت این مقدار را مطابقت دهیم و به کاربر اجازه ورود دهیم؟!

Bcrypt در هنگام هش کردن یک salt با مقدار 128 بیت به صورت random تولید می کند. این salt بخشی از هش می شود، از این رو ما همیشه یک مقدار هش متفاوت برای همان رشته ورودی دریافت می کنیم. salt تصادفی در واقع برای جلوگیری از حملات brute-force و همچنین برای جلوگیری از حملات rainbow table به صورت رندم تولید میشود. "هش" تولید شده توسط bcrypt فقط هش نیست. بلکه هش و salt بهم پیوسته است.

مطابق تصویر بالا مقدار cost که به اسم rounds در لاراول آن را میشناسیم میزان cpu مصرفی برای تولید هش را مشخص میکند از این رو در محیط تست این مقدار به مقدار ۴ کاهش پیدا میکند تا میزان cpu مصرفی به حداقل برسد. در لاراول با بررسی فایل config/hashing.php میتوانیم الگوریتم هش کردن و مقدار rounds را تغییر دهیم.

برای اینکه بتوانیم مقادیر هش شده را مطابقت دهیم طبق الگوریتم Bcrypt میتوانیم این مقادیر را با متدهای فراهم شده توسط این الگوریتم مقایسه کنیم. در لاراول این امکان با متد Hash::check فراهم شده است، برای اعتبارسنجی کاربر و مطابقت رمز عبور به صورت زیر اقدام می‌کنیم:

// Auth Login $credentials = [ 'email' => $request->username,'password' => $request->password]; Auth::guard($guard)->attempt($credentials); // Bcrypt $hash1 = bcrypt('secret') $hash2 = bcrypt('secret') Hash::check('secret', $hash1) Hash::check('secret', $hash2)

چگونه فایل‌ها را Encrypt کنیم

استفاده از متدهای Encrypt لاراول برای فایلهای با حجم کم میتواند مناسب باشد ولی وقتی که تعداد فایلها و حجم آنها زیاد می‌شود با توجه به این محتوای فایلها (File Content) در مموری باید لود شود و این فرآیند باعث ایجاد مشکل خواهد شد و کار اصولی و استانداردی نیست. برای آپلود فایلها از پکیج file-vault استفاده میکنیم که در ادامه مراحل نصب و استفاده را توضیح میدهیم. ولی نکته مهم این است که ما برای فایلهای با حجم بالا باید فایلها را به صورت Chunk شده آپلود کنیم که برای اینکار میتوانیم از یک پکیج قوی به اسم pion/laravel-chunk-upload استفاده کنیم.

ابتدا پکیج file vault را با استفاده از composer نصب میکنیم.

composer require soarecostin/file-vault

پس از نصب با اجرای دستور زیر فایل config را به پروژه خود انتقال میدهیم تا بتوانیم Key موردنظر برای Encryption را وارد کنیم.

php artisan vendor:publish --provider=&quotSoareCostin\FileVault\FileVaultServiceProvider&quot

با استفاده از متد FileVault::encrypt($file) فایل موردنظر ما Encrypt می‌شود و فایل اصلی حذف می‌شود. این متد فایل را با همان نام و با پسوند .enc در همان path ذخیره می‌کند. اگر میخواهید نام فایل را عوض کنیم پارامتر دوم را که همان اسم فایل جدید است به متد ذکر شده پاس دهید و اگر میخواهید که فایل اصلی حذف نشود به جای متد encrypt از از متد encryptCopy استفاده کنید.

همچنین با استفاده از متد زیر میتوانیم برای هر فایل بر اساس یک unique key فایلها را Encrypt کنیم یعنی برای هر user یک key داشته باشیم و با key اختصاصی مربوط به کاربر فایلها را Encrypt کنیم.

FileVault::key($encryptionKey)->encrypt('file.txt');

با مطالعه مستندات پکیج در File encryption / decryption in Laravel میتوانید به صورت کاملتر از ویژگیهای پکیج آگاهی داشته باشید.

ذخیره و انتقال فایل‌ها به سروری دیگر (آپلود و ذخیره سازی در Amazon s3,...)

تا به اینجای کار ما فایلها را به صورت Encrypt شده ذخیره سازی کردیم ولی نکته مهم و اصولی این است که در صورتیکه میزان فایلهای پروژه و حجم آنها زیاد است برای فایلهای پروژه یک سرور مجزا همچون Amazon S3 را لحاظ کنیم. خوشبختانه لاراول امکان ذخیره سازی و انتقال فایلها را با تعریف درایور موردنظر فراهم کرده است و نیاز به دردسر زیادی نیست. برای مثال همانند کد زیر ما با تعریف مقدار disk مشخص میکنیم که آپلود فایل به کدام storage ما انتقال داده شود که این storage را در مسیر config/filesystems.php میتوانیم تعریف و تغییر دهیم.

Storage::disk('s3')->putFile('photos', new File('/path/to/photo'));

ذخیره سازی و انتقال فایلها به سرور زمان زیادی را با توجه نتورک خواهد گرفت و این ممکنه باعث خطای time out و ... شود به همین خاطر توصیه میکنیم برای اینکار از queued jobs استفاده کنید.

queueها به شما امکان می دهند پردازش یک کار وقت گیر را به تعویق بیندازید. به تعویق انداختن این کارهای وقت گیر به افزایش سرعت درخواست های وب برنامه شما کمک می‌کند. ما برای اینکار از ۲ صف، یکی برای encrypt کردن فایلها و دیگری برای انتقال فایل به سرور موردنظر استفاده میکنیم. در لاراول می توانید queueها را به صورت زنجیره ای تنظیم کنید (Job chaining) تا queueها به ترتیب اجرا شوند، پس می توانیم بلافاصله پس از encrypt فایل، آپلود فایل را در سرور دیگر (S3) شروع کنیم، در این ساختار Job chaining اگر یکی از jobها fail شود مابقی jobها اجرا نمی‌شوند. برای استفاده از این ساختار مثال زیر را مشاهده کنید.

ProcessPodcast::withChain([ new OptimizePodcast, new ReleasePodcast ])->dispatch();

نکته: در صورتیکه از Amazon S3 استفاده میکنید باید پکیج آن را که در مستندات لاراول نوشته شده است نصب کنید. (league/flysystem-aws-s3-v3) و یا در صورتیکه از SFTP استفاده میکنید پکیج (league/flysystem-sftp) را نصب کنید.

برای افزایش پرفرمنس برنامه با استفاده از کامپوزر به صورت زیر پکیج flysystem-cached-adapter را نصب میکنیم.

composer require league/flysystem-cached-adapter

بعد از نصب پکیج‌های فوق پیاده سازی را بر روی پروژه خود شروع میکنیم. ابتدا با کامند زیر queueable jobsهای موردنیاز را ایجاد میکنیم.

php artisan make:job EncryptFile php artisan make:job MoveFileToStorageServer

که EncryptFile به صورت زیر خواهد بود:

namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use SoareCostin\FileVault\Facades\FileVault; class EncryptFile implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $filename; /** * Create a new job instance. * * @return void */ public function __construct($filename) { $this->filename = $filename; } /** * Execute the job. * * @return void */ public function handle() { FileVault::encrypt($this->filename); } }

و فایل MoveFileToStorageServer به صورت زیر خواهد بود:

namespace App\Jobs; use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Http\File; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Storage; class MoveFileToStorageServer implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $filename; /** * Create a new job instance. * * @return void */ public function __construct($filename) { $this->filename = $filename . '.enc'; } /** * Execute the job. * * @return void */ public function handle() { // Upload file to S3 $result = Storage::disk('s3')->putFileAs( '/', new File(storage_path('app/' . $this->filename)), $this->filename ); // Forces collection of any existing garbage cycles // If we don't add this, in some cases the file remains locked gc_collect_cycles(); if ($result == false) { throw new Exception(&quotCouldn't upload file to S3&quot); } // delete file from local filesystem if (!Storage::disk('local')->delete($this->filename)) { throw new Exception('File could not be deleted from the local filesystem '); } } }

که در نهایت در کنترلر به صورت زیر از آن استفاده میکنیم:

if( $request->hasFile(&quotfile&quot) && $request->file(&quotfile&quot)->isValid() ){ $filename = Storage::putFile('files/', $request->file('file')); // check if we have a valid file uploaded if ($filename) { EncryptFile::withChain([ new MoveFileToStorageServer($filename), ])->dispatch($filename); } }

مطالعه بیشتر:

  1. Salt and Hash Passwords with bcrypt
  2. bcrypt - wikipedia
  3. Encryption - laravel
  4. password_hash
  5. How To Encrypt Large Files in Laravel
  6. How To Encrypt and Upload Large Files to Amazon S3 in Laravel
رمز عبورلاراولencryptionlaravel
من میکائیل هستم و در وبلاگم در مورد تجربیات کاریم و باورها و عقاید شخصیم می‌نویسم :)
شاید از این پست‌ها خوشتان بیاید