Python حرفه‌ای: API بررسی رمز عبور

Professional Python: Password Check API

25 اسفند 1399
درسنامه درس 28 از سری پایتون حرفه‌ای
Python حرفه ای: API بررسی رمز عبور (قسمت 28)

پروژه ساخت API بررسی رمز عبور

این پروژه یکی از پروژه های بسیار کاربردی این دوره است. در این پروژه اسکریپتی خواهیم داشت که رمز عبور ما را دریافت خواهد کرد، سپس بررسی می کند که آیا این رمز عبور تا به حال هک شده است یا خیر! سوالی که در این قسمت مطرح می شود این است که از کجا بدانیم رمز عبور ما هک شده است؟ اصلا این تشخیص بر اساس چه معیاری است؟

فرض کنید شما در وب سایت خاصی ثبت نام کرده اید. این وب سایت در آینده دچار data breach (انتشار داده ها توسط هکرها) می شود بنابراین اگر از رمز عبور شما محافظت نشده باشد، رمز عبور شما نیز به دنیای هکرها رسیده است. هکرها این رمزهای عبور را در پایگاه های داده خود ذخیره می کنند و بعد ها از آن برای ورود به حساب های کاربری مختلف استفاده می کنند. کمپانی هایی مانند فیسبوک و یاهو و ناسا و امثال آن نیز بارها دچار حملات data breach شده اند بنابراین از وب سایت های عادی انتظار زیادی نمی رود.

تشخیص اطلاعات شخصی در data breach

همانطور که در بخش قبلی توضیح دادم در صورت وقوع data breach داده های شخصی ما (بسته به سایت و نوع امنیت آن ها) به صورت عمومی پخش می شوند. آقایی به نام Troy Hunt که یکی از محققان مشهور حوزه امنیت می باشد، در همین راستا وب سایتی را به نام haveibeenpwned تاسیس کرده است که تمام داده های نشت شده را ردیابی کرده و به کاربران اطلاع می دهد که آیا آن داده خاص نشت شده است یا خیر. زمانی که به صفحه اول این وب سایت مراجعه می کنید، ایمیل شما را درخواست می کند. در صورتی که ایمیل خود را وارد کنید، به شما می گوید که آیا ایمیل شما نشت شده است یا خیر. اگر فرد فعالی در اینترنت باشید به احتمال زیاد حداقل یک نشتی ساده داشته اید چرا که ایمیل ها به صورت عمومی به وب سایت های بسیار پاس داده می شوند و حتی در برخی از وب سایت ها در قسمت کامنت ها نیز ذکر می شوند بنابراین نباید آنچنان نگران این موضوع باشید. نشت آدرس ایمیل به تنهایی مشکل امنیتی بزرگی نیست گرچه حالت ایده آلی هم نخواهد بود.

در مرحله بعدی به قسمت passwords از این سایت بروید (آدرس haveibeenpwned.com/Passwords) و رمز عبور خود را نیز وارد کنید. این وب سایت به شما خواهد گفت که آیا رمز عبور شما قبلا توسط هکرها نشت شده است یا خیر. در صورتی که نشتی برای رمز عبور شما پیدا شود باید سریعا آن رمز عبور را تغییر داده و دیگر هیچگاه از آن استفاده نکنید. چرا؟ هکرها از حملاتی با عنوان dictionary attack استفاده می کنند. dictionary attack همانطور که از نامش پیدا است، حمله ای است که در طی آن رمزهای عبور مختلف از یک دیکشنری (لیست بزرگی از رمز عبور های نشت شده) روی یک حساب کاربری خاص امتحان می شوند. در این فرآیند آنقدر رمزهای مختلف را روی حساب کاربری شما تست می کنند تا به رمز صحیح رسیده و وارد حساب کاربری شما شوند.

