使用Python3自帶GUI、RSA、SQLite3做的密碼管理軟件

 

 


#_*_ coding:utf8 _*_

## Python3 密碼管理 V1.0
## 2020-05-28
## 使用密鑰對加密解密密碼,密文存入本地SQLite3數據庫,使用時直接右鍵解密到粘貼板中

import os               # 操作Windows文件用
import time
import random
#import hashlib
import rsa

## 數據庫
import sqlite3

## 圖形化模塊
from tkinter import *
from tkinter import filedialog  # 選擇文件用
import tkinter.messagebox       # 彈出提示對話框
from tkinter import ttk         # 下拉菜單控件在ttk中

top = Tk()                          # 初始化Tk()
top.title('Python3 密碼管理 V1.0')  # 設置標題
width = 800                     # 設置窗口寬
height = 700                    # 設置窗口高
# 獲取屏幕尺寸以計算佈局參數,使窗口居屏幕中央
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 打開數據庫():
    ## 打開數據庫
    ## 讀取數據庫內容
    SQL_CMD = 'SELECT * FROM PWD'
    R = DEF_SQL_查詢和返回(SQL_CMD)
    if R[0] == 0:
        print("數據庫表PWD讀取成功")
        數據列表 = R[1]
        #print("數據列表", 數據列表)
        字段列表 = ['ID', 'PS', 'USER', 'PASS']
        字段和數據的存儲和展示(字段列表, 數據列表)
    else:
        ## 創建表
        print("數據庫表PWD讀取失敗,嘗試創建據庫表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'創建表失敗:{R[1]}'
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
##
########


######## 新密鑰對操作框
##


def 生成新密鑰對():
    當前設置密鑰長度 = IV_密鑰長度.get()
    ## 生成新密鑰對
    (公鑰, 私鑰) = rsa.newkeys(當前設置密鑰長度)

    ## 保存新密鑰對
    FILE_DATE = time.strftime('%Y%m%d_%H%M%S')
    新公鑰文件名 = f'公鑰_{當前設置密鑰長度}_{FILE_DATE}.pem'
    with open(新公鑰文件名, 'w') as f:
        f.write(公鑰.save_pkcs1().decode())
    新私鑰文件名 = f'私鑰_{當前設置密鑰長度}_{FILE_DATE}.pem'
    with open(新私鑰文件名, 'w') as f:
        f.write(私鑰.save_pkcs1().decode())
    INFO = '生成新密鑰對完成,長度爲:' + str(當前設置密鑰長度)
    SV_新密鑰對提示信息.set(INFO)
    ## 自動替換爲新生成密鑰對
    SV_選擇公鑰文件.set(新公鑰文件名)
    Label_公鑰提示信息['fg'] = 'green'
    SV_公鑰提示信息.set('自動替換爲新生成的公鑰文件')
    SV_選擇私鑰文件.set(新私鑰文件名)
    Label_私鑰提示信息['fg'] = 'green'
    SV_私鑰提示信息.set('自動替換爲新生成的私鑰文件')


新密鑰對操作框 = LabelFrame(top, text='新密鑰對操作框')
IV_密鑰長度 = IntVar()
IV_密鑰長度.set(1024)               # 設置默認值 1024
SV_新密鑰對提示信息 = StringVar()
SV_新密鑰對提示信息.set('生成密鑰需要等待數秒時間')

Label(新密鑰對操作框, text='設置新密鑰長度').grid(row=0, column=0, sticky='NW')
#Entry(新密鑰對操作框, textvariable=IV_密鑰長度, width=5).grid(row=0, column=1)
Combobox_密鑰長度 = ttk.Combobox(新密鑰對操作框, width=5)
Combobox_密鑰長度['value'] = (1024, 2048, 3072, 4096)
Combobox_密鑰長度.current(0)                            # 默認值中的內容爲索引,從0開始
Combobox_密鑰長度.grid(row=0, column=1, sticky='E')
def 選擇後執行函數(event):
    設置新密鑰長度 = Combobox_密鑰長度.get()
    IV_密鑰長度.set(設置新密鑰長度)
    print("設置新密鑰長度", 設置新密鑰長度)
Combobox_密鑰長度.bind('<<ComboboxSelected>>', 選擇後執行函數)
Button(新密鑰對操作框, text='生成新密鑰對', command=生成新密鑰對).grid(row=0, column=2, sticky='NW')
Label_新密鑰對提示信息 = Label(新密鑰對操作框, textvariable=SV_新密鑰對提示信息)
Label_新密鑰對提示信息['fg'] = '#FF6600'
Label_新密鑰對提示信息.grid(row=0, column=3)


新密鑰對操作框.grid(row=0, column=0, sticky='NW')
##
########


######## 公鑰文件框
##
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')
##
########


######## 私鑰文件框
##
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')
##
########



######## 存儲操作框
##
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 E:
                print("打開公鑰文件失敗", E)
                ERROR = f'打開公鑰文件失敗:{E}'
                tkinter.messagebox.showerror(title='ERROR', message=ERROR)
            else:
                print("打開公鑰文件成功")
                try:
                    pubkey = rsa.PublicKey.load_pkcs1(f.read().encode())
                except Exception as E:
                    print("讀取公鑰失敗", E)
                    f.close()
                    ERROR = f'讀取公鑰失敗:{E}'
                    tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                else:
                    print("讀取公鑰成功")
                    f.close()
                    密碼_B = str(密碼).encode('UTF8')                                       # 轉成UTF8編碼的字節
                    #print("文本密碼轉字節密碼", 密碼_B, type(密碼_B))
                    ## 公鑰加密
                    公鑰加密後密文 = rsa.encrypt(密碼_B, pubkey)
                    #print("公鑰加密後密文", 公鑰加密後密文)
                    
                    ## 字節類型轉成16進制字符串存儲到數據庫
                    STR_HEX = Bytes2HEX_STR(公鑰加密後密文)
                    #print("公鑰加密後密文 轉 STR_HEX", type(STR_HEX))
                    #print(STR_HEX)

                    pubkey = ''         # 每次用完清空
                    
                    ## 存入數據庫
                    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("帳號和密碼都加密存儲")
    備註 = 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 E:
                print("打開公鑰文件失敗", E)
                ERROR = f'打開公鑰文件失敗:{E}'
                tkinter.messagebox.showerror(title='ERROR', message=ERROR)
            else:
                print("打開公鑰文件成功")
                try:
                    pubkey = rsa.PublicKey.load_pkcs1(f.read().encode())
                except Exception as E:
                    print("讀取公鑰失敗", E)
                    f.close()
                    ERROR = f'讀取公鑰失敗:{E}'
                    tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                else:
                    print("讀取公鑰成功")
                    f.close()
                    
                    ## 公鑰加密密碼
                    密碼_B = str(密碼).encode('UTF8')                       # 轉成UTF8編碼的字節
                    密碼公鑰加密後密文 = rsa.encrypt(密碼_B, pubkey)
                    STR_HEX_密碼 = Bytes2HEX_STR(密碼公鑰加密後密文)        ## 字節類型轉成16進制字符串
                    
                    ## 公鑰加密帳號
                    帳號_B = str(帳號).encode('UTF8')
                    帳號公鑰加密後密文 = rsa.encrypt(帳號_B, pubkey)
                    STR_HEX_帳號 = Bytes2HEX_STR(帳號公鑰加密後密文)

                    pubkey = ''         # 每次用完清空
                    
                    ## 存入數據庫
                    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')        ## top 3-1

備註框 = 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("選中單元格內容", 選中單元格內容)
    ## 解密密文
    私鑰文件 = SV_選擇私鑰文件.get()
    if 私鑰文件.rstrip() == '':
        ERROR = '沒有選擇私鑰文件'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        try:
            f = open(私鑰文件,'r')
        except Exception as E:
            ERROR = f'打開私鑰文件失敗:{E}'
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
        else:
            print("打開私鑰文件成功")
            try:
                privkey = rsa.PrivateKey.load_pkcs1(f.read().encode())
            except Exception as E:
                print("讀取私鑰失敗", E)
                f.close()
                ERROR = f'讀取私鑰失敗:{E}'
                tkinter.messagebox.showerror(title='ERROR', message=ERROR)
            else:
                print("讀取私鑰成功")
                f.close()
                ## 讀取字符串並還原字節類型
                try:
                    還原字節碼 = bytes.fromhex(選中單元格內容)
                except Exception as E:
                    #print("還原字節碼失敗,格式有誤(非16進制字符串形式", E)
                    ERROR = f'還原字節碼失敗,格式有誤(非16進制字符串形式:{E}'
                    tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                else:
                    #print("還原字節碼", type(還原字節碼))
                    #print(還原字節碼)
                    ## 私鑰解密
                    try:
                        B_私鑰解密後明文 = rsa.decrypt(還原字節碼, privkey)
                    except Exception as E:
                        #print("私鑰解密失敗", E)
                        ERROR = f'私鑰解密失敗:{E}'
                        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                    else:
                        print("私鑰解密成功")
                        #print("B_私鑰解密後明文(字節碼)", type(B_私鑰解密後明文))
                        S_私鑰解密後明文 = B_私鑰解密後明文.decode()
                        #print("S_私鑰解密後明文(字符串)", S_私鑰解密後明文)
                        privkey = ''         # 每次用完清空
                        top.clipboard_clear()
                        top.clipboard_append(S_私鑰解密後明文)
                        
                        ## 提示哪個數據被複制到剪貼板
                        選中行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):
    行數 = 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()
    主窗口寬, 主窗口高, 主窗口X, 主窗口Y = re.findall('[0-9]+', 主窗口大小和位置)
    畫布限寬 = int(主窗口寬)
    print("畫布限寬", 畫布限寬)
    if 畫布限寬 < 589:
        畫布限寬 = 589                  # 保障最小寬度
    畫布限高 = int(主窗口高) -300       # 減去顯示框上邊的內容
    print("畫布限高", 畫布限高)
    if 畫布限高 < 250:
        畫布限高 = 250                  # 保障最小高度
    
    ## 設置畫布參數
    總行數 = 行數 + 1
    
    ## 畫布可滾動顯示的最大寬和高(要剛好能放下畫布裏的Frame裏的全部控件)
    畫布滾動最右邊 = 786          ## Enter默認寬20==140像素(7像素/每寬) ID 10 PS 30 USER20 PASS50
    畫布滾動最下邊 = 21*總行數    ## 20*行數 + 行數*1
    
    ## 動態設置顯示畫布固定顯示寬和高(要和主顯示框的大小匹配)
    if 畫布限寬 > 畫布滾動最右邊:
        畫布['width'] = 畫布滾動最右邊
    else:
        畫布['width'] = 畫布限寬 - 30
    
    if 畫布限高 > 畫布滾動最下邊:
        畫布['height'] = 畫布滾動最下邊
    else:
        畫布['height'] = 畫布限高
    
    畫布['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=10, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=0,sticky='W')
    
    ## PS
    初始值 = str(L[1])                  # 轉成字符串
    字典_查詢字段_座標_初值[(0,1)] = 初始值 # 保存初始值
    輸入框對象 = Entry(字段框, width=30, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=1,sticky='W')
    
    ## USER
    初始值 = str(L[2])                  # 轉成字符串
    字典_查詢字段_座標_初值[(0,2)] = 初始值 # 保存初始值
    輸入框對象 = Entry(字段框, width=20, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=2,sticky='W')
    
    ## PASS
    初始值 = str(L[3])                  # 轉成字符串
    字典_查詢字段_座標_初值[(0,3)] = 初始值 # 保存初始值
    輸入框對象 = Entry(字段框, width=50, 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=10)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=0,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
        
        ## PS
        初始值 = str(LL[行][1])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,1)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=30)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=1,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
        
        ## USER
        初始值 = str(LL[行][2])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,2)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=20)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=2,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
        
        ## PASS
        初始值 = str(LL[行][3])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,3)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=50)
        輸入框對象.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')
##
########



######## 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')

命令行_按鈕框 = 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)  # 自動設置滾動條滑動幅度




## 開軟件後先初始化數據庫
## 如果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'創建表失敗:{R[1]}'
    tkinter.messagebox.showerror(title='ERROR', message=ERROR)

## 再打開數據庫
打開數據庫()

# 進入消息循環
top.mainloop()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章