تمرین و پروژه‌های پایتون

ساخت تولیدکننده رمز عبور با پایتون؛ پروژه امن و قابل توسعه با secrets

ساخت تولیدکننده رمز عبور با پایتون

🗓 آخرین به‌روزرسانی:

ساخت تولیدکننده رمز عبور با پایتون یک پروژه کوتاه اما بسیار آموزنده است؛ چون هم با رشته‌ها، توابع و ورودی کاربر سروکار داریم و هم با یک موضوع واقعی یعنی تولید داده تصادفی برای کاربردهای امنیتی. در این آموزش فقط چند کاراکتر را با random.choice() کنار هم نمی‌گذاریم. یک برنامه کامل می‌سازیم که طول رمز را از کاربر می‌گیرد، نوع کاراکترهای مجاز را انتخاب می‌کند، حداقل یک کاراکتر از هر گروه انتخاب‌شده را تضمین می‌کند، ورودی نامعتبر را مدیریت می‌کند و برای تولید رمز از ماژول مناسب‌تر secrets استفاده می‌کند.

اگر دو پروژه قبلی این مجموعه، یعنی بازی حدس عدد با پایتون و بازی سنگ کاغذ قیچی با پایتون را انجام داده باشید، ساختار تابع، حلقه و دریافت ورودی برایتان آشناست. این پروژه قدم بعدی خوبی است، چون علاوه بر تمرین مبانی پایتون، تفاوت میان «تصادفی بودن برای بازی» و «تصادفی بودن برای کاربرد حساس» را هم نشان می‌دهد.

در این پروژه چه چیزی می‌سازیم؟

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

  • تعیین طول رمز عبور
  • فعال یا غیرفعال کردن حروف بزرگ انگلیسی
  • فعال یا غیرفعال کردن حروف کوچک انگلیسی
  • فعال یا غیرفعال کردن اعداد
  • فعال یا غیرفعال کردن نمادها
  • تضمین حضور حداقل یک کاراکتر از هر گروه انتخاب‌شده
  • استفاده از secrets به‌جای random برای انتخاب کاراکترها
  • اعتبارسنجی طول و گزینه‌های ورودی
  • امکان تولید چند رمز بدون اجرای دوباره فایل

چرا برای رمز عبور از random استفاده نمی‌کنیم؟

ماژول random برای شبیه‌سازی، بازی، نمونه‌سازی و بسیاری از کارهای معمولی عالی است. در پروژه سنگ کاغذ قیچی، انتخاب کامپیوتر با random.choice() کاملاً مناسب بود. اما برای تولید رمز عبور، توکن یا داده‌ای که قرار است غیرقابل پیش‌بینی باشد، بهتر است از ابزار مخصوص این کار استفاده شود.

ماژول secrets در کتابخانه استاندارد پایتون برای تولید انتخاب‌های تصادفی مناسب کاربردهای امنیتی طراحی شده است. در این پروژه به همین دلیل از secrets.choice() استفاده می‌کنیم. اگر با مفهوم ماژول‌ها آشنایی کافی ندارید، آموزش ماژول‌ها و پکیج‌ها در پایتون را مطالعه کنید.

پیش‌نیازهای پروژه

هیچ کتابخانه خارجی لازم نیست. secrets و string هر دو همراه پایتون هستند. یک فایل با نام زیر بسازید:

password_generator.py

سپس آن را اجرا کنید:

python password_generator.py

برای درک بهتر کد، آشنایی با تابع input در پایتون و پارامتر و آرگومان در توابع پایتون مفید است.

مرحله اول: وارد کردن ماژول‌های مورد نیاز

import secrets
import string

ماژول string چند مجموعه آماده از کاراکترها در اختیار ما می‌گذارد. برای مثال:

print(string.ascii_uppercase)
print(string.ascii_lowercase)
print(string.digits)
print(string.punctuation)

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

مرحله دوم: ساخت ساده‌ترین نسخه تولید رمز

اگر بخواهیم یک نسخه بسیار ساده بسازیم، می‌توانیم همه کاراکترها را در یک Pool قرار دهیم و از آن انتخاب کنیم:

characters = (
    string.ascii_letters
    + string.digits
    + string.punctuation
)

password = "".join(
    secrets.choice(characters)
    for _ in range(16)
)

print(password)

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

مرحله سوم: طراحی تابع generate_password

تابع اصلی پروژه چند پارامتر دریافت می‌کند تا بتوانیم رفتار آن را کنترل کنیم:

def generate_password(
    length=16,
    use_upper=True,
    use_lower=True,
    use_digits=True,
    use_symbols=True,
):
    pass

به این ترتیب تابع فقط برای یک حالت ثابت نوشته نشده و در پروژه‌های دیگر هم قابل استفاده است.