آیا این روش امن است؟ اگر دانش کافی در حوزه ارتباطات اینترنتی داشته باشید می دانید که چنین روشی (تایپ رمز عبور در یک فرم و ارسال آن به یک وب سایت) هیچ گاه به صورت ۱۰۰% امن نخواهد بود. با اینکه وب سایت haveibeenpwned.com یک وب سایت مورد اعتماد است اما ممکن است بین ما و سرور های این وب سایت، هکری وجود داشته باشد. چطور؟ ممکن است قبلا کار اشتباهی را انجام داده باشید (مثلا برنامه ای مخرب را دانلود کرده باشید) و یک اسکریپت یا برنامه خرابکار به سیستم شما تزریق شده باشد یا فردی اتصال بین شما و سرور سایت را قطع کرده است تا اطلاعات ارسال را مشاهده کند. با اینکه این وب سایت از پروتکل رمزنگاری شده HTTPS استفاده می کند اما همیشه بهتر است از ریسک های غیر ضروری اجتناب کنید بنابراین روش امن تر چیست؟

به جای استفاده از مرورگر برای انتقال اطلاعات می توانیم از API ارائه شده توسط این وب سایت استفاده کنیم!

روش اشتباه ارسال درخواست به API

در قدم اول از شروع پروژه باید کاری کنیم که بتوانیم به API های مختلف در اینترنت درخواست ارسال کنیم. این کار با پکیجی به نام requests انجام می شود:

pypi.org/project/requests/

این پکیج یک پکیج مشهور در پایتون برای ارتباط با API و ارسال درخواست های HTTP می باشد. با ورود به صفحه بالا می توانید راجع به این پکیج اطلاعات بیشتری کسب کنید. در قدم اول باید با استفاده از دستور زیر، پکیج را نصب کنیم:

pip install requests

با اجرای این دستور پکیج requests برای ما نصب می شود بنابراین باید آن را وارد فایل اسکریپت خود کنیم. من نام فایل خودم را password-checker.py گذاشته ام اما شما می توانید هر نام دیگری را انتخاب کنید:

import requests

در مرحله بعدی باید آدرس API مورد نظرمان را داشته باشیم. برای انجام این کار کافی است که به آدرس زیر از توضیحات رسمی این وب سایت مراجعه کنیم:

haveibeenpwned.com/API/v3

در آنجا می بینیم که آدرس مورد نظر ما بدین شکل است:

https://api.pwnedpasswords.com/range/PASSWORD

البته باید به جای PASSWORD از رمز عبور خودتان استفاده کنید. با این حساب در اسکریپت خودمان می گوییم:

import requests




url = 'https://api.pwnedpasswords.com/range/' + 'mypassword'

result = requests.get(url)

print(result)

در کد بالا مشخص است که من رمز عبور ساده ای (mypassword) را برای تست انتخاب کرده ام و سپس با متد get از پکیج requests آن را ارسال کرده ام. نتیجه این درخواست در متغیر result ذخیره خواهد شد بنابراین آن را چاپ کرده ایم. با اجرای این کد نتیجه زیر را دریافت می کنیم:

<Response [400]>

کد ۴۰۰ در هنگام کار با یک API یعنی نوع درخواست ارسال شده توسط ما اشتباه بوده است. یعنی چه؟ معمولا خطای ۴۰۰ زمانی رخ می دهد یک فیلد خاص از درخواست ما موجود نباشد یا به جای یک فیلد، فیلد دیگری را ارسال کرده باشیم و الی آخر. روش صحیح ارسال این درخواست چیست؟ این API از ما مقدار هش شده رمز عبور را می خواهد نه خود رمز عبور را! چرا؟ همانطور که گفتم اگر خود رمز عبور را به صورت ساده ارسال کنیم ممکن است توسط فردی خرابکار به سرقت برود بنابراین ارسال هش بسیار راحت تر است.

Hash چیست و چرا؟

برای هَش کردن رمز عبور ابتدا باید با مفهوم هش آشنا شوید. هش ها نوعی رمزنگاری یک طرفه هستند که به ازای یک مقدار خاص، مقدار هش شده یا رمزنگاری شده خاصی با طول ثابت را برمی گردانند. منظور من از یک طرفه چیست؟ زمانی که می گوییم هش یا توابعی یک طرفه هستند یعنی نمی توانیم به صورت عادی از یک هش، مقدار اصلی آن را پیدا کنیم. برای مشاهده یک هش ساده می توانیم به وب سایت passwordsgenerator.net/sha1-hash-generator مراجعه کنیم. هش های مختلفی با الگوریتم های مختلفی وجود دارند. به طور مثال MD5 یا SHA512 یا SHA1 و غیره. شما می توانید لیست کاملی از توابع هش را در این صفحه ویکی پدیا مطالعه کنید. ما می توانیم به وب سایت passwordsgenerator.net مراجعه کرده و سپس مقدار مورد نظر خودمان را در آن وارد کنیم، در نهایت مقدار هش شده آن را دریافت خواهیم کرد. به طور مثال اگر من عبارت Roxo را تایپ کنم چنین مقداری را می گیرم:

EAB02B6983AF237D76F044943CF6D220858DD3F7

این رشته رمز گونه، هشِ رشته Roxo است و همانطور که گفتم اگر این هش را به کسی بدهیم، نمی تواند مقدار اصلی (Roxo) را از آن استخراج کند. تمام وب سایت های معروف دنیا و افرادی که در حوزه کاری خود اطلاعات خوبی دارند، رمز عبور کاربران را هش می کنند. آیا حملات منجر به data breach را به خاطر دارید؟ اگر وب سایتی که دچار حمله شده است، رمز عبور کاربران خود را به صورت هش شده ذخیره کرده باشد دیگر هکرها به این رمزهای عبور دسترسی نخواهند داشت.

ما باید در API خود به جای ارسال رمز عبور این هش را ارسال کنیم تا اگر کسی بین سیستم ما و سرور های وب سایت نشسته باشد باز هم نتواند به رمز عبور ما دسترسی پیدا کند و فقط رشته ای رمزنگاری شده را پیدا کند. سوال بعدی اینجاست که این وب سایت چطور می تواند این هش را با رمزهای عبور دیگر مقایسه کند؟ وب سایت haveibeenpwned.com با ما قرارداد می بندد که فقط از هش های SHA1 استفاده کنیم. همچنین باید بدانید که این وب سایت تمام رمزهای نشت شده را به صورت هش ذخیره می کند بنابراین هش ارسالی توسط ما را با هش ذخیره شده در پایگاه داده خود بررسی می کند و بدین ترتیب بدون اینکه رمز ما را بداند، تشخیص می دهد که رمز عبور ما در پایگاه داده وجود دارد (نشت شده است) یا ایمن است.

روش صحیح ارسال داده به API

اما باز هم سوالی پیش می آید: آیا این روش واقعا ایمن است؟ آیا هش ها غیر قابل نفوذ هستند و کسی نمی تواند آن ها را مهندسی معکوس کند؟ در سال های اخیر برخی از الگوریتم های hashing شکسته شده اند و عملا می توانیم آن ها را مهندسی معکوس کنیم. به طور مثال موسسه مهندسی نرم افزار CMU اعلام کرده است که MD5 از نظر رمزنگاری شکسته شده است و از این به بعد منسوخ اعلام می شود. البته اکثر الگوریتم های hashing هنوز معتبر هستند و MD5 الگوریتمی بسیار ساده بود. با این حال برای رعایت امنیت بیشتر، وب سایت haveibeenpwned.com از تکنیکی به نام k-Anonymity استفاده می کند. این تکنیک به وب سایت ها اجازه می دهد فردیت ما را تشخیص دهند، بدون اینکه اطلاعات شخصی ما را داشته باشند! چطور؟ ما می توانیم به جای ارسال تمام رشته هش شده، فقط ۵ حرف اول را برای haveibeenpwned ارسال کنیم و این وب سایت نیز لیستی از هش های موجود با این ۵ حرف را برای ما برمی گرداند. حالا می توانیم خودمان از بین هش ها به دنبال هش کامل خود بگردیم تا بدانیم که داده های ما نشت پیدا کرده اند یا خیر:

import requests




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2




url = 'https://api.pwnedpasswords.com/range/' + '91DFD'

result = requests.get(url)

print(result)

رمز من mypassword است و هش کامل آن را نیز به صورت یک کامنت برایتان قرار داده ام تا آن را ببینید اما توجه داشته باشید که من در درخواست خودم تنها ۵ حرف اول (91DFD) را ارسال کرده ام. با اجرای کد بالا نتیجه زیر را می گیریم:

<Response [200]>

کد وضعیت ۲۰۰ به معنی موفقیت آمیز بودن درخواست ما است اما داده های ما کجاست؟ بیایید این کد را به صورت یک تابع بنویسیم:

import requests

import hashlib




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2







def pwned_api_check(password):

    sha1password = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()

    return sha1password

