add support to encrypt database

This commit is contained in:
2022-01-26 18:30:11 +08:00
parent 84f2cb1fea
commit ccb61741ee
6 changed files with 96 additions and 10 deletions

View File

@@ -6,6 +6,7 @@ compress_method: "bzip2" # Optional. Default value: null. Supported value: "bzi
# Optional. Default value: null. bzip2 support 1-9 (Default: 9). gzip support 0-9 (Default: 9). lzma or lzip support 0-9 (Default: 6). # Optional. Default value: null. bzip2 support 1-9 (Default: 9). gzip support 0-9 (Default: 9). lzma or lzip support 0-9 (Default: 6).
# zstd support 0-22 (Default: 3). brotli support 0-11 (Default: unset). # zstd support 0-22 (Default: 3). brotli support 0-11 (Default: unset).
compress_level: 6 compress_level: 6
encrypt_db: false # Optional. Default value: false. Encrypt the database. Warning: The default python sqlite library don't support encrypt, it just ignore encrypt phases.
programs: programs:
- name: Your program name # This name is used to identify different application. - name: Your program name # This name is used to identify different application.
base: /path/to/save/path # Must be absoulte path. base: /path/to/save/path # Must be absoulte path.

View File

@@ -37,10 +37,12 @@ class Opts:
action = OptAction.BACKUP action = OptAction.BACKUP
programs_list = None programs_list = None
optimize_db = False optimize_db = False
change_key = False
def __init__(self, cml: List[str]): def __init__(self, cml: List[str]):
try: try:
r = getopt(cml, 'hc:', ['help', 'config=', 'optimize-db']) r = getopt(cml, 'hc:', ['help', 'config=', 'optimize-db',
'change-key'])
for i in r[0]: for i in r[0]:
if i[0] == '-h' or i[0] == '--help': if i[0] == '-h' or i[0] == '--help':
self.print_help() self.print_help()
@@ -50,6 +52,8 @@ class Opts:
self.config_file = i[1] self.config_file = i[1]
elif i[0] == '--optimize-db': elif i[0] == '--optimize-db':
self.optimize_db = True self.optimize_db = True
elif i[0] == '--change-key':
self.change_key = True
if len(r[1]) > 0: if len(r[1]) > 0:
cm = r[1] cm = r[1]
re = OptAction.from_str(cm[0]) re = OptAction.from_str(cm[0])
@@ -78,4 +82,5 @@ game-backuper [options] list_leveldb_key [<db_path> [...]]
Options: Options:
-h, --help Print help message. -h, --help Print help message.
-c, --config <path> Set config file. -c, --config <path> Set config file.
--optimize-db Optimize the sqlite3 database''') --optimize-db Optimize the sqlite3 database
--change-key Change encrypt password''')

View File

@@ -418,6 +418,7 @@ class Program(BasicOption, NFBasicOption):
class Config(BasicOption, NFBasicOption): class Config(BasicOption, NFBasicOption):
dest = '' dest = ''
encrypt_db = False
progs = [] progs = []
progs_name = [] progs_name = []
@@ -431,6 +432,12 @@ class Config(BasicOption, NFBasicOption):
if 'dest' not in t or not isinstance(t['dest'], str): if 'dest' not in t or not isinstance(t['dest'], str):
raise ValueError("Config file don't have dest or dest is not str.") raise ValueError("Config file don't have dest or dest is not str.")
self.dest = t['dest'] self.dest = t['dest']
if 'encrypt_db' in t:
if not isinstance(t['encrypt_db'], bool):
raise ValueError('encrypt_db should be true or false.')
self.encrypt_db = t['encrypt_db']
else:
self.encrypt_db = False
if 'programs' not in t: if 'programs' not in t:
raise ValueError("No programs found.") raise ValueError("No programs found.")
self.parse_all(t) self.parse_all(t)

View File