ساخت گروه‌های انتخاب‌شده

selected_sets = []

if use_upper:
    selected_sets.append(string.ascii_uppercase)

if use_lower:
    selected_sets.append(string.ascii_lowercase)

if use_digits:
    selected_sets.append(string.digits)

if use_symbols:
    selected_sets.append(string.punctuation)

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

if not selected_sets:
    raise ValueError(
        "حداقل یک گروه کاراکتری باید فعال باشد."
    )

استفاده از Exception باعث می‌شود تابع رفتار مشخصی در برابر تنظیمات نامعتبر داشته باشد. برای مرور این مبحث می‌توانید به مدیریت خطا در پایتون با try و except مراجعه کنید.

تضمین حضور هر گروه در رمز عبور

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

password_chars = [
    secrets.choice(chars)
    for chars in selected_sets
]

اگر چهار گروه فعال باشند، در این مرحله چهار کاراکتر داریم و می‌دانیم هر چهار گروه در رمز حضور دارند.

بررسی کافی بودن طول رمز

طول رمز نباید از تعداد گروه‌های اجباری کمتر باشد:

if length < len(selected_sets):
    raise ValueError(
        "طول رمز برای گروه‌های انتخاب‌شده کافی نیست."
    )

در رابط تعاملی این پروژه حداقل طول را ۸ قرار می‌دهیم، بنابراین این خطا معمولاً رخ نمی‌دهد، اما نگه داشتن بررسی در خود تابع باعث می‌شود تابع مستقل و قابل اعتماد باقی بماند.

تکمیل باقی کاراکترهای رمز

همه مجموعه‌های انتخابی را کنار هم می‌گذاریم:

pool = "".join(selected_sets)

سپس تا رسیدن به طول مورد نظر، کاراکتر جدید اضافه می‌کنیم:

for _ in range(
    length - len(password_chars)
):
    password_chars.append(
        secrets.choice(pool)
    )

چرا باید ترتیب کاراکترها را مخلوط کنیم؟

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

secrets.SystemRandom().shuffle(
    password_chars
)

در آخر لیست را به رشته تبدیل می‌کنیم:

return "".join(password_chars)

نسخه کامل تابع تولید رمز عبور

def generate_password(
    length=16,
    use_upper=True,
    use_lower=True,
    use_digits=True,
    use_symbols=True,
):
    selected_sets = []

    if use_upper:
        selected_sets.append(
            string.ascii_uppercase
        )

    if use_lower:
        selected_sets.append(
            string.ascii_lowercase
        )

    if use_digits:
        selected_sets.append(
            string.digits
        )

    if use_symbols:
        selected_sets.append(
            string.punctuation
        )

    if not selected_sets:
        raise ValueError(
            "حداقل یک گروه کاراکتری باید فعال باشد."
        )

    if length < len(selected_sets):
        raise ValueError(
            "طول رمز برای گروه‌های انتخاب‌شده کافی نیست."
        )

    password_chars = [
        secrets.choice(chars)
        for chars in selected_sets
    ]

    pool = "".join(selected_sets)

    for _ in range(
        length - len(password_chars)
    ):
        password_chars.append(
            secrets.choice(pool)
        )

    secrets.SystemRandom().shuffle(
        password_chars
    )

    return "".join(password_chars)

مرحله چهارم: دریافت طول رمز از کاربر

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

def get_password_length():
    while True:
        value = input(
            "طول رمز را وارد کنید (8 تا 128): "
        ).strip()

        try:
            length = int(value)
        except ValueError:
            print("لطفاً یک عدد صحیح وارد کنید.")
            continue

        if 8 <= length <= 128:
            return length

        print(
            "طول رمز باید بین 8 تا 128 باشد."
        )

مرحله پنجم: ساخت تابع سؤال بله یا خیر

برای هر گروه کاراکتری باید از کاربر سؤال کنیم. تکرار کد برای چهار سؤال ضرورتی ندارد:

def ask_yes_no(message):
    while True:
        answer = input(
            f"{message} (بله/خیر): "
        ).strip().lower()

        if answer in (
            "بله", "yes", "y"
        ):
            return True

        if answer in (
            "خیر", "نه", "no", "n"
        ):
            return False

        print(
            "پاسخ معتبر نیست؛ بله یا خیر وارد کنید."
        )

کد کامل پروژه تولیدکننده رمز عبور با پایتون

import secrets
import string


