ساخت ابزار Brute-Force برای کرک هَش SHA-1 به کمک پایتون مقدماتی

۳۶۳ بازدید
آخرین به‌روزرسانی: ۰۸ مهر ۱۴۰۲
زمان مطالعه: ۱۷ دقیقه
ساخت ابزار Brute-Force برای کرک هَش SHA-1 به کمک پایتون مقدماتی

توسعه‌دهندگانی که سیستم‌های login را طراحی می‌کنند، به خوبی می‌دانند که ذخیره‌سازی رمزهای عبور به صورت متن ساده معمولاً امن نیست و بهتر است رمزهای عبور به صورت هَش (Hash) شده ذخیره شوند تا از سرقت اطلاعات هویتی از سوی هکرها جلوگیری شود. به دلیل روش عملکرد هش‌ها، همه آن‌ها به طرز یکسانی تولید نمی‌شوند. برخی از هش ها نسبت به بقیه آسیب‌پذیری بیشتری دارند و در این میان دانستن اندکی از زبان پایتون می‌تواند به طراحی ابزاری برای حمله بروت فورس (Brute-Force) به هش های ضعیف کمک کند. این حمله به منظور دستیابی به رمزهای عبور سازنده‌ هش مفید خواهد بود.

توضیح اندکی در مورد هش‌ها

هکرها غالباً کل یک پایگاه داده حاوی داده‌های نام کاربری و رمزعبور کاربران را به سرقت می‌برند و به همین دلیل هش‌ها روشی ترجیحی برای ذخیره‌سازی اطلاعات حساسی مانند رمزهای عبور هستند.

هش‌ها شکل متفاوتی از رمزنگاری هستند، چون در این روش داده‌ای ذخیره نمی‌شود. به جای آن هش را ذخیره می‌کنند. هش ها به صورت اعدادی هستند که حاصل محاسبات اجرا شده بر روی داده هش شونده هستند. این داده می‌تواند رمز عبور یا کل یک فایل باشد. در این حالت دوم از هش برای تضمین این مسئله استفاده می‌شود که فایلی که دانلود می‌شود با فایلی که به منظور دانلود قرار داده شده است یکسان است. در مورد رمز عبور هش تضمین می‌کند که رمز عبوری که کاربر در وب‌سایت وارد می‌کند با رمز عبوری که در هنگام ثبت‌نام تعیین کرده است یکسان است.

بسته به اندازه فایل یا رمز عبور که هش می‌شود، از اندازه‌های بلوک متفاوتی مانند SHA-1 یا MD5 برای ساخت بلوک‌های ثابتی از داده استفاده می‌شود و محاسبات پیچیده‌ای به صورت بلوک به بلوک روی آن انجام می‌شود تا زمانی که مقدار نهایی حاصل شود. این مقدار عدد بسیار بزرگی است که برای منحصربه‌فرد بودن طراحی شده است و از این رو می‌تواند تضمین کند که یک فایل بر اساس مقایسه مقادیر هش با فایل دیگری مطابقت دارد یا نه. اگر مقدار هش متفاوت باشد، در این صورت متوجه می‌شویم که بخشی از فایل تغییر یافته است.

این روش بسیار خوبی است، زیرا اگر کاربر هر رمز عبوری به جز رمز عبور اولیه وارد کند، مقدار هش کاملاً متفاوت خواهد بود. به همین دلیل کافی است برنامه‌نویس هش را ذخیره کند. چون هر بار که کاربر نیاز دارد وارد وب‌سایت شود، می‌تواند رمز عبور را وارد کند تا یک هش جدید ایجاد شده و با هش ذخیره شده در پایگاه داده مقایسه شود.

به عنوان مثال، هش عبارت «faradars» در روش SHA-1 به صورت زیر است:

42a26b0c4333880302175dca52180c3d95ed0039

متأسفانه همه هش ها برای برنامه‌نویسان جهت ذخیره‌سازی رمز عبور به طرز یکسانی تولید نمی‌شوند. در هش هایی مانند SHA-1 چند مشکل وجود دارند که باعث می‌شوند ذخیره‌سازی رمزهای عبور با هش هایی مانند SHA-1 راه‌حلی چندان ایده‌آل نباشد.

به عنوان مثال یکی از این مشکلات آن است که هر بار یک کلمه یکسان را با روش SHA-1 هش کنیم، دقیقاً هش یکسانی تولید می‌کند. دلیل این مسئله به طراحی این روش مربوط است؛ در نتیجه شما می‌توانید به سادگی تعداد بالایی از مواردی که حدس می‌زنید را با استفاده از SHA-1 هش کنید و سپس آن‌ها را به سرعت مقایسه کنید تا رمز عبوری اصلی که با SHA-1 هش شده است را پیدا کنید. از آنجا که SHA-1 به منظور سریع بودن طراحی شده است این فرایند در مقدار زمان بسیار کمی انجام می‌شود و به همین جهت حمله بروت فورس به سادگی انجام می‌یابد.

