add support to encrypt database
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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''')
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user