@@ -1,7 +1,13 @@
from sqlite3 import connect from getpass import getpass
from os import close
from os.path import join from os.path import join
from typing import List, Union from shutil import move
from sqlite3 import connect, Connection, DatabaseError
from tempfile import mkstemp
from threading import Lock from threading import Lock
from typing import List, Union
from game_backuper.cml import Opts
from game_backuper.config import Config
from game_backuper.file import File, hydrate_file_if_needed from game_backuper.file import File, hydrate_file_if_needed
from game_backuper.filetype import FileType from game_backuper.filetype import FileType
@@ -56,17 +62,64 @@ class Db:
self.db.execute(FILETYPE_TABLE) self.db.execute(FILETYPE_TABLE)
self.db.commit() self.db.commit()
def __init__(self, loc: str, optimize_db: bool = False): def __init__(self, config: Config, opts: Opts):
fn = join(loc, "data.db") self._cfg = config
self._opt = opts
fn = join(config.dest, "data.db")
hydrate_file_if_needed(fn) hydrate_file_if_needed(fn)
self.db = connect(fn, check_same_thread=False) self.db = connect(fn, check_same_thread=False)
if optimize_db: if config.encrypt_db:
passpharse = getpass('Please input the password of the database:')
if not self.encrypted:
tfn = mkstemp()
close(tfn[0])
tfn = tfn[1]
db = connect(tfn)
self.__set_encrypt_key(passpharse, db)
for q in self.db.iterdump():
db.execute(q)
self.db.close()
db.close()
move(tfn, fn)
self.db = connect(fn, check_same_thread=False)
elif opts.change_key:
self.__set_encrypt_key(passpharse)
passpharse = getpass('Please input new password of the database:') # noqa: E501
tfn = mkstemp()
close(tfn[0])
tfn = tfn[1]
db = connect(tfn)
self.__set_encrypt_key(passpharse, db)
for q in self.db.iterdump():
db.execute(q)
self.db.close()
db.close()
move(tfn, fn)
self.db = connect(fn, check_same_thread=False)
self.__set_encrypt_key(passpharse)
else:
if self.encrypted:
passpharse = getpass('Please input the password of the database:') # noqa: E501
self.__set_encrypt_key(passpharse)
tfn = mkstemp()
close(tfn[0])
tfn = tfn[1]
db = connect(tfn)
for q in self.db.iterdump():
db.execute(q)
self.db.close()
db.close()
move(tfn, fn)
self.db = connect(fn, check_same_thread=False)
if opts.optimize_db:
self.db.execute('VACUUM;') self.db.execute('VACUUM;')
self.db.commit() self.db.commit()
ok = self.__check_database() ok = self.__check_database()
if not ok: if not ok:
self.__create_table() self.__create_table()
self._lock = Lock() self._lock = Lock()
if config.encrypt_db and not self.encrypted:
print('Warning: Current library do not support encryption.')
def __read_version(self) -> List[int]: def __read_version(self) -> List[int]:
if 'version' not in self._exist_table: if 'version' not in self._exist_table:
@@ -75,6 +128,12 @@ class Db:
for i in cur: for i in cur:
return [k for k in i if isinstance(k, int)] return [k for k in i if isinstance(k, int)]
def __set_encrypt_key(self, key: str, db: Connection = None):
if db is None:
db = self.db
db.execute('PRAGMA cipher_salt = "x\'2d506b1d2c3e7b075518f9db81039657\'";') # noqa: E501
db.execute('PRAGMA key = \'%s\';' % (key.replace("'", "\\'")))
def __updateExistsTable(self): def __updateExistsTable(self):
cur = self.db.execute('SELECT * FROM main.sqlite_master;') cur = self.db.execute('SELECT * FROM main.sqlite_master;')
self._exist_table = {} self._exist_table = {}
@@ -105,6 +164,17 @@ class Db:
(i[0], f.type)) (i[0], f.type))
self.db.commit() self.db.commit()
@property
def encrypted(self):
try:
con = connect(join(self._cfg.dest, 'data.db'))
con.execute('SELECT count(*) FROM sqlite_master;')
con.close()
return False
except DatabaseError:
con.close()
return True
def get_file(self, prog: str, file: str) -> File: def get_file(self, prog: str, file: str) -> File:
with self._lock: with self._lock:
cur = self.db.execute( cur = self.db.execute(

View File

@@ -6,8 +6,11 @@ from shutil import copy2
from game_backuper.filetype import FileType from game_backuper.filetype import FileType
from platform import system from platform import system
if system() == "Windows": if system() == "Windows":
from game_backuper.cfapi import hydrate_file try:
have_cfapi = True from game_backuper.cfapi import hydrate_file
have_cfapi = True
except Exception:
have_cfapi = False
else: else:
have_cfapi = False have_cfapi = False

View File

@@ -14,6 +14,6 @@ def main(cm=None):
cfg = Config(cml.config_file) cfg = Config(cml.config_file)
if not exists(cfg.dest): if not exists(cfg.dest):
makedirs(cfg.dest) makedirs(cfg.dest)
db = Db(cfg.dest, cml.optimize_db) db = Db(cfg, cml)
bk = Backuper(db, cfg, cml) bk = Backuper(db, cfg, cml)
return bk.run() return bk.run()