برای این مسئله راه‌حل‌هایی وجود دارند و یکی از رایج‌ترین انواع آن افزودن یک مقدار salt است. Salt رشته‌ای متنی است که پیش از هش کردن یک رمز عبور به آن اضافه می‌شود. برای مثال می‌توانیم کلمه salt را به رشته «faradars» اضافه کنیم. با این که قبلاً هش عبارت faradars را محاسبه کرده‌ایم؛ اما مقدار هش عبارت saltfaradars یا faradarssalt می‌تواند کاملاً متفاوت باشد. این امر کمک زیادی به افزایش امنیت هش می‌کند؛ اما اگر salt برای هر کاربر منحصربه‌فرد نباشد؛ در این صورت محاسبه salt چندان دشوار نخواهد بود و دوباره با همان مشکل مواجه خواهیم شد.

Bcrypt به امن‌تر ساختن هش‌ها کمک می‌کند

راه‌حل بهتر آن است که مقدار salt تصادفی باشد و الگوریتم هش کردن با در نظر گرفتن این نکته طراحی شده باشد.

الگوریتم Bcrypt نه تنها روش حمله بروت فورس را کُند می‌سازد؛ بلکه در زمان تولید هش یک salt تصادفی به آن اضافه می‌کند. در نتیجه هیچ دو هش Bcrypt یکسان نخواهند بود؛ حتی اگر از رمزعبور دقیقاً یکسانی ایجاد شده باشند. برای این که یک هش را حدس بزنید باید از یک تابع Bcrypt استفاده کنید که رمز عبور حدس زده را دریافت می‌کند و هش را نیز به عنوان آرگومان می‌پذیرد و سپس تأیید می‌کند که این دو مطابقت دارند یا نه.

برای این که نشان دهیم روش هش کردن چقدر تغییر می‌کند، یک کد پایتون نوشته‌ایم که هر رمز عبوری را به روش‌های SHA-1، MD5 و Bcrypt هش می‌کند.

import hashlib, bcrypt
#Demonstrates the difference between two types of hashing, SHA1 and Bcrypt
password = input("Input the password to hash\n>")
print("\nSHA1:\n")
for i in range(3):
    setpass = bytes(password, 'utf-8')
    hash_object = hashlib.sha1(setpass)
    guess_pw = hash_object.hexdigest()
    print(guess_pw)
print("\nMD5:\n")
for i in range(3):
    setpass = bytes(password, 'utf-8')
    hash_object = hashlib.md5(setpass)
    guess_pw = hash_object.hexdigest()
    print(guess_pw)
print("\nBCRYPT:\n")
for i in range(3):
    hashed = bcrypt.hashpw(setpass, bcrypt.gensalt(10))

همان‌طور که در ادامه می‌توانید مشاهده کنید، هش های MD5 و SHA-1 همگی یکسان هستند؛ اما هش های Bcrypt هر بار که تولید شوند تغییر می‌یابند. بدیهی است که ازنظر توسعه‌دهندگان، Bcrypt روش بهتری است اما اگر برحسب اتفاق با یک پایگاه داده از رمزهای عبور هش شده به صورت SHA-1 یا MD5 مواجه شوید، می‌توانید با استفاده از روش بروت فورس هش ها را به رمزهای عبور معادلشان تبدیل کنید.

/Users/skickar/venv/untitled10/bin/python /Users/skickar/Desktop/TestSHA1.py
Input the password to hash
>nullbyte

SHA1:

32c0ced56f1fe08583bdb079d85a35a81995018c
32c0ced56f1fe08583bdb079d85a35a81995018c
32c0ced56f1fe08583bdb079d85a35a81995018c

MD5:

5f804b61f8dcf70044ad8c1385e946a8
5f804b61f8dcf70044ad8c1385e946a8
5f804b61f8dcf70044ad8c1385e946a8

BCRYPT:

b'$2b$10$Z1WVDUi50fmqyrpw19rIyOLPIKVUFeh7HO0FfQi1MbKjyxyduG2WS'
b'$2b$10$F.vehMYSUh/6zmTR/VY2quTnPfzPDcIdHTfZpb8twqjRIIIEFcbUW'
b'$2b$10$pZyptPPDHrnIgpU7wTW2nu4cfGAUS65kcGZb6FMC7KmYwJmuwSoLO'

ساختن برنامه پایتون 3 برای حمله بروت فورس SHA-1