def generate_password(
    length=16,
    use_upper=True,
    use_lower=True,
    use_digits=True,
    use_symbols=True,
):
    selected_sets = []

    if use_upper:
        selected_sets.append(
            string.ascii_uppercase
        )

    if use_lower:
        selected_sets.append(
            string.ascii_lowercase
        )

    if use_digits:
        selected_sets.append(
            string.digits
        )

    if use_symbols:
        selected_sets.append(
            string.punctuation
        )

    if not selected_sets:
        raise ValueError(
            "حداقل یک گروه کاراکتری باید فعال باشد."
        )

    if length < len(selected_sets):
        raise ValueError(
            "طول رمز برای گروه‌های انتخاب‌شده کافی نیست."
        )

    password_chars = [
        secrets.choice(chars)
        for chars in selected_sets
    ]

    pool = "".join(selected_sets)

    for _ in range(
        length - len(password_chars)
    ):
        password_chars.append(
            secrets.choice(pool)
        )

    secrets.SystemRandom().shuffle(
        password_chars
    )

    return "".join(password_chars)


def get_password_length():
    while True:
        value = input(
            "طول رمز را وارد کنید (8 تا 128): "
        ).strip()

        try:
            length = int(value)
        except ValueError:
            print("لطفاً یک عدد صحیح وارد کنید.")
            continue

        if 8 <= length <= 128:
            return length

        print(
            "طول رمز باید بین 8 تا 128 باشد."
        )


def ask_yes_no(message):
    while True:
        answer = input(
            f"{message} (بله/خیر): "
        ).strip().lower()

        if answer in (
            "بله", "yes", "y"
        ):
            return True

        if answer in (
            "خیر", "نه", "no", "n"
        ):
            return False

        print(
            "پاسخ معتبر نیست؛ بله یا خیر وارد کنید."
        )


def main():
    print(
        "=== تولیدکننده رمز عبور با پایتون ==="
    )

    while True:
        length = get_password_length()

        use_upper = ask_yes_no(
            "از حروف بزرگ استفاده شود؟"
        )
        use_lower = ask_yes_no(
            "از حروف کوچک استفاده شود؟"
        )
        use_digits = ask_yes_no(
            "از اعداد استفاده شود؟"
        )
        use_symbols = ask_yes_no(
            "از نمادها استفاده شود؟"
        )

        try:
            password = generate_password(
                length=length,
                use_upper=use_upper,
                use_lower=use_lower,
                use_digits=use_digits,
                use_symbols=use_symbols,
            )
        except ValueError as error:
            print(f"خطا: {error}")
            print(
                "تنظیمات را دوباره انتخاب کنید."
            )
            continue

        print(
            f"\nرمز تولیدشده:\n{password}"
        )

        again = ask_yes_no(
            "رمز دیگری تولید شود؟"
        )

        if not again:
            print("برنامه پایان یافت.")
            break


if __name__ == "__main__":
    main()

نمونه اجرای برنامه

=== تولیدکننده رمز عبور با پایتون ===
طول رمز را وارد کنید (8 تا 128): 16
از حروف بزرگ استفاده شود؟ (بله/خیر): بله
از حروف کوچک استفاده شود؟ (بله/خیر): بله
از اعداد استفاده شود؟ (بله/خیر): بله
از نمادها استفاده شود؟ (بله/خیر): بله

