iT邦幫忙

0

[Day21]簡易密碼產生器

  • 分享至 

  • xImage
  •  

用 Python 標準庫做一個本機密碼產生器:可選字元種類、長度、避免易混淆字元,一鍵複製到剪貼簿,並顯示預估強度(位元熵)。
不需安裝任何套件。

功能重點

  • 勾選字元集:大小寫字母、數字、符號
  • 長度可調(預設 16),可選「避免易混淆字元」(0/O、1/l/I…)
  • 產生時保證每種已勾選的字元至少各出現一次
  • 顯示/隱藏密碼、一鍵複製到剪貼簿
  • 顯示預估強度:位元熵(bits)與簡單等級

程式碼(存成 password_gen_gui.py)

import tkinter as tk
from tkinter import ttk, messagebox
import string, secrets, math, random

SAFE_SYMBOLS = "!#$%&()*+,-./:;<=>?@[]^_{|}~"  # 略去引號、反引號、反斜線

def entropy_bits(length: int, charset_size: int) -> float:
    if length <= 0 or charset_size <= 1:
        return 0.0
    return length * math.log2(charset_size)

def strength_label(bits: float) -> str:
    if bits < 40: return "弱"
    if bits < 60: return "中"
    if bits < 80: return "強"
    return "非常強"

def build_charset(use_lower, use_upper, use_digit, use_symbol, avoid_ambiguous):
    chars = ""
    if use_lower: chars += string.ascii_lowercase
    if use_upper: chars += string.ascii_uppercase
    if use_digit: chars += string.digits
    if use_symbol: chars += SAFE_SYMBOLS
    if avoid_ambiguous:
        for ch in "0O1lI|`'\"\\":
            chars = chars.replace(ch, "")
    return chars

def generate_password():
    try:
        length = int(len_var.get())
        if length <= 0 or length > 128:
            raise ValueError
    except ValueError:
        messagebox.showerror("輸入錯誤", "長度請輸入 1~128 的整數")
        return

    use_lower = lower_var.get()
    use_upper = upper_var.get()
    use_digit = digit_var.get()
    use_symbol = symbol_var.get()
    avoid = avoid_var.get()

    if not (use_lower or use_upper or use_digit or use_symbol):
        messagebox.showerror("設定錯誤", "請至少勾選一種字元類別")
        return

    charset = build_charset(use_lower, use_upper, use_digit, use_symbol, avoid)
    if len(charset) < 2:
        messagebox.showerror("設定錯誤", "有效字元過少,請取消「避免易混淆」或勾選更多類別")
        return

    # 先確保每類至少一個
    buckets = []
    if use_lower:
        c = build_charset(True, False, False, False, avoid)
        buckets.append(secrets.choice(c))
    if use_upper:
        c = build_charset(False, True, False, False, avoid)
        buckets.append(secrets.choice(c))
    if use_digit:
        c = build_charset(False, False, True, False, avoid)
        buckets.append(secrets.choice(c))
    if use_symbol:
        c = build_charset(False, False, False, True, avoid)
        buckets.append(secrets.choice(c))

    # 其餘隨機補齊
    while len(buckets) < length:
        buckets.append(secrets.choice(charset))

    # 打散順序(使用安全亂數的 shuffle)
    sysrand = random.SystemRandom()
    sysrand.shuffle(buckets)

    pwd = "".join(buckets[:length])
    pwd_var.set(pwd)

    bits = entropy_bits(length, len(charset))
    entropy_var.set(f"預估強度:{bits:.1f} bits({strength_label(bits)})")

def copy_to_clip():
    pw = pwd_var.get().strip()
    if not pw:
        messagebox.showinfo("提示", "尚未產生密碼")
        return
    root.clipboard_clear()
    root.clipboard_append(pw)
    root.update()  # 確保離開程式仍在剪貼簿
    messagebox.showinfo("已複製", "密碼已複製到剪貼簿")

def toggle_show():
    pwd_entry.config(show="" if show_var.get() else "•")

# ---------------- GUI ----------------
root = tk.Tk()
root.title("密碼產生器")

main = ttk.Frame(root, padding=16); main.grid()

# 長度
ttk.Label(main, text="長度").grid(row=0, column=0, sticky="e")
len_var = tk.StringVar(value="16")
ttk.Entry(main, textvariable=len_var, width=6, justify="center").grid(row=0, column=1, sticky="w", padx=6)

# 選項
lower_var = tk.BooleanVar(value=True)
upper_var = tk.BooleanVar(value=True)
digit_var = tk.BooleanVar(value=True)
symbol_var = tk.BooleanVar(value=False)
avoid_var = tk.BooleanVar(value=True)

opt_row = ttk.Frame(main); opt_row.grid(row=1, column=0, columnspan=3, pady=6, sticky="w")
ttk.Checkbutton(opt_row, text="小寫 a-z", variable=lower_var).grid(row=0, column=0, padx=4)
ttk.Checkbutton(opt_row, text="大寫 A-Z", variable=upper_var).grid(row=0, column=1, padx=4)
ttk.Checkbutton(opt_row, text="數字 0-9", variable=digit_var).grid(row=0, column=2, padx=4)
ttk.Checkbutton(opt_row, text="符號", variable=symbol_var).grid(row=0, column=3, padx=4)
ttk.Checkbutton(opt_row, text="避免易混淆字元", variable=avoid_var).grid(row=0, column=4, padx=8)

# 生成/複製
btns = ttk.Frame(main); btns.grid(row=2, column=0, columnspan=3, pady=8)
ttk.Button(btns, text="產生", command=generate_password).grid(row=0, column=0, padx=6)
ttk.Button(btns, text="複製", command=copy_to_clip).grid(row=0, column=1, padx=6)

# 顯示密碼
pwd_var = tk.StringVar()
show_var = tk.BooleanVar(value=False)
ttk.Label(main, text="密碼").grid(row=3, column=0, sticky="e", pady=(6,2))
pwd_entry = ttk.Entry(main, textvariable=pwd_var, width=40, show="•")
pwd_entry.grid(row=3, column=1, columnspan=2, sticky="w", pady=(6,2))
ttk.Checkbutton(main, text="顯示", variable=show_var, command=toggle_show).grid(row=4, column=1, sticky="w")

# 熵/強度
entropy_var = tk.StringVar(value="預估強度:—")
ttk.Label(main, textvariable=entropy_var).grid(row=5, column=0, columnspan=3, pady=(8,0), sticky="w")

# 快捷鍵
root.bind("<Return>", lambda e: generate_password())
root.mainloop()

使用方式

python password_gen_gui.py

實作:
https://ithelp.ithome.com.tw/upload/images/20251008/20169368M662m02W9s.png

小提醒

  • 網站常見規則(至少大小寫、數字、符號)可直接勾選對應選項;若遇到網站禁止某些符號,就把「符號」關掉或自行修改 SAFE_SYMBOLS。
  • 「預估強度」是理論上限,實際安全性仍取決於使用情境與儲存方式。

圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言