بخشی از مراحل رشد یک هکر این است که یاد بگیرد ابزارهای مورد نیازش را خودش بنویسد. در ابتدا ابزارهای شما ساده خواهند بود و مسائل کوچکی را حل می‌کنند، اما وقتی تجربه کسب می‌کنید می‌توانید انتظار دستاوردهای هر چه بیشتری را داشته باشید. در آغاز زبان‌های برنامه‌نویسی مانند ++C که کاملاً انواع متغیرهای تعریف‌شده‌ای (typed) دارند، ممکن است درک دشواری داشته باشند؛ اما پایتون 3 انعطاف‌پذیر است و زبان مناسبی برای مبتدیان محسوب می‌شود که امکان ساخت ایده‌های مجرد و ساخت نمونه‌های اولیه را به سادگی فراهم می‌سازد.

برنامه ساده‌ای که در ادامه خواهیم نوشت به ما می‌آموزد که یک هکر چگونه ابزاری برای بهره‌برداری از یک آسیب‌پذیری می‌نویسد. در این نمونه SHA-1 یک آسیب‌پذیری برای حمله بروت فورس است، زیرا می‌توانید دو مقدار هش را با هم مقایسه کنید و بنابراین برنامه‌ای می‌نویسیم که دقیقاً همین کار را انجام دهد.

برای نوشتن هر برنامه‌ای باید ابتدا مراحلی که برنامه برای موفقیت طی خواهد کرد را بنویسید. این فهرست ممکن است کمی طولانی به نظر برسد؛ اما می‌توان آن را فشرده‌تر ساخت و باید تا حد امکان در مورد چیزهایی که می‌خواهید به دست آورید تا به خروجی مورد نظر دست یابید به طور کاملاً مشخص عمل کنید. برخی برنامه‌نویس‌ها استفاده از وایت بورد یا نرم‌افزارهای آنلاین تولید فلوچارت مانند mindmup را برای ترسیم روش کار برنامه‌ها از آغاز تا پایان ترجیح می‌دهند.

وقتی مراحل مورد نیاز را مشخص کردید می‌توانید وارد مرحله نوشتن شبه کد بشوید. در این بخش، مراحل مورد نیاز را به ترتیب طراحی می‌کنید تا هم خوانا باشد و هم به روش عملکرد کدی که در عمل خواهید نوشت نزدیک‌تر باشد. وقتی شبه کد نوشته شد، می‌توانید شروع به نوشتن خط به خط کد بکنید، اشتباه‌ها را اصلاح نمایید و در هر مرحله که برنامه کامل‌تر می‌شود آن را مشاهده کرده و تعامل مراحل مختلف با هم را مورد نظارت قرار دهید.

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

برای ادامه دادن این راهنما به یک رایانه به همراه پایتون 3 نیاز دارید. پایتون 3 چند تفاوت با نسخه قبلی خود دارد؛ بنابراین باید مطمئن شوید که از نسخه صحیحی استفاده می‌کنید. پایتون 3 را می‌توانید به چند روش مختلف نصب کنید. در لینوکس می‌توانید دستور نصب زیر را وارد کنید:

apt install python3

شما به یک IDE پایتون 3 هم نیاز خواهید داشت. IDE به معنی محیط توسعه یکپارچه نرم‌افزاری است که به شما کمک می‌کند تا کد خود را نوشته، تست و بررسی کنید. برای پایتون به طور اختصاصی PyCharm from Jetbrains توصیه می‌شود. به علاوه نسخه حرفه‌ای این IDE برای دانشجویان بدون هزینه در دسترس است که اگر حائز شرایط دریافت رایگان آن باشید، کاملاً ارزش امتحان کردن را دارد.

برای این که همه چیز به درستی کار کند، باید برخی کتابخانه‌ها را ایمپورت کنیم. ما در این کد از کتابخانه‌های urllib, urlopen و hashlib استفاده خواهیم کرد تا بتوانیم فایل‌هایی را از url های بیرونی باز کرده و حدس‌های رمزعبور را به صورت SHA-1 هش کنیم. برای گنجاندن این کتابخانه‌ها یک فایل پایتون 3 جدید در IDE خود ایجاد کنید و کد زیر را در خط اول وارد نمایید:

from urllib.request import urlopen, hashlib

این دستور کتابخانه‌های مورد نیاز را ایمپورت می‌کند و تضمین می‌کند که بقیه برنامه به این کتابخانه‌ها دسترسی دارد. اگر بخواهید هر یک از این کتابخانه‌ها را روی رایانه خود نصب کنید، می‌توانید به طور کلی این کار را با استفاده از دستور pip install و سپس ذکر نام کتابخانه انجام دهید.

