import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import subprocess
import base64
import platform
import ipaddress
import secrets
import qrcode
from PIL import Image, ImageTk
import io
# 导入 cryptography 库相关模块
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives import serialization
class WireGuardConfigGenerator:
def __init__(self, root):
self.root = root
self.root.title("WireGuard 配置生成器")
self.root.geometry("700x650")
# 判断操作系统
self.is_windows = platform.system() == "Windows"
# 样式
self.style = ttk.Style()
self.style.configure("TFrame", padding=10)
self.style.configure("TButton", padding=5)
self.style.configure("TLabel", padding=5)
# 变量
self.server_private_key = tk.StringVar()
self.server_public_key = tk.StringVar()
self.server_address = tk.StringVar(value="10.0.0.1/24")
self.server_port = tk.StringVar(value="51820")
self.server_endpoint = tk.StringVar(value="example.com")
self.output_dir = tk.StringVar(value=os.path.expanduser("~"))
self.client_ips = []
self.client_private_keys = []
self.client_public_keys = []
self.client_names = []
# 主框架
main_frame = ttk.Frame(root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建标签页
self.notebook = ttk.Notebook(main_frame)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 服务器标签页
server_tab = ttk.Frame(self.notebook)
self.notebook.add(server_tab, text="服务器")
# 客户端标签页
client_tab = ttk.Frame(self.notebook)
self.notebook.add(client_tab, text="客户端")
# 生成标签页
generate_tab = ttk.Frame(self.notebook)
self.notebook.add(generate_tab, text="生成配置")
# 二维码标签页
qr_tab = ttk.Frame(self.notebook)
self.notebook.add(qr_tab, text="二维码")
# 设置服务器标签页
self.setup_server_tab(server_tab)
# 设置客户端标签页
self.setup_client_tab(client_tab)
# 设置生成标签页
self.setup_generate_tab(generate_tab)
# 设置二维码标签页
self.setup_qr_tab(qr_tab)
def setup_server_tab(self, parent):
# 服务器密钥框架
keys_frame = ttk.LabelFrame(parent, text="服务器密钥")
keys_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Button(keys_frame, text="生成密钥", command=self.generate_server_keys).grid(row=0, column=0, padx=5, pady=5)
ttk.Label(keys_frame, text="私钥:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(keys_frame, textvariable=self.server_private_key, width=50).grid(row=1, column=1, padx=5, pady=2)
ttk.Label(keys_frame, text="公钥:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(keys_frame, textvariable=self.server_public_key, width=50).grid(row=2, column=1, padx=5, pady=2)
# 服务器网络框架
network_frame = ttk.LabelFrame(parent, text="服务器网络")
network_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(network_frame, text="监听端口:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(network_frame, textvariable=self.server_port, width=10).grid(row=0, column=1, sticky=tk.W, padx=5,
pady=2)
ttk.Label(network_frame, text="地址:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(network_frame, textvariable=self.server_address, width=20).grid(row=1, column=1, sticky=tk.W, padx=5,
pady=2)
ttk.Label(network_frame, text="公网域名/IP:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(network_frame, textvariable=self.server_endpoint, width=30).grid(row=2, column=1, sticky=tk.W, padx=5,
pady=2)
# 输出目录
output_frame = ttk.LabelFrame(parent, text="输出目录")
output_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Entry(output_frame, textvariable=self.output_dir, width=50).grid(row=0, column=0, padx=5, pady=5)
ttk.Button(output_frame, text="浏览...", command=self.browse_output_dir).grid(row=0, column=1, padx=5, pady=5)
def setup_client_tab(self, parent):
# 控制框架
controls_frame = ttk.Frame(parent)
controls_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Button(controls_frame, text="添加客户端", command=self.add_client).pack(side=tk.LEFT, padx=5, pady=5)
ttk.Button(controls_frame, text="删除选中", command=self.remove_client).pack(side=tk.LEFT, padx=5, pady=5)
# 客户端列表
tree_frame = ttk.Frame(parent)
tree_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.tree = ttk.Treeview(tree_frame, columns=("name", "ip", "pubkey"), show="headings")
self.tree.heading("name", text="名称")
self.tree.heading("ip", text="IP地址")
self.tree.heading("pubkey", text="公钥")
self.tree.column("name", width=100)
self.tree.column("ip", width=150)
self.tree.column("pubkey", width=300)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 滚动条
scrollbar = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=self.tree.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.configure(yscrollcommand=scrollbar.set)
def setup_generate_tab(self, parent):
# 生成按钮框架
gen_frame = ttk.Frame(parent)
gen_frame.pack(fill=tk.X, padx=5, pady=20)
ttk.Button(gen_frame, text="生成服务器配置", command=self.generate_server_config).pack(pady=10)
ttk.Button(gen_frame, text="生成所有客户端配置", command=self.generate_all_client_configs).pack(pady=10)
ttk.Button(gen_frame, text="生成所有配置", command=self.generate_all_configs).pack(pady=10)
# 预览框架
preview_frame = ttk.LabelFrame(parent, text="配置预览")
preview_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.preview_text = tk.Text(preview_frame, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
def setup_qr_tab(self, parent):
# 客户端选择框架
select_frame = ttk.LabelFrame(parent, text="选择客户端")
select_frame.pack(fill=tk.X, padx=5, pady=5)
self.selected_client = tk.StringVar()
self.client_dropdown = ttk.Combobox(select_frame, textvariable=self.selected_client, state="readonly")
self.client_dropdown.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)
ttk.Button(select_frame, text="生成二维码", command=self.generate_qr_code).pack(side=tk.LEFT, padx=5, pady=5)
ttk.Button(select_frame, text="保存二维码", command=self.save_qr_code).pack(side=tk.LEFT, padx=5, pady=5)
# 二维码显示框架
qr_display_frame = ttk.LabelFrame(parent, text="二维码")
qr_display_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.qr_canvas = tk.Canvas(qr_display_frame, bg="white")
self.qr_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 存储当前二维码图像
self.current_qr_image = None
self.current_qr_photoimage = None
def browse_output_dir(self):
folder = filedialog.askdirectory(initialdir=self.output_dir.get())
if folder:
self.output_dir.set(folder)
def generate_wireguard_keypair(self):
"""使用 cryptography 库生成 WireGuard 密钥对"""
try:
# 生成私钥
private_key = X25519PrivateKey.generate()
# 将私钥转换为字节并进行 Base64 编码
private_bytes = private_key.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
private_key_b64 = base64.b64encode(private_bytes).decode('ascii')
# 从私钥获取公钥
public_key = private_key.public_key()
# 将公钥转换为字节并进行 Base64 编码
public_bytes = public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
public_key_b64 = base64.b64encode(public_bytes).decode('ascii')
return private_key_b64, public_key_b64
except Exception as e:
messagebox.showerror("错误", f"生成密钥对失败: {str(e)}")
return None, None
def generate_server_keys(self):
try:
# 检查是否可以使用 wg 命令行工具
if self.is_windows or not self.check_wg_available():
# 使用 cryptography 库生成密钥对
private_key_b64, public_key_b64 = self.generate_wireguard_keypair()
else:
# 使用 wg 命令行工具生成密钥对
private_key_proc = subprocess.run(['wg', 'genkey'], capture_output=True, text=True)
if private_key_proc.returncode != 0:
raise Exception("生成私钥失败")
private_key_b64 = private_key_proc.stdout.strip()
public_key_proc = subprocess.run(['wg', 'pubkey'], input=private_key_b64, capture_output=True,
text=True)
if public_key_proc.returncode != 0:
raise Exception("生成公钥失败")
public_key_b64 = public_key_proc.stdout.strip()
if private_key_b64 and public_key_b64:
self.server_private_key.set(private_key_b64)
self.server_public_key.set(public_key_b64)
except Exception as e:
messagebox.showerror("错误", f"生成密钥失败: {str(e)}")
def check_wg_available(self):
"""检查 wg 命令行工具是否可用"""
try:
result = subprocess.run(['wg', '--version'], capture_output=True, text=True)
return result.returncode == 0
except:
return False
def add_client(self):
# 创建添加客户端对话框
dialog = tk.Toplevel(self.root)
dialog.title("添加客户端")
dialog.geometry("400x250")
dialog.transient(self.root)
dialog.grab_set()
# 新客户端的变量
client_name = tk.StringVar()
client_ip = tk.StringVar()
client_private_key = tk.StringVar()
client_public_key = tk.StringVar()
# 客户端信息框架
info_frame = ttk.LabelFrame(dialog, text="客户端信息")
info_frame.pack(fill=tk.X, padx=10, pady=10)
ttk.Label(info_frame, text="名称:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(info_frame, textvariable=client_name).grid(row=0, column=1, padx=5, pady=2)
ttk.Label(info_frame, text="IP地址:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
# 根据现有客户端创建默认IP
next_ip = self.get_next_ip()
client_ip.set(next_ip)
ttk.Entry(info_frame, textvariable=client_ip).grid(row=1, column=1, padx=5, pady=2)
# 客户端密钥框架
keys_frame = ttk.LabelFrame(dialog, text="客户端密钥")
keys_frame.pack(fill=tk.X, padx=10, pady=10)
def generate_client_keys():
try:
# 检查是否可以使用 wg 命令行工具
if self.is_windows or not self.check_wg_available():
# 使用 cryptography 库生成密钥对
private_key_b64, public_key_b64 = self.generate_wireguard_keypair()
else:
# 使用 wg 命令行工具生成密钥对
private_key_proc = subprocess.run(['wg', 'genkey'], capture_output=True, text=True)
if private_key_proc.returncode != 0:
raise Exception("生成私钥失败")
private_key_b64 = private_key_proc.stdout.strip()
public_key_proc = subprocess.run(['wg', 'pubkey'], input=private_key_b64, capture_output=True,
text=True)
if public_key_proc.returncode != 0:
raise Exception("生成公钥失败")
public_key_b64 = public_key_proc.stdout.strip()
if private_key_b64 and public_key_b64:
client_private_key.set(private_key_b64)
client_public_key.set(public_key_b64)
except Exception as e:
messagebox.showerror("错误", f"生成密钥失败: {str(e)}")
ttk.Button(keys_frame, text="生成密钥", command=generate_client_keys).grid(row=0, column=0, columnspan=2,
padx=5, pady=5)
ttk.Label(keys_frame, text="私钥:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(keys_frame, textvariable=client_private_key).grid(row=1, column=1, padx=5, pady=2)
ttk.Label(keys_frame, text="公钥:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
ttk.Entry(keys_frame, textvariable=client_public_key).grid(row=2, column=1, padx=5, pady=2)
# 按钮
buttons_frame = ttk.Frame(dialog)
buttons_frame.pack(fill=tk.X, padx=10, pady=10)
def save_client():
name = client_name.get().strip()
ip = client_ip.get().strip()
private_key = client_private_key.get().strip()
public_key = client_public_key.get().strip()
# 验证输入
if not name:
messagebox.showerror("错误", "客户端名称是必填项")
return
if not ip:
messagebox.showerror("错误", "IP地址是必填项")
return
if not private_key or not public_key:
messagebox.showerror("错误", "密钥是必填项")
return
# 添加到我们的列表
self.client_names.append(name)
self.client_ips.append(ip)
self.client_private_keys.append(private_key)
self.client_public_keys.append(public_key)
# 添加到树视图
self.tree.insert("", tk.END, values=(name, ip, public_key))
# 更新客户端下拉列表
self.update_client_dropdown()
dialog.destroy()
ttk.Button(buttons_frame, text="保存", command=save_client).pack(side=tk.RIGHT, padx=5)
ttk.Button(buttons_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
def update_client_dropdown(self):
"""更新客户端下拉列表"""
self.client_dropdown['values'] = self.client_names
if self.client_names:
self.client_dropdown.current(0)
def get_next_ip(self):
# 获取服务器网络以确定下一个客户端IP
server_net = self.server_address.get()
try:
net = ipaddress.IPv4Network(server_net, strict=False)
base_ip = net.network_address
# 从.2开始(服务器通常是.1)
if not self.client_ips:
return f"{base_ip + 1}"
# 找到下一个可用IP
used_ips = set(self.client_ips)
for i in range(2, 255):
candidate = f"{base_ip + i}"
if candidate not in used_ips:
return candidate
return f"{base_ip + 2}" # 默认回退
except Exception:
# 回退
return "10.0.0.2"
def remove_client(self):
selected = self.tree.selection()
if not selected:
messagebox.showinfo("信息", "未选择客户端")
return
# 从树和列表中删除
for item in selected:
index = self.tree.index(item)
self.tree.delete(item)
del self.client_names[index]
del self.client_ips[index]
del self.client_private_keys[index]
del self.client_public_keys[index]
# 更新客户端下拉列表
self.update_client_dropdown()
def generate_server_config(self):
# 验证
if not self.server_private_key.get():
messagebox.showerror("错误", "服务器私钥是必填项")
return
# 创建配置
config = "[Interface]\n"
config += f"PrivateKey = {self.server_private_key.get()}\n"
config += f"Address = {self.server_address.get()}\n"
config += f"ListenPort = {self.server_port.get()}\n"
# 添加特定平台的防火墙规则
if self.is_windows:
config += f"PostUp = netsh advfirewall firewall add rule name=\"WireGuard\" dir=in action=allow protocol=UDP localport={self.server_port.get()}\n"
config += f"PostDown = netsh advfirewall firewall delete rule name=\"WireGuard\" dir=in protocol=UDP localport={self.server_port.get()}\n"
else:
# Linux
config += "PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\n"
config += "PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\n"
# 将客户端添加为对等点
for i in range(len(self.client_names)):
config += "\n[Peer]\n"
config += f"# {self.client_names[i]}\n"
config += f"PublicKey = {self.client_public_keys[i]}\n"
config += f"AllowedIPs = {self.client_ips[i]}/32\n"
# 显示预览
self.preview_text.delete(1.0, tk.END)
self.preview_text.insert(tk.END, config)
# 保存到文件
filename = os.path.join(self.output_dir.get(), "wg0.conf")
try:
with open(filename, "w") as f:
f.write(config)
messagebox.showinfo("成功", f"服务器配置已保存到 {filename}")
except Exception as e:
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
def get_client_config(self, index):
"""获取指定索引的客户端配置内容"""
# 获取客户端数据
name = self.client_names[index]
ip = self.client_ips[index]
private_key = self.client_private_keys[index]
# 获取服务器端点
endpoint = self.server_endpoint.get()
port = self.server_port.get()
# 创建配置
config = "[Interface]\n"
config += f"PrivateKey = {private_key}\n"
config += f"Address = {ip}/24\n"
config += "DNS = 8.8.8.8, 8.8.4.4\n\n"
config += "[Peer]\n"
config += f"PublicKey = {self.server_public_key.get()}\n"
config += f"Endpoint = {endpoint}:{port}\n"
config += "AllowedIPs = 0.0.0.0/0\n"
config += "PersistentKeepalive = 25\n"
return config
def generate_client_config(self, index):
"""生成并保存客户端配置文件"""
# 获取客户端数据
name = self.client_names[index]
config = self.get_client_config(index)
# 保存到文件
safe_name = name.replace(" ", "_")
filename = os.path.join(self.output_dir.get(), f"{safe_name}.conf")
try:
with open(filename, "w") as f:
f.write(config)
return True
except Exception as e:
messagebox.showerror("错误", f"为{name}保存配置失败: {str(e)}")
return False
def generate_all_client_configs(self):
if not self.client_names:
messagebox.showinfo("信息", "没有客户端可生成配置")
return
if not self.server_public_key.get():
messagebox.showerror("错误", "服务器公钥是必填项")
return
success_count = 0
for i in range(len(self.client_names)):
if self.generate_client_config(i):
success_count += 1
messagebox.showinfo("成功", f"在{self.output_dir.get()}中生成了{success_count}个客户端配置")
def generate_all_configs(self):
self.generate_server_config()
self.generate_all_client_configs()
def generate_qr_code(self):
"""为选定的客户端生成二维码"""
if not self.client_names:
messagebox.showinfo("信息", "没有可用的客户端")
return
selected = self.selected_client.get()
if not selected:
messagebox.showinfo("信息", "请选择一个客户端")
return
# 找到选定客户端的索引
try:
index = self.client_names.index(selected)
except ValueError:
messagebox.showerror("错误", "找不到选定的客户端")
return
# 获取客户端配置
config = self.get_client_config(index)
# 生成二维码
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(config)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# 调整大小以适应画布
canvas_width = self.qr_canvas.winfo_width() if self.qr_canvas.winfo_width() > 1 else 450
canvas_height = self.qr_canvas.winfo_height() if self.qr_canvas.winfo_height() > 1 else 450
# 存储当前二维码图像
self.current_qr_image = img
# 将PIL图像转换为PhotoImage
img_resized = img.resize((min(canvas_width, 450), min(canvas_height, 450)))
# 将PIL图像转换为PhotoImage
self.current_qr_photoimage = ImageTk.PhotoImage(img_resized)
# 清除画布并显示新的二维码
self.qr_canvas.delete("all")
self.qr_canvas.create_image(
canvas_width // 2, canvas_height // 2,
image=self.current_qr_photoimage,
anchor=tk.CENTER
)
def save_qr_code(self):
"""保存当前二维码图像到文件"""
if self.current_qr_image is None:
messagebox.showinfo("信息", "请先生成二维码")
return
selected = self.selected_client.get()
if not selected:
messagebox.showinfo("信息", "请选择一个客户端")
return
# 创建文件保存对话框
safe_name = selected.replace(" ", "_")
filename = filedialog.asksaveasfilename(
initialdir=self.output_dir.get(),
initialfile=f"{safe_name}_qr.png",
defaultextension=".png",
filetypes=[("PNG 图像", "*.png"), ("所有文件", "*.*")]
)
if not filename:
return
try:
self.current_qr_image.save(filename)
messagebox.showinfo("成功", f"二维码已保存到 {filename}")
except Exception as e:
messagebox.showerror("错误", f"保存二维码失败: {str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = WireGuardConfigGenerator(root)
root.mainloop()