طبیعتا در قدم اول باید رمز عبوری را که دریافت می کنیم تبدیل به هش کنیم به همین دلیل تابع  pwned_api_check را نوشته ام. این تابع از ماژول پیش ساخته ای به نام hashlib استفاده می کنید که در ابتدای فایل import شده است. این ماژول متدی به نام sha1 دارد که باید رمز عبور ما را دریافت کند. روش بالا روشی برای ساخت هش های SHA1 است و باید قالب آن را یاد بگیرید. در نهایت این مقدار هش شده را برگردانده ایم. در مرحله بعدی باید تابعی را برای ارتباط با API بنویسیم بنابراین:

import requests

import hashlib




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2







def request_api_data(query_char):

    url = 'https://api.pwnedpasswords.com/range/' + query_char

    result = requests.get(url)

    if result.status_code != 200:

        raise RuntimeError(f'Error fetching: {result.status_code}')

    return result







def pwned_api_check(password):

    sha1password = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()

    return sha1password

همانطور که می بینید تابع دیگر ما request_api_data نام دارد و ۵ حرف اول هش را دریافت می کند (ما نامش را query_char گذاشته ایم). در مرحله بعدی درخواست خود را مثل قبل به API ارسال می کنیم و اگر کد وضعیت (همان status_code در کد بالا) ۲۰۰ نباشد یعنی درخواست موفقیت آمیز نبوده و یک خطا را برمی گردانیم. این خطا کد وضعیت را نیز درون خودش دارد (مشخص است که از formatted strings استفاده کرده ایم). در غیر این صورت نیز result را برگردانده ایم. مسئله بعدی اینجاست که باید ۵ حرف اول از رمز عبور هش شده را به این تابع (request_api_data) پاس بدهیم اما چطور؟

import requests

import hashlib




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2







def request_api_data(query_char):

    url = 'https://api.pwnedpasswords.com/range/' + query_char

    result = requests.get(url)

    if result.status_code != 200:

        raise RuntimeError(f'Error fetching: {result.status_code}')

    return result







def pwned_api_check(password):

    sha1password = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()

    first5_char, tail = sha1password[:5], sha1password[5:]

    response = request_api_data(first5_char)

    return response

همانطور که می بینید من با استفاده از تکنیک های slicing از ابتدا تا ایندکس ۵ را درون متغیری به نام first5_char و از ایندکس ۵ الی آخر را نیز در متغیری به نام tail قرار داده ام. سپس تابع request_api_data را درون تابع pwned_api_check صدا زده ایم تا بتوانیم ۵ کاراکتر اول را به آن پاس بدهیم. اگر دقت کنید متغیر tail هنوز هیچ استفاده ای ندارد اما فعلا آن را نادیده بگیرید.

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

import requests

import hashlib




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2







def request_api_data(query_char):

    url = 'https://api.pwnedpasswords.com/range/' + query_char

    result = requests.get(url)

    if result.status_code != 200:

        raise RuntimeError(f'Error fetching: {result.status_code}')

    return result







def read_res(response):

    print(response.text)







def pwned_api_check(password):

    sha1password = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()

    first5_char, tail = sha1password[:5], sha1password[5:]

    response = request_api_data(first5_char)

    return read_res(response)







pwned_api_check('password123')

خصوصیت text در شیء response داده های ارسال شده از سمت API را برایمان برمی گرداند بنابراین من تابعی به نام read_res را تعریف کرده ام که فعلا برای تست نتیجه را print می کند تا داده های دریافتی را مشاهده کنیم. این تابع درون pwned_api_check صدا زده شده است و در نهایت نیز خود pwned_api_check را با رمز عبوری ساده مانند password123 صدا زده ایم. با اجرای کد بالا نتیجه زیر را دریافت می کنیم:

00791BB54CC9122C70C1156FD97134EB83E:3

008CDEBE10E31BF09C9BD20CBCC2C9CEDA3:2

00BD64FF4BE8674BC4C85CE380856184F9C:1

0195EA9FE8111E4BF79C5E3062AC43B48E7:1

02F089AA64C104FF24007DC566F85BE1E3D:1

036000F8D5A826C7BE84468F4D0C48E6BAB:4

037334A4FDC8C7678DA86ACFC1F0565FB8C:1