برای ادامه این راهنما می‌توانید برنامه‌های پایتون که برای این مثال نوشته شده‌اند را دانلود کنید. برای انجام این کار پنجره ترمینال را باز کنید و سه دستور زیرا را وارد نمایید تا اسکریپت‌های مورد نیاز را دانلود کرده، به دایرکتوری مربوطه رفته و فایل‌های دانلود شده را در این دایرکتوری فهرست کنید:

git clone https://gitlab.com/skickar/SHA1cracker
cd SHA1cracker
ls

گام یکم: دریافت هش SHA-1 از کاربر

در اولین دستور لازم است آن هش را که می‌خواهیم کرک کنیم از کاربر بگیریم. برای انجام این کار می‌توانیم از تابع input استفاده کنیم که یک اعلان به کاربر نشان می‌دهد تا بتواند پاسخی را دریافت کند.

در پایتون می‌توانیم این پاسخ را بدون انجام هیچ کاری در پیش‌زمینه در یک متغیر ذخیره کنیم. دلیل این مسئله آن است که پایتون شبیه ++C نیست و مانند آن الزام نمی‌کند که همه چیز از آغاز اعلان شده باشد. ما می‌توانیم در هر زمان یک متغیر ایجاد کرده و داده‌هایی را که می‌خواهیم در آن ذخیره کنیم.

ما این متغیر را SHA-1 می‌نامیم، زیرا درون آن یک هش SHA-1 ذخیره خواهیم کرد. می‌توانیم این عبارت را تایپ کنیم تا یک متغیر ایجاد شود و سپس باید پاسخ کاربر را به آن انتساب دهیم تا متغیر پر شود. در پایتون از نماد (=) برای مقایسه دو شیء جهت یکسان بودن استفاده نمی‌شود. این حالت با نماد (==) انجام می‌یابد. نماد تساوی درواقع یک نوع دستور در پایتون محسوب می‌شود که به وسیله آن داده سمت راست به متغیر سمت چپ انتساب می‌یابد.

ما هر چه را که کاربر وارد می‌کند به این متغیر انتساب می‌دهیم و از این رو تابع input را فراخوانی می‌کنیم که به ما اجازه می‌دهد تا متنی که برای کاربر نمایش خواهد یافت را درون یک جفت پرانتز بنویسیم. برای این که به پایتون بگوییم که این رشته (مجموعه‌ای از کاراکترها) را نمایش دهد، آن را درون یک جفت گیومه نیز قرار می‌دهیم. نتیجه نهایی به صورت زیر خواهد بود:

sha1hash = input("Please input the hash to crack.\n>")

وقتی کد فوق را اجرا کنیم یک اعلان برای کاربر نمایش می‌یابد که می‌گوید: «Please input the hash to crack.» یعنی لطفاً هشی که می‌خواهید کرک شود را وارد نمایید. پس از آن کاراکتر یک خط جدید به صورت (n\) می‌بینیم. این بدان معنی است که اعلان باید به خط جدید برود. در نهایت یک کاراکتر > درج شده است که به کاربر تفهیم می‌کند که پاسخ خود را در خط جدید وارد نماید. وقتی این فایل به نام NBspecial.py اجرا شود، نتیجه به صورت زیر خواهد بود:

Dell:SHA1cracker skickar$
Dell:SHA1cracker skickar$ python3 NBspecial.py
Please input the hash to crack
>_

زمانی که کاربر یک هش وارد کند این مقدار در متغیر sha1hash برای استفاده بعدی در برنامه ذخیره می‌شود.

گام دوم: باز کردن یک فایل پر از حدس‌های رمز عبور

در مرحله بعد می‌بایست یک فهرست از رمزهای عبور رایج را باز کنیم. ما از فهرستی شامل 10،000 رمز عبور رایج در این نمونه استفاده می‌کنیم که یک فایل متنی ساده است که روی گیت‌هاب میزبانی شده است. شما می‌توانید از فهرست‌های دیگری مانند leaked passwords online یا ones made with the Mentalist یا Crunch استفاده کنید.

در مورد فایلی که استفاده می‌کنیم شروع به انتساب آن به یک متغیر می‌کنیم که آن را LIST_OF_COMMON_PASSWORDS نامیده‌ایم. برای باز کردن فایل از تابع urlopen استفاده می‌کنیم که به ما اجازه می‌دهد تا فایل‌های متنی را به راحتی باز کنیم و به پایتون بگوییم که انکودینگ صحیح فایل چیست. به این منظور از فرمت زیر استفاده کنید:

urlopen('TheURLYouWantToOpen').read()

