增加密文私鑰功能
#_*_ coding:utf8 _*_
## Python3 密碼管理 V2.2
## 2020-06-05
## 需要安裝第三方模塊 pycryptodome
## 安裝方式 pip install pycryptodome
## 1 生成密鑰對,設置要保存的用戶名和密碼
## 2 對【明文密碼】使用公鑰進行加密後存入本地SQLite3數據庫
## 3 提取數據庫中【密文密碼】使用私鑰解密爲【明文密碼】直接複製到粘貼板中
import os # 操作Windows文件用
import time
import random
## pycryptodome
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
## 數據庫
import sqlite3
## 圖形化模塊
from tkinter import *
from tkinter import filedialog # 選擇文件用
import tkinter.messagebox # 彈出提示對話框
from tkinter import ttk # 下拉菜單控件在ttk中
top = Tk() # 初始化Tk()
top.title('Python3 密碼管理 V2.2') # 設置標題
width = 820 # 設置窗口寬
height = 800 # 設置窗口高
# 獲取屏幕尺寸以計算佈局參數,使窗口居屏幕中央
screenwidth = top.winfo_screenwidth()
screenheight = top.winfo_screenheight()
alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth-width)/2, (screenheight-height)/2)
top.geometry(alignstr)
top.resizable(width=True, height=True) # 設置窗口是否可變長、寬,True:可變,False:不可變
######## Sqlite3 SQL 語句函數
##
## 數據庫操作:執行SQL查詢語句,返回執行狀態和執行結果(數據列表)
def DEF_SQL_查詢和返回(SQL_CMD):
數據庫連接對象 = sqlite3.connect('PWD.DB') # 嘗試打開數據庫文件
print("打開連接")
try:
遊標對象 = 數據庫連接對象.cursor() # 創建一個遊標
except Exception as e:
ERROR = '創建遊標失敗' + str(e)
return(1, ERROR)
else:
print("創建遊標")
try:
遊標對象.execute(SQL_CMD)
except Exception as e:
ERROR = f'執行SQL語句 {SQL_CMD} 失敗 {e}'
遊標對象.close()
print("關閉遊標")
return(1, ERROR)
else:
全部記錄 = 遊標對象.fetchall()
遊標對象.close()
print("關閉遊標")
return(0, 全部記錄)
## 執行一條SQL語句
def DEF_SQL_執行(SQL_CMD):
數據庫連接對象 = sqlite3.connect('PWD.DB') # 嘗試打開數據庫文件
print("打開連接")
try:
遊標對象 = 數據庫連接對象.cursor() # 創建一個遊標
except Exception as e:
ERROR = '創建遊標失敗' + str(e)
return(1, ERROR)
else:
print("創建遊標")
try:
遊標對象.execute(SQL_CMD)
except Exception as e:
遊標對象.close()
print("關閉遊標")
ERROR = str(e)
return(1, ERROR)
else:
數據庫連接對象.commit() # 提交更改
遊標對象.close()
print("關閉遊標")
數據庫連接對象.close()
print("關閉連接")
return(0,)
## 執行SQL腳本
def DEF_SQL_執行腳本(SQL_SCRIPT):
數據庫連接對象 = sqlite3.connect('PWD.DB') # 嘗試打開數據庫文件
print("打開連接")
try:
遊標對象 = 數據庫連接對象.cursor()
except Exception as e:
ERROR = str(e)
return(1, ERROR)
else:
print("創建遊標")
try:
遊標對象.executescript(SQL_SCRIPT) # 執行SQL腳本
except Exception as e:
遊標對象.close()
print("關閉遊標")
ERROR = str(e)
return(1, ERROR)
else:
數據庫連接對象.commit() # 提交更改
遊標對象.close()
print("關閉遊標")
數據庫連接對象.close()
print("關閉連接")
return(0,)
## 初始化數據庫:PWD表不存在則新建表
def 初始化數據庫():
## 如果數據表 PWD 不存在則新建
SQL_CMD = '''CREATE TABLE IF NOT EXISTS PWD(
ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
PS TEXT,
USER TEXT,
PASS TEXT);'''
R = DEF_SQL_執行(SQL_CMD)
if R[0] == 0:
print("初始化數據表 PWD 成功")
else:
ERROR = f'初始化數據表 PWD 失敗:{R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
## 打開/刷新數據表
def 打開數據庫():
## 打開數據庫,讀取數據庫密碼錶內容
SQL_CMD = 'SELECT * FROM PWD'
R = DEF_SQL_查詢和返回(SQL_CMD)
if R[0] == 0:
print("數據庫表 PWD 讀取成功")
數據列表 = R[1]
#print("數據列表", 數據列表)
## 顯示在界面
字段列表 = ['ID', 'PS', 'USER', 'PASS']
字段和數據的存儲和展示(字段列表, 數據列表)
else:
ERROR = f'數據庫表 PWD 讀取失敗:{R[1]}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
##
########
######## 新密鑰對操作框
##
def 生成密鑰對_明文私鑰():
當前設置密鑰長度 = IV_密鑰長度.get()
隨機數生成器 = Random.new().read
密鑰對 = RSA.generate(當前設置密鑰長度, 隨機數生成器)
時間 = time.strftime('%Y%m%d_%H%M%S')
## 保存公鑰
公鑰 = 密鑰對.publickey().exportKey() # 導出公鑰
新公鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PUBLIC_{時間}.pem'
with open(新公鑰文件名, 'wb') as f:
f.write(公鑰)
print("新公鑰文件名", 新公鑰文件名)
## 保存私鑰
私鑰 = 密鑰對.exportKey() # 導出明文私鑰
新私鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PRIVATE_{時間}.pem'
with open(新私鑰文件名, 'wb') as f:
f.write(私鑰)
print("新私鑰文件名(明文私鑰)", 新私鑰文件名)
INFO = '生成新密鑰對完成,長度爲:' + str(當前設置密鑰長度)
SV_新密鑰對提示信息.set(INFO)
## 自動替換爲新生成密鑰對
SV_選擇公鑰文件.set(新公鑰文件名)
Label_公鑰提示信息['fg'] = 'green'
SV_公鑰提示信息.set('自動替換爲新生成的公鑰文件')
SV_選擇私鑰文件.set(新私鑰文件名)
Label_私鑰提示信息['fg'] = 'green'
SV_私鑰提示信息.set('自動替換爲新生成的私鑰文件')
def 生成密鑰對_密文私鑰():
當前設置密鑰長度 = IV_密鑰長度.get()
私鑰密碼 = SV_新私鑰密碼.get().strip()
if 私鑰密碼 == '':
print("密文私鑰需要設置密碼")
Label_新密鑰對提示信息['fg'] = 'red'
SV_新密鑰對提示信息.set('密文私鑰需要設置密碼')
else:
隨機數生成器 = Random.new().read
密鑰對 = RSA.generate(當前設置密鑰長度, 隨機數生成器)
時間 = time.strftime('%Y%m%d_%H%M%S')
## 保存公鑰
公鑰 = 密鑰對.publickey().exportKey() # 導出公鑰
新公鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PUBLIC_{時間}.pem'
with open(新公鑰文件名, 'wb') as f:
f.write(公鑰)
print("新公鑰文件名", 新公鑰文件名)
## 保存私鑰
私鑰 = 密鑰對.exportKey(passphrase=私鑰密碼, pkcs=8, protection='scryptAndAES128-CBC') # 導出密文私鑰
新私鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PRIVATE_{時間}.pem'
with open(新私鑰文件名, 'wb') as f:
f.write(私鑰)
print("新私鑰文件名(密文私鑰)", 新私鑰文件名)
SV_新私鑰密碼.set('') # 清空私鑰密碼輸入框
INFO = '生成新密鑰對完成,長度爲:' + str(當前設置密鑰長度)
SV_新密鑰對提示信息.set(INFO)
## 自動替換爲新生成密鑰對
SV_選擇公鑰文件.set(新公鑰文件名)
Label_公鑰提示信息['fg'] = 'green'
SV_公鑰提示信息.set('自動替換爲新生成的公鑰文件')
SV_選擇私鑰文件.set(新私鑰文件名)
Label_私鑰提示信息['fg'] = 'green'
SV_私鑰提示信息.set('自動替換爲新生成的私鑰文件')
def 生成新密鑰對():
if 私鑰是否加密.get():
print("密文私鑰")
生成密鑰對_密文私鑰()
else:
print("文明私鑰")
生成密鑰對_明文私鑰()
新密鑰對操作框 = LabelFrame(top, text='新密鑰對操作框')
IV_密鑰長度 = IntVar()
IV_密鑰長度.set(1024) # 設置默認值 1024
SV_新私鑰密碼 = StringVar()
SV_新密鑰對提示信息 = StringVar()
SV_新密鑰對提示信息.set('生成密鑰需要等待數秒時間')
Label(新密鑰對操作框, text='設置新密鑰長度').grid(row=0, column=0, sticky='E') # 新密鑰對操作框 0-0
Combobox_密鑰長度 = ttk.Combobox(新密鑰對操作框, width=5)
Combobox_密鑰長度['value'] = (1024, 2048, 3072, 4096)
Combobox_密鑰長度.current(0) # 默認值中的內容爲索引,從0開始
Combobox_密鑰長度.grid(row=0, column=1, sticky='E') # 新密鑰對操作框 0-1
def 選擇後執行函數(event):
設置新密鑰長度 = Combobox_密鑰長度.get()
IV_密鑰長度.set(設置新密鑰長度)
print("設置新密鑰長度", 設置新密鑰長度)
Combobox_密鑰長度.bind('<<ComboboxSelected>>', 選擇後執行函數)
Label_新密鑰對提示信息 = Label(新密鑰對操作框, textvariable=SV_新密鑰對提示信息)
Label_新密鑰對提示信息['fg'] = '#FF6600'
Label_新密鑰對提示信息.grid(row=0, column=2) # 新密鑰對操作框 0-2
## 私鑰是否加密
def DEF_選擇按鈕():
print(私鑰是否加密.get())
if 私鑰是否加密.get():
print("密文私鑰")
else:
print("文明私鑰")
私鑰是否加密 = BooleanVar()
選擇按鈕 = Checkbutton(新密鑰對操作框, text='加密私鑰', variable=私鑰是否加密, command=DEF_選擇按鈕)
選擇按鈕.grid(row=1, column=0) # 新密鑰對操作框 1-0
Label(新密鑰對操作框, text='設置新私鑰密碼').grid(row=1, column=1, sticky='W') # 新密鑰對操作框 1-1
Entry(新密鑰對操作框, textvariable=SV_新私鑰密碼, width=20, show='*').grid(row=1, column=2) # 新密鑰對操作框 1-2
Button(新密鑰對操作框, text='生成新密鑰對', command=生成新密鑰對).grid(row=1, column=3, sticky='NW') # 新密鑰對操作框 1-3
新密鑰對操作框.grid(row=0, column=0, sticky='NW') ## top 0-0
##
########
######## 公鑰文件框
##
SV_選擇公鑰文件 = StringVar() ## TK全局變量:可以實時更新顯示
SV_公鑰提示信息 = StringVar() ## 顯示公鑰文件的情況
def DEF_按鈕_選擇公鑰文件():
選擇文件 = filedialog.askopenfilename()
SV_選擇公鑰文件.set(選擇文件)
print("SV_選擇公鑰文件", SV_選擇公鑰文件)
## 選擇文件後更新一下提示信息
Label_公鑰提示信息['fg'] = 'green'
SV_公鑰提示信息.set('使用手動選擇公鑰文件')
公鑰操作框 = LabelFrame(top, text='公鑰')
Button(公鑰操作框, text='選擇公鑰文件', command=DEF_按鈕_選擇公鑰文件).grid(row=0, column=0, sticky='NW')
Entry(公鑰操作框, textvariable=SV_選擇公鑰文件, width=55).grid(row=0, column=1)
Label_公鑰提示信息 = Label(公鑰操作框, textvariable=SV_公鑰提示信息)
Label_公鑰提示信息.grid(row=0, column=2)
默認公鑰文件 = '公鑰.pem'
if os.path.isfile(默認公鑰文件):
SV_選擇公鑰文件.set(默認公鑰文件) ## 設置公鑰默認存放位置爲程序同目錄下,方便使用
Label_公鑰提示信息['fg'] = 'green'
SV_公鑰提示信息.set('自動選擇默認公鑰文件')
else:
Label_公鑰提示信息['fg'] = '#FF6600'
SV_公鑰提示信息.set('加密需要先選擇公鑰')
公鑰操作框.grid(row=1, column=0, sticky='NW') ## top 1-0
##
########
######## 私鑰文件框
##
SV_選擇私鑰文件 = StringVar()
SV_私鑰提示信息 = StringVar()
def DEF_按鈕_選擇私鑰文件():
選擇文件 = filedialog.askopenfilename()
SV_選擇私鑰文件.set(選擇文件) # 實時更新顯示
print("SV_選擇私鑰文件", SV_選擇私鑰文件)
Label_私鑰提示信息['fg'] = 'green'
SV_私鑰提示信息.set('使用手動選擇私鑰文件')
私鑰操作框 = LabelFrame(top, text='私鑰')
Button(私鑰操作框, text='選擇私鑰文件', command=DEF_按鈕_選擇私鑰文件).grid(row=0, column=0, sticky='NW')
Entry(私鑰操作框, textvariable=SV_選擇私鑰文件, width=55).grid(row=0, column=1)
Label_私鑰提示信息 = Label(私鑰操作框, textvariable=SV_私鑰提示信息)
Label_私鑰提示信息.grid(row=0, column=2)
默認私鑰文件 = '私鑰.pem'
if os.path.isfile(默認私鑰文件):
SV_選擇私鑰文件.set(默認私鑰文件) ## 設置私鑰默認存放位置爲程序同目錄下,方便使用(不安全,私鑰加密後可以提高安全性)
Label_私鑰提示信息['fg'] = 'green'
SV_私鑰提示信息.set('自動選擇默認私鑰文件')
else:
Label_私鑰提示信息['fg'] = '#FF6600'
SV_私鑰提示信息.set('解密需要先選擇私鑰')
私鑰操作框.grid(row=2, column=0, sticky='NW') ## top 2-0
##
########
######## 私鑰密碼框
##
私鑰密碼框 = LabelFrame(top, text='私鑰密碼框')
Entry_私鑰密碼 = Entry(私鑰密碼框, width=15, show='*')
Entry_私鑰密碼.grid(row=0, column=0)
私鑰密碼框.grid(row=2, column=1, sticky='W') ## top 2-1
##
########
######## 存儲操作框
##
def 明文存入():
print("明文存入")
備註 = Entry_備註.get().strip()
帳號 = Entry_帳號.get().strip()
密碼 = Entry_密碼.get().strip()
if 備註 == '' or 帳號 == '' or 密碼 == '':
ERROR = '備註/帳號/密碼 中有空內容'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
#print(備註,帳號,密碼)
## 存入數據庫
SQL_插入數據 = f'INSERT INTO PWD (PS, USER, PASS) VALUES ("{備註}", "{帳號}", "{密碼}");'
R_INSERT = DEF_SQL_執行(SQL_插入數據)
if R_INSERT[0] == 0:
print("插入數據成功")
Entry_備註.delete(0, END) # 界面:清空輸入框
Entry_帳號.delete(0, END) # 界面:清空輸入框
Entry_密碼.delete(0, END) # 界面:清空輸入框
打開數據庫()
else:
#print("插入數據失敗", R_INSERT[1])
ERROR = f'插入數據失敗:{R_INSERT[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 密碼加密存入():
print("密碼加密存入")
備註 = Entry_備註.get().strip()
帳號 = Entry_帳號.get().strip()
密碼 = Entry_密碼.get().strip()
if 備註 == '' or 帳號 == '' or 密碼 == '':
ERROR = '備註/帳號/密碼 中有空內容'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
#print(備註,帳號,密碼)
## 明文密碼加密
公鑰文件 = SV_選擇公鑰文件.get()
if 公鑰文件.rstrip() == '':
ERROR = '沒有選擇公鑰文件'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
try:
f = open(公鑰文件,'r')
except Exception as E1:
ERROR = f'打開公鑰文件失敗:{E1}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("打開公鑰文件成功")
STR_公鑰 = f.read()
f.close()
try:
OJB_公鑰 = RSA.importKey(STR_公鑰)
except Exception as E2:
ERROR = f'導入公鑰失敗:{E2}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
公鑰_PKCS115 = PKCS1_v1_5.new(OJB_公鑰)
明文字節碼 = 密碼.encode('utf8')
try:
密文字節碼 = 公鑰_PKCS115.encrypt(明文字節碼) # 公鑰加密
except Exception as E3:
ERROR = f'公鑰加密失敗:{E3}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("公鑰加密成功")
#print(密文字節碼)
## 字節類型轉成16進制字符串存儲到數據庫
STR_HEX = Bytes2HEX_STR(密文字節碼)
#print(STR_HEX)
## 存入數據庫
SQL_插入數據 = f'INSERT INTO PWD (PS, USER, PASS) VALUES ("{備註}", "{帳號}", "{STR_HEX}");'
R_INSERT = DEF_SQL_執行(SQL_插入數據)
if R_INSERT[0] == 0:
print("插入數據成功")
Entry_備註.delete(0, END) # 界面:清空輸入框
Entry_帳號.delete(0, END) # 界面:清空輸入框
Entry_密碼.delete(0, END) # 界面:清空輸入框
打開數據庫() # 更新數據庫顯示框
else:
ERROR = f'插入數據失敗:{R_INSERT[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 帳號密碼加密存入():
print("帳號和密碼都加密存儲")
賬號加密狀態 = 0
密碼加密狀態 = 0
備註 = Entry_備註.get().strip()
帳號 = Entry_帳號.get().strip()
密碼 = Entry_密碼.get().strip()
if 備註 == '' or 帳號 == '' or 密碼 == '':
ERROR = '備註/帳號/密碼 中有空內容'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
#print(備註,帳號,密碼)
## 明文密碼加密
公鑰文件 = SV_選擇公鑰文件.get()
if 公鑰文件.rstrip() == '':
ERROR = '沒有選擇公鑰文件'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
try:
f = open(公鑰文件,'r')
except Exception as E1:
ERROR = f'打開公鑰文件失敗:{E1}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("打開公鑰文件成功")
STR_公鑰 = f.read()
f.close()
try:
OJB_公鑰 = RSA.importKey(STR_公鑰)
except Exception as E2:
ERROR = f'導入公鑰失敗:{E2}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
公鑰_PKCS115 = PKCS1_v1_5.new(OJB_公鑰)
## 公鑰加密帳號
帳號字節碼 = 帳號.encode('UTF8')
try:
密文帳號字節碼 = 公鑰_PKCS115.encrypt(帳號字節碼) # 公鑰加密
except Exception as E3:
ERROR = f'公鑰加密賬號失敗:{E3}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("公鑰加密賬號成功")
#print(密文帳號字節碼)
## 字節類型轉成16進制字符串存儲到數據庫
STR_HEX_帳號 = Bytes2HEX_STR(密文帳號字節碼)
#print(STR_HEX_帳號)
賬號加密狀態 = 1
## 公鑰加密密碼
密碼字節碼 = 密碼.encode('utf8')
try:
密文密碼字節碼 = 公鑰_PKCS115.encrypt(密碼字節碼) # 公鑰加密
except Exception as E4:
ERROR = f'公鑰加密密碼失敗:{E4}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("公鑰加密密碼成功")
#print(密文密碼字節碼)
## 字節類型轉成16進制字符串存儲到數據庫
STR_HEX_密碼 = Bytes2HEX_STR(密文密碼字節碼)
#print(STR_HEX_密碼)
密碼加密狀態 = 1
## 存入數據庫
if 賬號加密狀態 == 密碼加密狀態 == 1:
SQL_插入數據 = f'INSERT INTO PWD (PS, USER, PASS) VALUES ("{備註}", "{STR_HEX_帳號}", "{STR_HEX_密碼}");'
R_INSERT = DEF_SQL_執行(SQL_插入數據)
if R_INSERT[0] == 0:
print("插入數據成功")
Entry_備註.delete(0, END) # 界面:清空輸入框
Entry_帳號.delete(0, END) # 界面:清空輸入框
Entry_密碼.delete(0, END) # 界面:清空輸入框
打開數據庫() # 更新數據庫顯示框
else:
ERROR = f'插入數據失敗:{R_INSERT[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 搜索備註信息():
print("搜索備註信息")
備註 = Entry_備註.get().strip()
if 備註 == '':
ERROR = '備註空內容'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("搜索備註", 備註)
SQL_CMD = f'SELECT * FROM PWD WHERE PS LIKE "%{備註}%"'
print("SQL_CMD", SQL_CMD)
R = DEF_SQL_查詢和返回(SQL_CMD)
if R[0] == 0:
print("執行搜索語句成功")
數據列表 = R[1]
#print("數據列表", 數據列表)
字段列表 = ['ID', 'PS', 'USER', 'PASS']
字段和數據的存儲和展示(字段列表, 數據列表)
else:
ERROR = f'執行搜索語句失敗:{R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
存儲操作框 = LabelFrame(top, text='存儲操作框')
存儲操作框.grid(row=3, column=0, sticky='NW', columnspan=2) ## top 3-0
備註框 = Frame(存儲操作框)
備註框.grid(row=1, column=0, sticky='NW') ## 存儲操作框 1-0
Label(備註框, text='備註').grid(row=0, column=0) ## 存儲操作框 備註框 0-0
Entry_備註 = Entry(備註框, width=20)
Entry_備註.grid(row=1, column=0) ## 存儲操作框 備註框 1-0
Button(備註框, text='搜索備註信息', command=搜索備註信息).grid(row=2, column=0) ## 存儲操作框 備註框 2-0
帳號框 = Frame(存儲操作框)
帳號框.grid(row=1, column=1, sticky='NW') ## 存儲操作框 1-1
Label(帳號框, text='帳號').grid(row=0, column=0) ## 存儲操作框 帳號框 0-0
Entry_帳號 = Entry(帳號框, width=20)
Entry_帳號.grid(row=1, column=0) ## 存儲操作框 帳號框 1-0
密碼框 = Frame(存儲操作框)
密碼框.grid(row=1, column=2, sticky='NW') ## 存儲操作框 1-2
Label(密碼框, text='密碼').grid(row=0, column=0) ## 存儲操作框 密碼框 0-0
Entry_密碼 = Entry(密碼框, width=30)
Entry_密碼.grid(row=1, column=0) ## 存儲操作框 密碼框 1-0
數據庫寫入框 = Frame(存儲操作框)
數據庫寫入框.grid(row=1, column=3, sticky='ESWN') ## 存儲操作框 1-3
Label(數據庫寫入框, text='密碼存入數據庫方式').grid(row=0, column=0, columnspan=3) ## 存儲操作框 數據庫寫入框 0-0
Button(數據庫寫入框, text=' 明文存入 ', command=明文存入).grid(row=1, column=0, sticky='NW') ## 存儲操作框 數據庫寫入框 1-0
Button(數據庫寫入框, text=' 密碼加密存入 ', command=密碼加密存入).grid(row=1, column=1, sticky='NW') ## 存儲操作框 數據庫寫入框 1-1
Button(數據庫寫入框, text=' 帳號和密碼都加密 ', command=帳號密碼加密存入).grid(row=1, column=2, sticky='NW') ## 存儲操作框 數據庫寫入框 1-2
SV_剪貼板提示信息 = StringVar()
Label_剪貼板提示信息 = Label(存儲操作框, textvariable=SV_剪貼板提示信息)
Label_剪貼板提示信息['fg'] = '#FF6600'
Label_剪貼板提示信息.grid(row=2, column=0, columnspan=3, sticky='ESWN') ## 存儲操作框 2-0
##
########
######## 密碼操作框
##
def 生成隨機密碼():
密碼長度 = IV_密碼位數.get()
數字字符列表 = ['0','1','2','3','4','5','6','7','8','9']
大寫字母列表 = ['A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z']
小寫字母列表 = ['a','b','c','d','e','f','g','h','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z']
符號字符列表 = ['`','~','!','@','#','$','%','^','&','*','(',')','-','_','=','+','[','{',']','}','|',';',':','<','.','>','/','?']
組合列表 = 數字字符列表 + 大寫字母列表 + 小寫字母列表 + 符號字符列表
if 密碼長度 > 84:
ERROR = '過長,請設置長度 <= 84'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
random.shuffle(組合列表)
隨機密碼 = ''
for i in range(0, 密碼長度):
隨機密碼 += 組合列表[i]
#print(隨機密碼)
Entry_密碼.delete(0, END) # 界面:清空輸入框
Entry_密碼.insert(END, 隨機密碼)
## 字節碼轉16進制格式的字符串
def Bytes2HEX_STR(Bytes):
STR_HEX = ''
for i in Bytes:
STR_HEX += hex(i)[2:].zfill(2) ## '0x50' 取 '50' 部分,如果不足2個字符,填充0
return(STR_HEX)
IV_密碼位數 = IntVar()
IV_密碼位數.set(20) ## 設置默認密碼位數
密碼操作框 = LabelFrame(存儲操作框, text='')
Label(密碼操作框, text='設置密碼長度').grid(row=0, column=0, sticky='NW')
Entry(密碼操作框, textvariable=IV_密碼位數, width=5).grid(row=0, column=1)
Button(密碼操作框, text='生成隨機密碼', command=生成隨機密碼).grid(row=0, column=2, sticky='NW')
Button(密碼操作框, text='刷新數據庫', bg='#00FFFF', command=打開數據庫).grid(row=0, column=3, sticky='NW')
密碼操作框.grid(row=0, column=0, sticky='NW', columnspan=8)
##
########
######## 數據庫顯示/操作框
##
字典_查詢字段_座標_初值 = {} # KEY=控件座標 || VAULE=初始值 || {(控件行號,控件列號):初始值} || { (0,0):123 }
字典_查詢結果_座標_初值 = {} # KEY=控件座標 || VAULE=初始值 || {(控件行號,控件列號):初始值} || { (0,0):123 }
數據框_定位行 = IntVar()
數據框_定位列 = IntVar()
## 信息複製到剪貼板
def DEF_解密後複製到剪貼板():
控件行號 = 數據框_定位行.get()
控件列號 = 數據框_定位列.get()
print("控件行號", 控件行號, "控件列號", 控件列號)
選中單元格內容 = 字典_查詢結果_座標_初值[(控件行號,控件列號)]
print("選中單元格內容", 選中單元格內容)
## 讀取選中單元格字符串嘗試還原字節類型
try:
還原字節碼 = bytes.fromhex(選中單元格內容)
except Exception as E1:
#print("還原字節碼失敗,格式有誤(非16進制字符串形式", E1)
ERROR = f'還原字節碼失敗,格式有誤(非16進制字符串形式:{E1}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
#print("還原字節碼", type(還原字節碼))
#print(還原字節碼)
## 嘗試加載私鑰
私鑰文件 = SV_選擇私鑰文件.get()
if 私鑰文件.rstrip() == '':
ERROR = '沒有選擇私鑰文件'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
try:
f = open(私鑰文件,'r')
except Exception as E2:
ERROR = f'打開私鑰文件失敗:{E2}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("打開私鑰文件成功")
STR_私鑰 = f.read()
f.close()
## 根據是否私鑰是否被加密分別處理
私鑰密碼 = Entry_私鑰密碼.get()
if 私鑰密碼 != '':
print("私鑰被加密,先解密私鑰,再導入使用")
try:
OJB_私鑰 = RSA.importKey(STR_私鑰, passphrase=私鑰密碼)
except Exception as E3:
ERROR = f'導入私鑰失敗,私鑰【密碼錯誤】或【格式錯誤】:{E3}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("導入私鑰成功")
私鑰_PKCS115 = PKCS1_v1_5.new(OJB_私鑰)
## 私鑰嘗試解密
隨機數生成器 = Random.new().read
try:
明文字節碼 = 私鑰_PKCS115.decrypt(還原字節碼, 隨機數生成器)
except Exception as E4:
ERROR = f'私鑰解密失敗:{E4}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("私鑰解密成功")
明文字符串 = 明文字節碼.decode('utf8')
top.clipboard_clear()
top.clipboard_append(明文字符串)
## 提示哪個數據被複制到剪貼板
選中行ID = 字典_查詢結果_座標_初值[(控件行號,0)]
選中行列名 = 字典_查詢字段_座標_初值[(0,控件列號)]
INFO = f'ID【{選中行ID}】的【{選中行列名}】【解密後內容】 已經複製到剪貼板'
#tkinter.messagebox.showinfo(title='INFO', message=INFO)
SV_剪貼板提示信息.set(INFO)
else:
print("私鑰無加密,直接導入使用")
try:
OJB_私鑰 = RSA.importKey(STR_私鑰)
except Exception as E3:
ERROR = f'導入私鑰失敗,私鑰【被加密】或【格式錯誤】:{E3}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("導入私鑰成功")
私鑰_PKCS115 = PKCS1_v1_5.new(OJB_私鑰)
## 私鑰嘗試解密
隨機數生成器 = Random.new().read
try:
明文字節碼 = 私鑰_PKCS115.decrypt(還原字節碼, 隨機數生成器)
except Exception as E4:
ERROR = f'私鑰解密失敗:{E4}'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("私鑰解密成功")
明文字符串 = 明文字節碼.decode('utf8')
top.clipboard_clear()
top.clipboard_append(明文字符串)
## 提示哪個數據被複制到剪貼板
選中行ID = 字典_查詢結果_座標_初值[(控件行號,0)]
選中行列名 = 字典_查詢字段_座標_初值[(0,控件列號)]
INFO = f'ID【{選中行ID}】的【{選中行列名}】【解密後內容】 已經複製到剪貼板'
#tkinter.messagebox.showinfo(title='INFO', message=INFO)
SV_剪貼板提示信息.set(INFO)
def DEF_保留原值複製到剪貼板():
控件行號 = 數據框_定位行.get()
控件列號 = 數據框_定位列.get()
print("控件行號", 控件行號, "控件列號", 控件列號)
選中單元格內容 = 字典_查詢結果_座標_初值[(控件行號,控件列號)]
print("選中單元格內容", 選中單元格內容)
top.clipboard_clear()
top.clipboard_append(選中單元格內容)
## 提示哪個數據被複制到剪貼板
選中行ID = 字典_查詢結果_座標_初值[(控件行號,0)]
選中行列名 = 字典_查詢字段_座標_初值[(0,控件列號)]
INFO = f'ID【{選中行ID}】的【{選中行列名}】【原值】 已經複製到剪貼板'
#tkinter.messagebox.showinfo(title='INFO', message=INFO)
SV_剪貼板提示信息.set(INFO)
## 菜單按鈕函數:刪除整行(提取控件行號信息)
def DEL_ROW():
控件行號 = 數據框_定位行.get()
DEF_刪除記錄(控件行號)
## 刪除成功後的行列號和當前行列號有差別,立刻設置爲無效行列號,防止後面誤刪
數據框_定位行.set(-1)
數據框_定位列.set(-1)
## 菜單按鈕函數:刪除整行(根據控件行號刪除)
def DEF_刪除記錄(控件行號):
ID = 字典_查詢結果_座標_初值[(控件行號, 0)]
print("DELETE ID", ID)
SQL_CMD = f'DELETE FROM PWD WHERE ID = "{ID}"'
print("SQL_CMD", SQL_CMD)
## 刪除記錄
R = DEF_SQL_執行(SQL_CMD)
if R[0] == 0:
打開數據庫()
else:
ERROR = f'刪除記錄失敗:{R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
## 事件函數:右鍵菜單
def DEF_彈出_數據框_右鍵菜單(event):
# 取值控制座標
選中控件 = event.widget
行 = 選中控件.grid_info()['row']
列 = 選中控件.grid_info()['column']
# 賦值控制座標變量
數據框_定位行.set(行)
數據框_定位列.set(列)
## 彈出菜單
光標X軸 = event.x_root
光標Y軸 = event.y_root
數據框_右鍵菜單.post(光標X軸, 光標Y軸) # 光標位置顯示菜單
## 創建數據框右鍵菜單
數據框_右鍵菜單 = Menu()
數據框_右鍵菜單.add_command(label='解密 到 剪貼板', command=DEF_解密後複製到剪貼板)
數據框_右鍵菜單.add_separator() # 分隔線
數據框_右鍵菜單.add_separator()
數據框_右鍵菜單.add_command(label='原值 到 剪貼板', command=DEF_保留原值複製到剪貼板)
數據框_右鍵菜單.add_separator()
數據框_右鍵菜單.add_separator()
數據框_右鍵菜單.add_command(label='刪除此行', command=DEL_ROW)
## 清空框內控件
def FRAME_CLEAR(FRAME_NAME):
for X in FRAME_NAME.winfo_children():
X.destroy()
## 在顯編框展示結果,保存結果到全局變量
def 字段和數據的存儲和展示(L, LL):
ID寬 = 3
PS寬 = 55
USER寬 = 30
PASS寬 = 20
行數 = len(LL)
FRAME_CLEAR(LabelFrame_顯編框) # 清空框內控件
## 創建畫布
畫布 = Canvas(LabelFrame_顯編框, bg='#00CED1') # 創建畫布
畫布.grid(row=0,column=0) # 顯示畫布
## 在畫布裏創建 Frame
畫布Frame框 = Frame(畫布)
字段框 = Frame(畫布Frame框)
字段框.grid(row=0,column=0,sticky='NW')
數據框 = Frame(畫布Frame框)
數據框.grid(row=1,column=0,sticky='NW')
## 動態設置畫布窗口寬高:根據主主窗口的參數設置限寬限高
主窗口大小和位置 = top.geometry()
print("主窗口大小和位置", 主窗口大小和位置)
主窗口寬, 主窗口高, 主窗口X, 主窗口Y = re.findall('[0-9]+', 主窗口大小和位置)
print("主窗口寬", 主窗口寬)
print("主窗口高", 主窗口高)
if int(主窗口寬) < 500:
主窗口寬 = width
else:
主窗口寬 = int(主窗口寬)
畫布限寬 = 主窗口寬
print("畫布限寬(想和主框同寬)", 畫布限寬)
if int(主窗口高) < 700:
主窗口高 = height
else:
主窗口高 = int(主窗口高)
畫布限高 = int(主窗口高) -500 # 減去顯示框上邊的內容
print("畫布限高(預計)", 畫布限高)
if 畫布限高 < 200:
畫布限高 = 200 # 保障最小高度
print("畫布限高需要保障最小高度", 畫布限高)
print("畫布限寬(實際)", 畫布限寬)
print("畫布限高(實際)", 畫布限高)
## 設置畫布參數
## 畫布可滾動顯示的最大寬和高(要剛好能放下畫布裏的Frame裏的全部控件)
畫布滾動最右邊 = (ID寬+PS寬+USER寬+PASS寬)*7 + 19 ## Enter默認寬20==140像素(7像素/每寬)
畫布滾動最下邊 = (行數+1)*21 + 3 ## (數據行數+字段行數)*(每行20高+間隔1像素) + (留空)
print("畫布包含元素需寬", 畫布滾動最右邊)
print("畫布包含元素需高", 畫布滾動最下邊)
## 動態設置顯示畫布固定顯示寬和高(要和主顯示框的大小匹配)
if 畫布限寬 > 畫布滾動最右邊:
畫布['width'] = 畫布滾動最右邊
print("畫布限寬>畫布包含元素最大寬度,畫布寬採用實際需要的寬度", 畫布滾動最右邊)
else:
畫布['width'] = 畫布限寬 - 30
print("畫布限寬<=畫布包含元素最大寬度,畫布寬度採用(畫布限寬-30)留出右邊滾動條的顯示位置", 畫布限寬-30)
if 畫布限高 > 畫布滾動最下邊:
畫布['height'] = 畫布滾動最下邊
print("畫布限高>畫布包含元素最大高度,畫布高採用實際需要的高度", 畫布滾動最下邊)
else:
畫布['height'] = 畫布限高
print("畫布限高<=畫布包含元素最大高度,畫布高度採用(畫布限高)", 畫布限高)
畫布['scrollregion'] = (0,0,畫布滾動最右邊,畫布滾動最下邊) # 一個元組 tuple (w, n, e, s) ,定義了畫布可滾動的最大區域,w 爲左邊,n 爲頭部,e 爲右邊,s 爲底部
# 豎滾動條
Scrollbar_畫布_豎 = Scrollbar(LabelFrame_顯編框, command=畫布.yview)
Scrollbar_畫布_豎.grid(row=0,column=1,sticky=S+W+E+N)
# 橫滾動條
Scrollbar_畫布_橫 = Scrollbar(LabelFrame_顯編框, command=畫布.xview, orient=HORIZONTAL)
Scrollbar_畫布_橫.grid(row=1,column=0,sticky=S+W+E+N)
畫布.config(xscrollcommand=Scrollbar_畫布_橫.set, yscrollcommand=Scrollbar_畫布_豎.set) # 自動設置滾動幅度
畫布.create_window((0,0), window=畫布Frame框, anchor='nw')
## 在 畫布裏的Frame裏創建控件
# 清除全局字典的內容
字典_查詢字段_座標_初值.clear()
字典_查詢結果_座標_初值.clear()
## 字段名
## 固定行:0
## 固定列:ID PS USER PASS
## ID
初始值 = str(L[0]) # 轉成字符串
字典_查詢字段_座標_初值[(0,0)] = 初始值 # 保存初始值
輸入框對象 = Entry(字段框, width=ID寬, bg='#00BFFF')
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=0,column=0,sticky='W')
## PS
初始值 = str(L[1])
字典_查詢字段_座標_初值[(0,1)] = 初始值
輸入框對象 = Entry(字段框, width=PS寬, bg='#00BFFF')
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=0,column=1,sticky='W')
## USER
初始值 = str(L[2])
字典_查詢字段_座標_初值[(0,2)] = 初始值
輸入框對象 = Entry(字段框, width=USER寬, bg='#00BFFF')
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=0,column=2,sticky='W')
## PASS
初始值 = str(L[3])
字典_查詢字段_座標_初值[(0,3)] = 初始值
輸入框對象 = Entry(字段框, width=PASS寬, bg='#00BFFF')
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=0,column=3,sticky='W')
## 數據值
for 行 in range(0, 行數):
## 固定列:ID PS USER PASS
## ID
初始值 = str(LL[行][0]) # 轉成字符串
字典_查詢結果_座標_初值[(行,0)] = 初始值 # 保存初始值
輸入框對象 = Entry(數據框, width=ID寬)
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=行,column=0,sticky='W')
輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
## PS
初始值 = str(LL[行][1]) # 轉成字符串
字典_查詢結果_座標_初值[(行,1)] = 初始值 # 保存初始值
輸入框對象 = Entry(數據框, width=PS寬)
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=行,column=1,sticky='W')
輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
## USER
初始值 = str(LL[行][2]) # 轉成字符串
字典_查詢結果_座標_初值[(行,2)] = 初始值 # 保存初始值
輸入框對象 = Entry(數據框, width=USER寬)
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=行,column=2,sticky='W')
輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
## PASS
初始值 = str(LL[行][3]) # 轉成字符串
字典_查詢結果_座標_初值[(行,3)] = 初始值 # 保存初始值
輸入框對象 = Entry(數據框, width=PASS寬)
輸入框對象.insert(0, 初始值)
輸入框對象.grid(row=行,column=3,sticky='W')
輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
LabelFrame_顯編框 = LabelFrame(top, text='顯示/編輯框', bg='#FFD700')
LabelFrame_顯編框.grid(row=4,column=0,sticky='NW',columnspan=2) ## top 4-0
##
########
######## SQL命令框
##
## 執行用戶輸入的SQL語句
def DEF_按鈕_執行SQL語句():
SQL_CMD = 文本框_命令行.get(1.0, END).rstrip('\n') # 獲取編寫的SQL語句,去掉後面的回車符號
if SQL_CMD.strip() != '':
R = DEF_SQL_執行(SQL_CMD) # 調用非查詢語句函數
if R[0] == 0:
INFO = f'數據庫執行SQL語句 {SQL_CMD} 成功'
打開數據庫() ## 更新一下數據庫顯示
tkinter.messagebox.showinfo(title='成功', message=INFO)
else:
ERROR = f'數據庫執行SQL語句 {SQL_CMD} 失敗 {R[1]}'
tkinter.messagebox.showerror(title='失敗', message=ERROR)
else:
ERROR = '沒有輸入SQL語句'
tkinter.messagebox.showerror(title='錯誤', message=ERROR)
## 執行用戶輸入的SQL腳本
def DEF_按鈕_執行SQL腳本():
SQL_CMD = 文本框_命令行.get(1.0, END).rstrip('\n') # 獲取編寫的SQL語句,去掉後面的回車符號
if SQL_CMD.strip() != '':
R = DEF_SQL_執行腳本(SQL_CMD) # 調用非查詢語句函數
if R[0] == 0:
INFO = f'數據庫執行SQL腳本 {SQL_CMD} 成功'
打開數據庫() ## 更新一下數據庫顯示
tkinter.messagebox.showinfo(title='成功', message=INFO)
else:
ERROR = f'數據庫執行SQL腳本 {SQL_CMD} 失敗 {R[1]}'
tkinter.messagebox.showerror(title='失敗', message=ERROR)
else:
ERROR = 'SQL腳本無內容'
tkinter.messagebox.showerror(title='錯誤', message=ERROR)
## 清除命令框裏的內容
def DEF_按鈕_清屏():
文本框_命令行.delete(0.0, END)
文本框_命令行.focus_set()
命令框 = LabelFrame(top, text='SQL語句/SQL腳本')
命令框.grid(row=5,column=0,sticky='NW',columnspan=2) ## top 5-0
命令行_按鈕框 = Text(命令框)
命令行_按鈕框.grid(row=1,column=0)
Button(命令行_按鈕框, text='執行SQL語句', command=DEF_按鈕_執行SQL語句).grid(row=0, column=0)
Button(命令行_按鈕框, text='執行SQL腳本', command=DEF_按鈕_執行SQL腳本).grid(row=0, column=1)
Button(命令行_按鈕框, text='清屏', command=DEF_按鈕_清屏).grid(row=0, column=2)
文本框_命令行 = Text(命令框, height=3, wrap='none')
文本框_命令行.grid(row=2,column=0,sticky='NESW')
Scrollbar_命令框_豎 = Scrollbar(命令框)
Scrollbar_命令框_豎['command'] = 文本框_命令行.yview
Scrollbar_命令框_橫 = Scrollbar(命令框)
Scrollbar_命令框_橫['command'] = 文本框_命令行.xview
Scrollbar_命令框_橫['orient'] = HORIZONTAL
Scrollbar_命令框_豎.grid(row=2, column=1, sticky=S+W+E+N)
Scrollbar_命令框_橫.grid(row=3, column=0, sticky=S+W+E+N)
文本框_命令行.config(xscrollcommand=Scrollbar_命令框_橫.set, yscrollcommand=Scrollbar_命令框_豎.set) # 自動設置滾動條滑動幅度
## 開軟件後先執行數據庫的初始化和打開
初始化數據庫()
打開數據庫()
# 進入消息循環
top.mainloop()