037EBACBBCE93B89F26CDE8F5B53DDBA3CA:2

039196B61C82E7602D68914DA5AB570BF56:2

039510E87FCFAB2652203402AC02554B5D0:4

03F3C0876D60EBE658ABC59FD325C08DAB4:11

049BC3E44471D832F1FB7C178754E302D23:1

06B21476E242EF6444AB88CA94E05D453CF:1

06DFCBB7D24171EBB924FB52E3D0DDD76C4:2

079B135DAEDC937174ABFC7F58926B5AD3B:2

07CF890335992554579FBE532BC9DE96A73:3

085BB497B7D915B8B9552CD8B3FE3A5916B:1

087B378290A9B586D7E2C3ECC97766C8123:5

08F3C8260BA3180C57CBC4DF5DC2900CE57:1

097D641FB95427520A1A31B0F7CD946E5C1:2

098437C5736FCC2F1AA2A0FC41BC7C8150F:1

0B0276DFF60660453DB8B55C6F10C8D2428:1

0B4CE169004FE17614D2A2C14F577A30F35:1

0B6A71AD97A3C1B177FB4C57B5D146AB63F:3

0B9CDFBCEDE61E2C5B489DEE7E60D0D2106:9

0C1EF3FE3FAFCBFF75A6D1BA4FCA9DE0286:2

0C23798FEC41E648AAB9E25DBE621B93A27:1

0D5CE5D590E7A7FC512741AB84EDF0AE5F8:1

0D86A86C4047AA41321EF2A7B0529092773:1

0E408B60FEB8BC9BD1BDB80AD65FFC46241:5

0E68622065AFB533C7A4B068DD3C334E278:2

0E7E8ACDC25C5E726827A44A0E3FF1F05FE:1

0EF1802CDFB7889AFB9ACA40D715F8A0923:3

0FF7F0B7A76D2CF33B2FE2CBC5CF05546DD:1

103EF6DC1C006E00555F2DC6DB2BE7DEF66:1

106F6CEBF6F4843C2DDEE1674E80291B415:3

1072BD16617D8FDBC646B5B65B4FF5F8F3F:3

110C662A266E138ABC5C3E04163B75D82C2:3

111A76470A6D501550639983C57A8C73F42:1

11B281F6F8334E2596B41039B5FBD6087C7:4

13F314A0F290850D9783CC9CCBFF1CAE292:1

1479E963A7CC6EBD32084355A67B84362D9:2

14A5D024233F7B62EBE9C8AA363E095A39C:1

14A648838EDD5EADE97113375ABCC8CE737:1

1507799F9657CA1068E182BFB71D7FD2749:1

15466743A037FCBBDCE9EE9B887D64EA9EF:2

156FB53A3D99EF5D3A10518DB85A1EBB2D9:5

16AFEBB69016833288472896599B9B36D0B:1

172AA6D8C99157A9575E484A89670003A52:1

1746B161285CFDCFB16700E51AC6D7F92EA:1

17A010F35BC1CAAF49DA27E4F1DA8EA2104:2

1895D98FEDD625AC9BA4ADB8BDD8E0961E1:1

194C61639524E3D0305A5D9368FA07F48C7:4

1986BD3EB03C76A148658AF414EA84D9E1B:2

19B3625D2A6C37F9EBCEDBE45F7DBCE5C94:1

1A6642C2E2E0C459AD7EB8D27166DD06C5C:15

1A6A955F0BED67529FEE44B4CAD4F0EA4AF:1

1AAC17B59662C66E682A4A48A6153089760:5

1ADED22822E938DCD066F872E4A78AB00FD:1

1B041321C0159D2BE3D6F59483D79A68EE7:1

1B0D7AF4546364A62985819AAE51089DBFE:4

1B16D4B59DEE1BC2EB7AC15B451CF2B7A3B:1

1B643604F83E2E448003EA0ACB06DCD1364:3

1C57B37BC4C1EADF8FA558026047603D135:2

1C663675C18BF5774213682BD62B5189C30:4

1C96A6FE3C408AB035DEE49BB7271EC2154:14

1D1F3C26C8CA8F672CA6C8A0A1D85C629A4:6

1D23B2F1B5CED8D035AF80D48F5A0819917:2

1DD56368E42BD3769B786BFACF796119032:5