دستور فوق یک url درون گیومه را با متد read باز می‌کند، یعنی می‌خواهیم متنی را از یک فایل بخوانیم. برای این که مطمئن شویم تابع ()str می‌داند با چه چیزی کار می‌کند یک دستور utf-8 نیز به آن اضافه می‌کنیم تا این تابع به برنامه بگوید که انکودینگ متن مورد نظر به صورت utf-8 است.

در این مورد نیز داده‌ها را به صورت یک رشته ذخیره می‌کنیم و برای این که از هر گونه مشکل احتمالی در این فرایند جلوگیری کنیم، می‌توانیم با cast کردن داده‌هایی که در متغیر قرار می‌دهیم به صورت string، اطمینان حاصل کنیم که به صورت رشته هستند. Casting به منظور تغییر نوع داده‌ها استفاده می‌شود و با این فرایند مطمئن شویم که برای مثال اگر داده‌ها به صورت integer هم باشند در نهایت به صورت string در آمده‌اند به این منظور دستور ()str را وارد می‌کنیم و سپس داده‌هایی که می‌خواهیم به رشته تبدیل شوند را درون پرانتز می‌آوریم. نتیجه نهایی چیزی شبیه دستور زیر خواهد بود:

LIST_OF_COMMON_PASSWORDS = str(urlopen('https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10-million-password-list-top-10000.txt').read(), 'utf-8')

در این خط فایل متنی که در url ریموت قرار دارد را باز می‌کنیم، آن را به صورت یک فایل متنی utf-8 انکود می‌کنیم و سپس داده‌ها را دریک رشته به نام LIST_OF_COMMON_PASSWORDS ذخیره می‌نماییم.

گام سوم: انتخاب یک حدس از فهرست رمزهای عبور

اینک باید یک مسئله جذاب را حل کنید. با این که ما می‌دانیم 10،000 رمز عبور در یک فایل متنی وجود دارند؛ اما برنامه ایده‌ای در مورد تعداد رکوردهای درون این فایل ندارد و از این رو باید کدی ایجاد کنیم که برای هر حدس درون فایل رمزهای عبور یک بار اجرا شود.

بدین منظور از ساختاری به نام حلقه loop بهره می‌گیریم. حلقه loop یک مفهوم بسیار ابتدایی در برنامه‌نویسی محسوب می‌شود که شبیه زیر است:

for [an individual guess] in [the variable that guess is in]: [do this]

معنی شبه کد فوق آن است که ما به تعداد عدد مشخص شده در متغیر (در این مورد 10،000) دستور مشخصی را اجرا می‌کنیم. در عمل این به آن معنی است که یک حدس از فهرستی از حدس‌ها انتخاب می‌شود و سپس برمی‌گردد تا حدس بعدی را انتخاب کند تا این که همه حدس‌ها بررسی شوند.

برای متغیری که حدس را نگه‌داری می‌کند، هر نامی می‌توانیم تعیین کنیم؛ اما برای وضوح بیشتر آن را guess می‌نامیم. البته اگر دستور به صورت for x in LIST_OF_COMMON_PASSWORDS نیز باشد، باز هم به درستی کار می‌کند.

مسئله نهایی که باید حل کنیم این است که به پایتون بگوییم چگونه می‌تواند فهرست بلندبالایی از رمزهای عبور را به صورت حدس‌های منفرد درآورد. در فهرست رمزهای عبور که ما استفاده می‌کنیم همه روزهای عبور با یک خط جدید از هم جدا شده‌اند؛ بنابراین می‌توانیم از کاراکتر new line برای افراز LIST_OF_COMMON_PASSWORDS به حدس‌های منفرد بهره بگیریم.

برای عملیاتی کردن این مفهوم تابع ()split. را به انتهای متغیر LIST_OF_COMMON_PASSWORDS اضافه کرده و کد خط جدید (n\) را درون پرانتزها قرار می‌دهیم. نتیجه نهایی چیزی شبیه زیر خواهد بود:

for guess in LIST_OF_COMMON_PASSWORDS.split('\n'):

کد فوق یک رمزعبور را می‌گیرد، وقتی به کاراکتر خط جدید در متغیر LIST_OF_COMMON_PASSWORDS می‌رسد متوقف می‌شود. این کد به هر تعداد که رمز عبور در فهرست وجود داشته باشد، اجرا می‌شود؛ مگر این که در مراحل بعدی تعیین کرده باشیم به طرز متفاوتی عمل کند.

گام چهارم: هش کردن حدس‌هایی که از فهرست رمز عبور به دست آورده‌ایم

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