رمز تولیدشده:
K3Ru:{4K_&&XydJ<

رمز دیگری تولید شود؟ (بله/خیر): خیر
برنامه پایان یافت.

اگر بعضی سایت‌ها همه نمادها را قبول نکنند چه کنیم؟

string.punctuation مجموعه‌ای نسبتاً بزرگ از نمادها دارد و بعضی سرویس‌ها ممکن است تعدادی از آن‌ها را نپذیرند. در چنین شرایطی می‌توانید یک مجموعه سفارشی تعریف کنید:

SAFE_SYMBOLS = "!@#$%&*_-+=?"

سپس در بخش نمادها به‌جای string.punctuation از SAFE_SYMBOLS استفاده کنید. این تغییر به معنی «امن‌تر بودن» آن مجموعه نیست؛ فقط سازگاری با محدودیت سرویس مقصد را بیشتر می‌کند.

اشتباه‌های رایج در پروژه تولید رمز عبور

استفاده از random برای رمز واقعی

برای تمرین برنامه‌نویسی ممکن است نمونه‌های زیادی با random ببینید، اما برای رمز یا توکن واقعی بهتر است از secrets استفاده شود.

تضمین نکردن گروه‌های انتخاب‌شده

اگر فقط از Pool مشترک کاراکتر انتخاب کنید، ممکن است رمز نهایی یک گروه خاص را نداشته باشد. نسخه این آموزش ابتدا یک کاراکتر از هر گروه اضافه می‌کند.

ذخیره کردن رمز در فایل بدون نیاز

این برنامه فقط رمز را تولید و نمایش می‌دهد. ذخیره کردن خودکار رمزهای خام در فایل متنی، Log یا دیتابیس می‌تواند خطر ایجاد کند. اگر هدف شما ساخت سیستم حساب کاربری است، موضوع «ذخیره رمز عبور» مسئله جداگانه‌ای است و معمولاً رمز خام نباید ذخیره شود.

فرض اینکه پیچیدگی ظاهری همیشه مساوی امنیت است

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

نکات مهم برای استفاده واقعی

  • برای حساب‌های مختلف رمزهای جداگانه تولید کنید.
  • رمز تولیدشده را در کد منبع، Git، فایل Log یا Screenshot عمومی قرار ندهید.
  • برای مدیریت تعداد زیاد رمز، Password Manager معتبر گزینه عملی‌تری از فایل متنی است.
  • در برنامه‌های وب، ذخیره‌سازی رمز کاربر باید با الگوریتم‌های مناسب Hashing انجام شود؛ این پروژه جایگزین سیستم Authentication نیست.
  • برای داده حساس، الگوریتم امنیتی اختصاصی طراحی نکنید مگر اینکه دقیقاً بدانید چه مسئله‌ای را حل می‌کنید.

چطور پروژه را توسعه دهیم؟

  1. تولید چند رمز: از کاربر تعداد خروجی را بگیرید و مثلاً ۵ رمز متفاوت بسازید.
  2. حذف کاراکترهای مشابه: حروفی مثل O و عدد 0 یا l و 1 را برای خوانایی حذف کنید.
  3. حالت سازگار با سایت: مجموعه نمادهای مجاز را از کاربر دریافت کنید.
  4. نسخه رابط گرافیکی: با Tkinter یک پنجره با Slider طول و Checkbox گروه‌های کاراکتری بسازید.
  5. دکمه Copy: در نسخه GUI خروجی را مستقیم در Clipboard قرار دهید.
  6. خط فرمان: با argparse طول و گروه‌ها را از آرگومان‌های Terminal دریافت کنید.
  7. تولید Passphrase: در پروژه جداگانه به‌جای رشته تصادفی، چند واژه تصادفی مستقل را ترکیب کنید.

تمرین پیشنهادی

تابع generate_password() را طوری توسعه دهید که پارامتر جدیدی با نام exclude_ambiguous داشته باشد. اگر این مقدار True بود، کاراکترهای شبیه به هم مانند 0، O، 1، l و I از Pool حذف شوند. سپس بررسی کنید آیا تضمین حضور هر گروه هنوز درست کار می‌کند یا نه.

یک ایده جالب برای توسعه: رمز وای‌فای ساخته‌شده را به QR کد تبدیل کنید تا مهمان‌ها فقط با اسکن به شبکه وصل شوند. روش آن را در ساخت QR کد با پایتون توضیح داده‌ایم. پروژه‌های بیشتر را در ۳۲ پروژه پایتون از مبتدی تا پیشرفته ببینید.

این پروژه چه مفاهیمی را تمرین می‌دهد؟

  • Import کردن ماژول‌های کتابخانه استاندارد
  • کار با رشته‌ها
  • List و ساخت Pool کاراکترها
  • توابع و پارامترهای پیش‌فرض
  • List Comprehension
  • حلقه for و while
  • شرط‌ها
  • دریافت و اعتبارسنجی ورودی
  • Exception و ValueError
  • تقسیم برنامه به توابع مستقل
  • تفاوت تصادفی‌سازی معمولی و تولید مناسب داده حساس

جمع‌بندی

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

این پروژه تمرین خوبی برای تبدیل مباحث پایه پایتون به یک ابزار واقعی است. قبل از رفتن سراغ پروژه بعدی، پیشنهاد می‌شود حداقل یکی از قابلیت‌های توسعه‌ای را خودتان پیاده‌سازی کنید؛ مخصوصاً حذف کاراکترهای مشابه یا ساخت نسخه‌ای که چند رمز را در یک اجرا تولید کند.

📌 همچنین بخوانید: تمرین پایتون با جواب، ساخت بازی با پایتون و Pygame و ساخت ربات با پایتون

می‌خواهی پیشرفتت ذخیره شود؟با ورود یا ثبت‌نام رایگان، درس‌های تمام‌شده را علامت بزن و درصد پیشرفتت را در حساب کاربری ببین.ورود / ثبت‌نام برای ذخیره پیشرفت
درس بعدی · پروژه‌های پایتون (4 از 19)ساخت برنامه تبدیل واحد با پایتون؛ پروژه کامل طول، وزن، دما، حجم و سرعت ←
آموزش‌های تازه، کد آماده و رفع خطاهر هفته در کانال Dev Source؛ رایگان.
🧭 مسیر یادگیری پایتون از صفر
ماژول 6: توابع — درس 30 از 44

برای ذخیره پیشرفت در همه دستگاه‌ها و دریافت گواهی پایان مسیر، رایگان ثبت‌نام کن یا وارد شو.

دیدگاهتان را بنویسید

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