1E07227DBB012ED61D1DCFF4AE84B5149E4:3

1E5425013F990F7188ECC9E864917988B94:1

1EA8256C43D9FD53A179FF1C2A03D0341D3:2

1EC8D038C7CC6BC86316BECCFADDE8C36B9:4

1F446EBA62BC6900E6F811406D43C1E7744:1

1FFDE25361021985C701C2153D1DE2E04AE:1

200252232A59D5F8D4E6F7F3CE7B5C5D171:2

20F9D161395EFFFE57AACF0DB2FBE1F7C81:1

2140F2063B6879EAB51D1268F90E9798EBC:2

// بقیه نتایج

از آنجایی که نتیجه برگردانده شده بسیار زیاد است من فقط قسمتی از آن را برایتان قرار داده ام. در نتیجه بالا و در مقابل هر هش یک عدد وجود دارد (۱ یا ۲ یا ۴ و الی آخر). این عدد نشان می دهد که این رمز عبور تا به حال چند بار نشت پیدا کرده است. ما باید منطقی را بنویسیم که هش ما را از بین این هش ها پیدا کند:

def get_password_leaks_count(hashes, hash_to_check):

  hashes = (line.split(':') for line in hashes.text.splitlines())

  for h, count in hashes:

    if h == hash_to_check:

      return count

  return 0

ما در این قسمت از tuple comprehension استفاده کرده ایم (قبلا با آن آشنا شده بودیم) تا خطوط مختلف را بر اساس علامت دو نقطه (:) جدا کنیم تا هش را در یک قسمت و تعداد دفعات نشت را در قسمت دیگری داشته باشیم. هر کدام از هش ها نیز در یک خط جداگانه برگردانده می شود بنابراین باید از متد splitlines نیز استفاده کنیم تا تقسیم بندی ذکر شده (بر اساس :) روی هر هش انجام شود نه روی کل نتایج. در مرحله بعدی یک for را داریم که h و count را دریافت می کند به طوری که h همان هش ما و count تعداد دفعات نشت خواهد بود. حالا اگر h (هش کامل دریافتی از سمت API) برابر با hash_to_check (هش رمز کاربر که ما دریافت کرده ایم) باشد یعنی رمز عبور کاربر قبلا نشت پیدا کرده است بنابراین تعداد دفعات نشت را برمی گردانیم و در غیر این صورت عدد صفر را برمی گردانیم. حالا می توانیم اسکریپت خود را کامل تر کنیم:

import requests

import hashlib




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2







def request_api_data(query_char):

    url = 'https://api.pwnedpasswords.com/range/' + query_char

    result = requests.get(url)

    if result.status_code != 200:

        raise RuntimeError(f'Error fetching: {result.status_code}')

    return result







def get_password_leaks_count(hashes, hash_to_check):

    hashes = (line.split(':') for line in hashes.text.splitlines())

    for h, count in hashes:

        if h == hash_to_check:

            return count

    return 0







def pwned_api_check(password):

    sha1password = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()

    first5_char, tail = sha1password[:5], sha1password[5:]

    response = request_api_data(first5_char)

    return get_password_leaks_count(response, tail)







pwned_api_check('password123')

این بار من تابع get_password_leaks_count را درون pwned_api_check صدا زده ام تا بتوانیم عملیات مقایسه را انجام بدهیم. اینجاست که باید tail (ادامه هش) را نیز به آن پاس بدهیم تا عملیات جست و جو را انجام بدهد.

در مرحله آخر می خواهم تابعی به نام main تعریف کنم که پارامتر های پاس داده شده توسط ما را از ترمینال دریافت کرده و به این توابع تزریق کند. برای انجام این کار باید ماژول sys را وارد کنیم و سپس مثل جلسات قبل (در پروژه تبدیلگر تصاویر) با متد argv مقادیر پاس داده شده را دریافت کنیم:

import requests

import hashlib

import sys




# HASH: 91DFD9DDB4198AFFC5C194CD8CE6D338FDE470E2







def request_api_data(query_char):

    url = 'https://api.pwnedpasswords.com/range/' + query_char

    res = requests.get(url)

    if res.status_code != 200:

        raise RuntimeError(

            f'Error fetching: {res.status_code}, check the api and try again')

    return res