ما این متغیر را که برای نگهداری نسخه هش شده حدس استفاده می‌شود، hashedGuess می‌نامیم. سپس پیش از این که بتوانیم حدس استخراج شده از فهرست را هش کنیم، باید کمی کارهای مقدماتی انجام دهیم. برای تبدیل متغیر string باید آن را در یک شیء bytes به نام guess فراخوانی کنیم. دلیل الزام این حالت آن است که تابع SHA-1 تنها بر روی اشیای بایت عمل می‌کند و نه نوع string.

خوشبختانه تبدیل متغیرهای رشته‌ای به نوع بایت ساده است. این کار را می‌توان به همان روش تبدیل متغیر وارد شده از سوی کاربر به نوع string که در گام اول انجام دادیم می‌توانیم صورت بدهیم. این فرمول چیزی شبیه کد زیر است. در این مورد متغیر guess به نوع bytes تبدیل می‌شود و انکودینگ متن utf-8 است.

bytes(StringToTurnIntoBytes, 'EncodingOfString')

اینک که نسخه بایتی guess را داریم می‌توانیم آن را به وسیله کد زیر به هش SHA-1 تبدیل کنیم.

hashlib.sha1(BytesToHash).hexdigest()

در کد فوق هش SHA-1 از تابع hashlib فراخوانی می‌شود و متغیر بایتی که درون پرانتز قرار می‌دهیم هش می‌شود. به دلیل طرز کار SHA-1 می‌توانیم به اضافه کردن مواردی به آن ادامه دهیم؛ اما برای نمایش مقدار فعلی هش SHA-1 عبارت ()hexidigest. را به انتهای آن اضافه می‌کنیم.

در کد نهایی، مقدار guess هش شده را به متغیر HashedGuess انتساب می‌دهیم.

hashedGuess = hashlib.sha1(bytes(guess, 'utf-8')).hexdigest()

اینک حدس رمز عبور را به صورت یک هش در اختیار داریم که می‌توانیم این حدس را با هش اولیه SHA-1 مقایسه کرده و به طور مستقیم آن را کرک کنیم.

گام پنجم: مقایسه رمز عبور هش شده با هش اولیه

در این مرحله باید به برنامه بگوییم که در صورت مطابقت رمز عبور برنامه چه باید بکند. بدین منظور از یک عبارت ساده به نام عبارت if استفاده می‌کنیم.

عبارت if مانند عبارت for عمل می‌کند؛ به جز این که شرطی را بررسی می‌کند تا ببیند اگر بروت فورس به درستی عمل کرده باشد، در ادامه باید چه کار بکند. اگر شرط صحیح (True) باشد می‌توانید به برنامه بگویید که اقدام خاصی انجام دهد و اگر نادرست (False) باشد اقدام دیگری را صورت می‌دهد.

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

if [some condition to check is true]:
[do whatever this code says to do]

در مورد استفاده کنونی ما می‌خواهیم بدانیم آیا حدس هش شده با هش اولیه‌ای که کاربر به ما می‌دهد یکسان است یا نه و از این رو می‌توانیم از علامت == برای تعیین مساوی بودن استفاده کنیم. عبارتی که می‌خواهیم ارزیابی کنیم این است که آیا hashedGuess با sha1hash مساوی است یا نه. در کد ما این بررسی به صورت کد ساده زیر است:

if hashedGuess == sha1hash:

اینک که این مقایسه را تنظیم کردیم باید به برنامه توضیح دهیم که در سه شرایط مختلف که انتظار می‌رود چه باید بکند: الف) در صورت مطابقت ب) در صورت عدم مطابقت و ج) وجود رمزهای عبور بیشتر در فهرست برای بررسی.

گام ششم: تعیین منطق شرطی

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

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

print("The password is ", str(guess))

کد فوق هر چه درون گیومه است را نمایش می‌دهد و سپس نسخه رشته‌ای رمز عبور کنونی را که به درستی حدس زده شده است اضافه می‌کند. قرار دادن متغیر guess و نه متغیر hashedguess حائز اهمیت است زیرا نسخه hashedguess تنها یک هش دیگر از SHA-1 را به ما می‌دهد. در این مورد می‌توانیم آن متغیر را نیز به رشته تبدیل کنیم تا پایتون بدون اشکال آن را نمایش دهد. پس از نمایش این رمز عبور دستور ()quit را برای پایان برنامه می‌آوریم، زیرا اینک رمز عبور را یافته‌ایم.

اگر متغیر hashedguess و sha1hash مطابقت نداشته باشند، باید به برنامه توضیح دهیم که چه کار بکند. ما می‌توانیم این بخش از کد را با استفاده از عبارت elif اضافه کنیم. Elif یا همان else if به برنامه می‌گوید که اگر شرایط دیگری صحیح بود باید چه کار کند. عبارت بعدی ما این وضعیت را به صورت زیر بررسی می‌کند:

hashedGuess!= sha1hash