def get_password_leaks_count(hashes, hash_to_check):

    hashes = (line.split(':') for line in hashes.text.splitlines())

    for h, count in hashes:

        if h == hash_to_check:

            return count

    return 0







def pwned_api_check(password):

    sha1password = hashlib.sha1(password.encode('utf-8')).hexdigest().upper()

    first5_char, tail = sha1password[:5], sha1password[5:]

    response = request_api_data(first5_char)

    return get_password_leaks_count(response, tail)







def main(args):

    for password in args:

        count = pwned_api_check(password)

        if count:

            print(

                f'{password} was found {count} times... you should probably change your password!')

        else:

            print(f'{password} was NOT found. Carry on!')

    return 'done!'







if __name__ == '__main__':

    sys.exit(main(sys.argv[1:]))

اگر به تابع main توجه کنید می بینید که این تابع در args (آرگومان های پاس داده شده از ترمینال) گردش کرده و رمزهای عبور مختلف را دریافت می کند. چطور؟ همانطور که در انتهای کد می بینید من از slicing استفاده کرده ام و گفته ام از ایندکس ۱ به بعد همه چیز را دریافت می کنیم. ما می خواهیم اسکریپت طوری کار کند که بتوانیم هر تعداد رمز عبور را به آن پاس بدهیم و آن هم تک تک رمزهای عبور را بررسی کند. در مرحله بعدی تعداد دفعاتی که رمز عبور نشت کرده است را از تابع pwned_api_check می گیریم و بر اساس وجود رمزهای نشت شده عبارت متناسب با آن را نشان می دهیم. عبارت اول به شکل زیر است:

f'{password} was found {count} times... you should probably change your password!')

این رشته رمز عبور کاربر را نمایش می دهد و می گوید چند بار نشت پیدا کرده است، سپس به کاربر می گوید که باید رمز خود را عوض کند. رشته دوم نیز به شکل زیر است:

print(f'{password} was NOT found. Carry on!')

این رشته به کاربر می گوید که رمز عبور او پیدا نشده است و می تواند ادامه بدهد. در نهایت از __main__ استفاده کرده ایم تا فقط زمانی این اسکریپت را اجرا کنیم که مستقیما فایل password-checker.py (فایل اسکریپت ما) صدا زده شده باشد. قبلا در رابطه با __main__ صحبت کرده ام بنابراین وقت اضافه برای آن نمی گذارم. حالا زمان تست اسکریپت رسیده است. من ابتدا با یک رمز عبور بسیار ساده شروع می کنم،‌ password، و مطمئن هستم که این رمز عبور هزاران بار نشت پیدا کرده است. در مرحله بعدی رمز عبور قوی تری را به آن می دهیم که roxoIsAFreeLearningPlatform می باشد و طبیعتا نشت پیدا نکرده است. برای تست این دو رمز عبور مقدار زیر را در ترمینال می نویسیم:

python3 password-checker.py password roxoIsAFreeLearningPlatform

من در اینجا تنها دو رمز عبور را نوشته ام اما طبیعتا می توانید هر تعدادی را که می خواهید نوشته و ارسال نمایید. با اجرای این کد دو نتیجه زیر را دریافت می کنیم:

password was found 3861493 times... you should probably change your password!

roxoIsAFreeLearningPlatform was NOT found. Carry on!

done!

همانطور که می بینید این کد می گوید رمز عبور password به تعداد 3861493 بار در معرض نشت قرار داشته است بنابراین باید رمز خود را عوض کنیم اما در گردش دوم حلقه و برای رمز عبور roxoIsAFreeLearningPlatform هیچ نشتی پیدا نشده است و می توانیم ادامه بدهیم. در نهایت نیز done را گرفته ایم که در انتهای حلقه در تابع main چاپ کرده بودیم.

پروژه بعدی؟

پروژه بعدی ما یک پروژه ربات نویسی برای سرویس توییتر است! این پروژه یک پروژه بسیار جالب برای تمام افراد است و چند جلسه طول خواهد کشید اما دوست دارم در این قسمت قوانین و کلیت کار را توضیح بدهم تا از جلسه بعدی شروع به کدنویسی کنیم. در ابتدا باید در نظر داشته باشید که قرار است با API توییتر کار کنیم بنابراین باید در آن ثبت نام کنیم.

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

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.