این عبارت که با نماد!= نمایش می‌یابد، بررسی می‌کند که آیا دو متغیر مساوی هستند یا نه؛ به عبارت دیگر اگر این وضعیت صحیح باشد و دو مقدار هش شده مساوی نباشند و حدس رمز عبور نادرست باشد، باید به کاربر بگوییم که حدس ناموفق بوده است و سپس به ابتدای حلقه بازگردیم تا رمز عبور جدید را انتخاب کنیم.

بدین منظور کاری که قبلاً انجام دادیم را تکرار خواهیم کرد و به سادگی از تابع ()print برای نمایش یک پیام بهره می‌گیریم. در این پیام بیان می‌کنیم که: «Password guess", [guess], "does not match, trying next...» یعنی «رمز عبور حدس زده شده [guess] مطابقت نداشت، مورد بعدی بررسی می‌شود...» نتیجه نهایی کدی شبیه به زیر خواهد بود:

print("The password is ", str(guess))
        quit()
    elif hashedGuess!= sha1hash:
        print("Password guess ",str(guess)," does not match, trying next...")

این کد توضیح می‌دهد که در صورت صحیح بودن حدس، برنامه باید چه کار کند و در صورت عدم مطابقت نیز چه کار باید بکند؛ اما اگر کلاً هیچ موردی مطابقت نداشت چه باید کرد؟ به جای خروج از برنامه می‌توانیم قدری اطلاعات بیشتر به کاربر بدهیم یعنی بگوییم که همه فهرست‌های رمز عبور بررسی شده‌اند و دیگر حدسی برای بررسی وجود ندارد.

گام هفتم: برنامه‌نویسی وضعیتی که هیچ موردی مطابقت نداشته باشد

اگر این حلقه تا آخر ادامه یابد و هیچ موردی مطابقت نداشته باشد، حلقه به پایان می‌رسد، چون دیگر هیچ موردی برای انتخاب از فهرست حدس‌های رمز عبور وجود نخواهد داشت. در این حالت به جای خروج صرف از برنامه با قرار دادن یک عبارت print به کاربر در خارج از حلقه loop اطلاع می‌دهیم که همه حدس‌ها بررسی شده‌اند و هیچ موردی مطابقت نداشته است. بدین ترتیب اگر رمز عبور پیدا شود تابع print نهایی هرگز اجرا نمی‌شود، زیرا تابع ()quit قبل‌تر از آن اضافه شده است و در زمان یافتن رمز عبور صحیح، برنامه خاتمه می‌یابد.

سؤالی که اینجا پیش می‌آید این است که چگونه می‌توان این عبارت را خارج از حلقه loop قرار داد؟ در پایتون فاصله‌ها مهم هستند و از این رو می‌توانیم آن را صرفاً در یک خط جدید قرار دهیم و برای آن تورفتگی تعیین نکنیم. این وضعیت را در مثال زیر می‌توانید ببینید:

for guess in LIST_OF_COMMON_PASSWORDS.split('\n'):
    hashedGuess = hashlib.sha1(bytes(guess, 'utf-8')).hexdigest()
    if hashedGuess == sha1hash:
        print("The password is ", str(guess))
        quit()
    elif hashedGuess!= sha1hash:
        print("Password guess ",str(guess)," does not match, trying next...")
print("Password not in database, we'll get them next time.")

پایتون در ابتدا حلقه for را اجرا می‌کند، سپس عبارت‌های if و elif را بررسی می‌کند و تنها در صورتی که حلقه به پایان برسد، تابع ptint نهایی اجرا می‌شود، زیرا خارج از حلقه for اجرا می‌شود.

تابع print ساده است و شامل هیچ متغیری نیست؛ تنها یک رشته در آن درج شده است تا کاربر بداند که هیچ کدام از رمزهای عبور درون فهرست مطابقت نداشته‌اند.

print("Password not in database, we'll get them next time.")

با نوشتن این خط آخر در واقع یک برنامه کامل بروت فورس خواهیم داشت، پس اجازه بدهید آن را اجرا کنیم. در ابتدا یک اعلان ظاهر می‌شود که از کاربر هش SHA-1 برای کرک کردن می‌خواهد. هش زیر به آن داده می‌شود تا بررسی کند:

Dell:SHA1cracker skickar$ python3 NBspecial.py
Please input the hash to crack.
>cbfdac6008f9cab4083784cbd1874f76618d2a97

پس از زدن دکمه اینتر، اسکریپت شروع به کار می‌کند:

Password guess 171717 does not match, trying next...
Password guess panzer does not match, trying next...
Password guess lincoln does not match, trying next...
Password guess katana does not match, trying next...
Password guess firebird does not match, trying next...
Password guess blizzard does not match, trying next...
Password guess a1b2c3d4 does not match, trying next...
Password guess white does not match, trying next...
Password guess sterling does not match, trying next...
Password guess redhead does not match, trying next...
The password is password123
Dell:SHA1cracker skickar$ _

و همان‌طور که می‌بینید توانسته‌ایم رمز عبوری که کاربری برای ایجاد هش استفاده کرده است را بیابیم. بدین ترتیب موفق شده‌ایم هش SHA-1 را که یک‌طرفه است به طور معکوس اجرا کنیم.

پایتون جذاب و قوی است و می‌توانیم برنامه خود را خلاصه‌تر کنیم

با داشتن اندک دانشی از پایتون 3 می‌توانیم اسکریپت ساده‌ای را در 11 خط بنویسیم که رمز عبوری که هش از آن ایجاد شده است را بیابد. می‌توانید ببینید که کل کد زیر بدون هیچ توضیحی نوشته شده است. با قالب‌بندی اندکی هوشمندانه‌تر پایتون می‌توانیم این کد را فشرده‌تر نیز بکنیم که البته خوانایی یا درک آن کاهش می‌یابد و همه این موارد را در تنها سه خط کد اجرا نماییم:

from urllib.request import urlopen, hashlib
sha1hash = input("Please input the hash to crack.\n>")
LIST_OF_COMMON_PASSWORDS = str(urlopen('https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10-million-password-list-top-10000.txt').read(), 'utf-8')
for guess in LIST_OF_COMMON_PASSWORDS.split('\n'):
    hashedGuess = hashlib.sha1(bytes(guess, 'utf-8')).hexdigest()
    if hashedGuess == sha1hash:
        print("The password is ", str(guess))
        quit()
    elif hashedGuess!= sha1hash:
        print("Password guess ",str(guess)," does not match, trying next...")
print("Password not in database, we'll get them next time.")

این کار از طریق گرفتن هش مورد نیاز برای کرک و ایمپورت کردن کتابخانه‌ها در یک خط ممکن می‌شود. همچنین عبارت‌های for و if را نیز در یک خط با عبارتی که عملگر سه وضعیتی نامیده می‌شود فشرده می‌سازیم. به طور کلی قالب این عملگر سه وضعیتی به صورت زیر است و می‌توانیم هر مقدار که لازم است به آن اضافه کنیم:

<expression1> if <condition1> else <expression2> if <condition2>

در اسکریپت ما قالبی که استفاده می‌کنیم به صورت زیر خواهد بود:

<password match response> if <hashes match> else <password not in dictionary response> if <password is empty> else <password does not match response>

پس از اعمال این تغییرات می‌توانیم کدمان را مانند مثال زیر فشرده بکنیم:

from urllib.request import urlopen, hashlib; origin = input("Input SHA1 hash to crack\n>")
for password in str(urlopen('https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10-million-password-list-top-10000.txt').read(), 'utf-8').split('\n'):
    [print("The password is ", str(password)), quit()] if (hashlib.sha1(bytes(password, 'utf-8')).hexdigest()) == origin else print("Password not in database, we'll get them next time.") if password == "" else print("Password guess ", str(password), " does not match, trying next...")

سخن پایانی

با این که کد فوق بدون وجود هیچ توضیحی برای کسی که به تازگی با پایتون آشنا شده است، وحشت‌انگیز محسوب می‌شود؛ اما باید توجه داشته باشید که پایتون می‌تواند از یک ایده خام به سادگی از طریق اجرای برنامه و بررسی میانبرها به صورت چند خط خلاصه، فشرده شود.

اگر می‌خواهید این کد را به صورت یک خط درآورید، می‌توانید آن را درون تابع ()exec قرار دهید و کاراکترهای خط جدید (n\) را برای هر خط جدید در آن تعیین کنید. البته موارد استفاده این وضعیت زیاد نیستند؛ ولی اگر می‌خواهید کد خود را در حد امکان فشرده کنید، امکان خوبی محسوب می‌شود.

امیدواریم از این راهنمای مبتدی برای نوشتن بروت فورس SHA-1 در پایتون لذت برده باشید. در صورتی که هر نوع دیدگاه، پیشنهاد و یا انتقادی در مورد این راهنما یا برنامه‌نویسی مقدماتی پایتون 3 دارید می‌توانید در ادامه در بخش نظرات، با ما و دیگر خوانندگان فرادرس در میان بگذارید.

اگر این نوشته موردتوجه شما قرار گرفته است، پیشنهاد می‌کنیم موارد زیر را نیز ملاحظه نمایید:

==

بر اساس رای ۳ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
wonderhowto
نظر شما چیست؟

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