mirror of
https://github.com/kevinveenbirkenbach/splitted-secret.git
synced 2025-09-09 11:47:15 +02:00
Compare commits
45 Commits
550b689c12
...
main
Author | SHA1 | Date | |
---|---|---|---|
6f5c04ba11 | |||
a5ada012b6 | |||
69d0592b05 | |||
5e4fe2c01a | |||
b939870c1d | |||
44b2db0cbc | |||
960e14d59e | |||
62f51b449d | |||
09bb6d1e31 | |||
a0c17c9fd6 | |||
b3bd3a0095 | |||
11848ba222 | |||
a88ca6904a | |||
5ad38ab566 | |||
565c5d0961 | |||
07ba9b2164 | |||
63f2d9fd18 | |||
cc943c8c12 | |||
42a5b93d67 | |||
591af85d82 | |||
080b8d66e4 | |||
260d7dfbb7 | |||
4caec4e1dc | |||
4c3661de76 | |||
c85c108db5 | |||
baefc461fa | |||
bfcda49660 | |||
c06d1d34d2 | |||
7b33c5420d | |||
2b3e4932c2 | |||
a00550e0b6 | |||
7a7fddee81 | |||
b54723448d | |||
47af457162 | |||
f182e3226a | |||
e5806992ce | |||
e4217afe64 | |||
72765e280d | |||
0831ab3448 | |||
09f494804f | |||
284fec6c15 | |||
b3c31c0013 | |||
10689122c0 | |||
f2b2fd1b07 | |||
4860282de1 |
7
.github/FUNDING.yml
vendored
Normal file
7
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
github: kevinveenbirkenbach
|
||||
|
||||
patreon: kevinveenbirkenbach
|
||||
|
||||
buy_me_a_coffee: kevinveenbirkenbach
|
||||
|
||||
custom: https://s.veen.world/paypaldonate
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,2 @@
|
||||
data/decrypted/group_files/*
|
||||
data/decrypted/user_files/*
|
||||
data/encrypted/group_files/*
|
||||
data/encrypted/user_files/*
|
||||
accumulated.json*
|
||||
__pycache__
|
||||
INSTRUCTIONS.md
|
95
README.md
Normal file
95
README.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Split Secret (sisec) 🔐
|
||||
[](https://github.com/sponsors/kevinveenbirkenbach) [](https://www.patreon.com/c/kevinveenbirkenbach) [](https://buymeacoffee.com/kevinveenbirkenbach) [](https://s.veen.world/paypaldonate)
|
||||
|
||||
|
||||
[](LICENSE) [](https://github.com/kevinveenbirkenbach/split-secret/stargazers)
|
||||
|
||||
Split Secret is a versatile command-line tool for securely splitting a master secret among multiple users. Only when a defined quorum of users combines their secret shares can the original secret be decrypted. The tool also supports robust encryption, decryption, and cleanup operations to ensure sensitive data is managed securely throughout the process.
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Features
|
||||
|
||||
- **Secret Splitting:** Divide a master secret into shares distributed among users.
|
||||
- **Encryption & Decryption:** Securely encrypt and decrypt data files using strong cryptographic methods.
|
||||
- **User Management:** Add and manage user information along with their secret shares.
|
||||
- **Cleanup Operations:** Remove decrypted files after processing to maintain security.
|
||||
- **Interactive Modes:** Operate in active, preview, or interactive modes to match your workflow.
|
||||
- **Parallel Processing:** Efficiently handles file operations using process pooling.
|
||||
|
||||
---
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
Install Split Secret via [Kevin's Package Manager](https://github.com/kevinveenbirkenbach/package-manager) under the alias `sisec`:
|
||||
|
||||
```bash
|
||||
package-manager install sisec
|
||||
```
|
||||
|
||||
This command installs Split Secret globally, making it available as `sisec` in your terminal. 🚀
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
Split Secret offers several modes for managing your secrets. Here are a few example commands:
|
||||
|
||||
### Cleanup Data
|
||||
To delete all unnecessary decrypted and encrypted files:
|
||||
```bash
|
||||
sisec --mode cleanup
|
||||
```
|
||||
|
||||
### Encrypt Data & Generate Meta Data
|
||||
Encrypt the master secret file and generate encrypted metadata with additional user information:
|
||||
```bash
|
||||
sisec --secret-holders-amount 3 --quota 50 --mode encrypt --add-user-information --master-password "your_master_password" --meta --add-user-information << EOL
|
||||
Alan Turing
|
||||
+12358
|
||||
turing@turing-bomb.world
|
||||
Bletchley Park
|
||||
¯\_(ツ)_/¯
|
||||
Ada Lovelace
|
||||
+132134
|
||||
best@algorythm.ai
|
||||
Somewhere in London
|
||||
:)
|
||||
John von Neumann
|
||||
+5488142
|
||||
test@test3.de
|
||||
Washington D.C.
|
||||
<3 <3 <3
|
||||
EOL
|
||||
```
|
||||
|
||||
### Decrypt Meta Data File
|
||||
To decrypt the accumulated metadata file:
|
||||
```bash
|
||||
sisec --mode decrypt --meta
|
||||
```
|
||||
|
||||
For additional commands and options (such as user-specific decryption, file type filtering, or recursive processing), use:
|
||||
```bash
|
||||
sisec --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧑💻 Author
|
||||
|
||||
Developed by **Kevin Veen-Birkenbach**
|
||||
- 📧 [kevin@veen.world](mailto:kevin@veen.world)
|
||||
- 🌐 [https://www.veen.world](https://www.veen.world)
|
||||
|
||||
---
|
||||
|
||||
## 📜 License
|
||||
|
||||
This project is licensed under the **MIT License**.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributions
|
||||
|
||||
Contributions are welcome! Please feel free to fork the repository, submit pull requests, or open issues if you have suggestions or encounter any problems. Let's work together to make secure secret management accessible and efficient! 😊
|
23
Readme.md
23
Readme.md
@@ -1,23 +0,0 @@
|
||||
# Splitted Secret
|
||||
The purpose of this software is to splitt a secret over multiple people. Just if a defined amount of this people meet together they can encrypt the secret and have access to it.
|
||||
|
||||
# Requirements to know
|
||||
- Amount of People
|
||||
- How much people need to reunite for decrypting
|
||||
|
||||
# Requirements to implement
|
||||
- Plattform independend
|
||||
- easy to use
|
||||
|
||||
# setup
|
||||
```bash
|
||||
pip install numpy
|
||||
```
|
||||
|
||||
## Further Information
|
||||
- https://www.tutorialspoint.com/python/python_command_line_arguments.htm
|
||||
- https://docs.python.org/3/library/argparse.html#module-argparse
|
||||
- https://wiki.ubuntuusers.de/GoCryptFS/
|
||||
- https://pynative.com/python-generate-random-string/
|
||||
- https://www.studimup.de/abitur/stochastik/anzahl-der-m%C3%B6glichketen-berechnen-kombinatorik/
|
||||
- https://numpy.org/doc/stable/reference/generated/numpy.base_repr.html?highlight=base_repr#numpy.base_repr
|
2
data/.gitignore
vendored
Normal file
2
data/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.gpg
|
||||
*.txt
|
3
data/decrypted/.gitignore
vendored
Normal file
3
data/decrypted/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
main_data/*
|
||||
*.json
|
||||
*.txt
|
1
data/decrypted/group_files/.gitignore
vendored
Normal file
1
data/decrypted/group_files/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.txt
|
1
data/decrypted/user_files/.gitignore
vendored
Normal file
1
data/decrypted/user_files/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.json
|
1
data/encrypted/group_files/.gitignore
vendored
Normal file
1
data/encrypted/group_files/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.gpg
|
1
data/encrypted/user_files/.gitignore
vendored
Normal file
1
data/encrypted/user_files/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.json.pgp
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
pacman -S gpg tar python pip python-pip
|
||||
pip install numpy
|
@@ -1,11 +0,0 @@
|
||||
from .Cli import Cli
|
||||
|
||||
class AbstractSplittedSecret(Cli):
|
||||
def __init__(self):
|
||||
super(Cli, self).__init__()
|
||||
self.encrypted_folder="data/encrypted/"
|
||||
self.decrypted_folder="data/encrypted/"
|
||||
self.encrypted_group_files_folder = self.encrypted_folder + "group_files/"
|
||||
self.decrypted_group_files_folder = self.decrypted_folder + "group_files/"
|
||||
self.encrypted_user_files_folder = self.encrypted_folder + "user_files/"
|
||||
self.decrypted_user_files_folder = self.encrypted_folder + "user_files/"
|
@@ -1,26 +1,44 @@
|
||||
from .AbstractSplittedSecret import AbstractSplittedSecret
|
||||
class Cleanup(AbstractSplittedSecret):
|
||||
def __init__(self):
|
||||
super(Cleanup, self).__init__()
|
||||
self.encrypted_files_folders = [self.decrypted_group_files_folder,self.decrypted_user_files_folder]
|
||||
self.decrypted_files_folders = [self.encrypted_group_files_folder,self.encrypted_user_files_folder]
|
||||
from .Paths import Paths
|
||||
|
||||
class Cleanup():
|
||||
def __init__(self,cli,paths):
|
||||
self.cli = cli
|
||||
self.paths = paths
|
||||
|
||||
def getMetaFilePaths(self,file_type):
|
||||
return [
|
||||
self.paths.getGroupFilesFolderPath(file_type),
|
||||
self.paths.getUserFilesPath(file_type),
|
||||
self.paths.getAccumulatedFilePath(file_type)
|
||||
]
|
||||
|
||||
def getAllFilePaths(self,file_type):
|
||||
all_file_paths = self.getMetaFilePaths(file_type)
|
||||
if file_type == Paths.TYPE_DECRYPTED:
|
||||
all_file_paths.append(self.paths.getDecryptedMainDataStandartFolder())
|
||||
return all_file_paths
|
||||
|
||||
def deleteAllFilesInFolder(self,folder_path):
|
||||
try:
|
||||
self.executeCommand('rm -v ' + folder_path + '*')
|
||||
print(self.getCommandString())
|
||||
print(self.getOutputString())
|
||||
except:
|
||||
self.cli.executeCommand('rm -r ' + folder_path + '*')
|
||||
except Exception as error:
|
||||
pass
|
||||
|
||||
def deleteAllDecryptedFiles(self):
|
||||
for folder_path in self.decrypted_files_folders:
|
||||
def cleanupFiles(self,file_type):
|
||||
for folder_path in self.getAllFilePaths(file_type):
|
||||
self.deleteAllFilesInFolder(folder_path)
|
||||
|
||||
def deleteAllEncryptedFiles(self):
|
||||
for folder_path in self.encrypted_files_folders:
|
||||
def cleanupForUser(self,user):
|
||||
try:
|
||||
self.cli.executeCommand('find "' + self.paths.getDataFolderPath(Paths.TYPE_ENCRYPTED) + '" -not -name "*' + str(user) +'*" -type f -print | xargs rm -v')
|
||||
except Exception as error:
|
||||
pass
|
||||
self.cleanupFiles(Paths.TYPE_DECRYPTED)
|
||||
|
||||
def cleanupMetaData(self,file_type):
|
||||
for folder_path in self.getMetaFilePaths(file_type):
|
||||
self.deleteAllFilesInFolder(folder_path)
|
||||
|
||||
def deleteAll(self):
|
||||
self.deleteAllEncryptedFiles()
|
||||
self.deleteAllDecryptedFiles()
|
||||
self.cleanupFiles(Paths.TYPE_ENCRYPTED)
|
||||
self.cleanupFiles(Paths.TYPE_DECRYPTED)
|
@@ -19,8 +19,7 @@ class Cli(object):
|
||||
for line in stdout:
|
||||
self.output.append(line.decode("utf-8"))
|
||||
if process.wait() > bool(0):
|
||||
print(command, out, err)
|
||||
raise Exception("Exitcode is greater then 0")
|
||||
raise Exception("Error for: \nCommand:<<" + str(command) + ">>\nOutput:<<" + str(out) + ">>\nExitcode:<<" + str(err) + ">>")
|
||||
return self.output
|
||||
|
||||
def getOutputString(self):
|
||||
|
137
scripts/classes/Decryption.py
Normal file
137
scripts/classes/Decryption.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
|
||||
class AutomaticIdentificationImpossibleException(Exception):
|
||||
pass
|
||||
|
||||
class Decryption():
|
||||
|
||||
def __init__(self,cli,paths):
|
||||
self.user_id='0';
|
||||
self.user_password=''
|
||||
self.cli = cli
|
||||
self.paths = paths
|
||||
|
||||
def identifyUser(self):
|
||||
file_type = self.paths.TYPE_ENCRYPTED
|
||||
file_names = next(os.walk(self.paths.getUserFilesPath(file_type)), (None, None, []))[2]
|
||||
users = []
|
||||
user_file_suffix = self.paths.getUserFileSuffix(file_type)
|
||||
for file in file_names:
|
||||
if user_file_suffix in file:
|
||||
users.append(file.replace(user_file_suffix, ''))
|
||||
if len(users) < 2:
|
||||
return users[0]
|
||||
raise AutomaticIdentificationImpossibleException()
|
||||
|
||||
def initializeUser(self,user_id):
|
||||
self.user_id=str(user_id)
|
||||
self.user_file_decrypted_path = self.paths.getUserFilePath(self.user_id,self.paths.TYPE_DECRYPTED)
|
||||
|
||||
def initializeUserDataDecryption(self):
|
||||
self.decryptUserFile()
|
||||
self.user_data = self.loadJsonFile(self.user_file_decrypted_path)
|
||||
self.initializeNeededDecryptersAmount()
|
||||
self.initializeValidDecrypterIds()
|
||||
|
||||
def getEscapedMasterPassword(self):
|
||||
return shlex.quote(self.master_password)
|
||||
|
||||
def initializeGroupDataEncryption(self):
|
||||
self.group_name = self.getDecryptersGroupName()
|
||||
self.encrypted_group_file_path = self.paths.getGroupFilePath(self.group_name, self.paths.TYPE_DECRYPTED)
|
||||
self.decryptGroupFile()
|
||||
self.master_password = self.loadTxtFile(self.encrypted_group_file_path).strip()
|
||||
|
||||
def getMasterPassword(self):
|
||||
return self.master_password
|
||||
|
||||
def initializeNeededDecryptersAmount(self):
|
||||
self.needed_decrypters_amount = len(str(list(self.user_data['groups'].keys())[0]))
|
||||
|
||||
def initializeValidDecrypterIds(self):
|
||||
self.valid_decrypter_ids = []
|
||||
self.valid_decrypter_ids.append(int(self.user_id))
|
||||
for contact_id in self.user_data['contacts']:
|
||||
self.valid_decrypter_ids.append(int(contact_id))
|
||||
|
||||
def setUserPassword(self,user_password):
|
||||
self.user_password = str(user_password)
|
||||
|
||||
def resetDecrypterIds(self):
|
||||
self.decrypter_ids = []
|
||||
self.addDecrypterId(self.user_id)
|
||||
|
||||
def resetPasswordShare(self):
|
||||
self.password_parts = {}
|
||||
self.addPasswordShare(self.user_id,self.getPasswordShare())
|
||||
|
||||
def addPasswordShare(self,user_id,password_share):
|
||||
self.password_parts[str(user_id)] = password_share
|
||||
|
||||
def getGroupPassword(self):
|
||||
shared_password = ''
|
||||
for password_share_index in sorted(self.password_parts):
|
||||
shared_password += str(self.password_parts[password_share_index])
|
||||
return shared_password
|
||||
|
||||
def addDecrypterId(self,decrypter_id):
|
||||
decrypter_id = int(decrypter_id)
|
||||
if decrypter_id not in self.valid_decrypter_ids:
|
||||
raise Exception("The encrypter id is not valid. Valid encrypter ids are: " + str(self.valid_decrypter_ids))
|
||||
if len(self.decrypter_ids) >= self.needed_decrypters_amount:
|
||||
raise Exception("There are already sufficients decrypters (" + str(len(self.decrypter_ids)) + ") defined!")
|
||||
if decrypter_id in self.decrypter_ids:
|
||||
raise Exception("The decrypter is already in the list.")
|
||||
self.decrypter_ids.append(decrypter_id)
|
||||
|
||||
def getUserId(self):
|
||||
return self.user_id
|
||||
|
||||
def getCoDecrypterIds(self):
|
||||
co_decrypter_ids = self.decrypter_ids[:]
|
||||
co_decrypter_ids.remove(int(self.user_id))
|
||||
return co_decrypter_ids
|
||||
|
||||
def getDecrypterIds(self):
|
||||
return self.decrypter_ids
|
||||
|
||||
def getDecryptersGroupName(self):
|
||||
self.decrypter_ids.sort()
|
||||
return ''.join(str(x) for x in self.decrypter_ids)
|
||||
|
||||
def getPasswordShare(self):
|
||||
return self.user_data['groups'][str(self.getDecryptersGroupName())]
|
||||
|
||||
def getNeededDecryptersAmount(self):
|
||||
return self.needed_decrypters_amount
|
||||
|
||||
def loadTxtFile(self,file_path):
|
||||
return Path(file_path).read_text()
|
||||
|
||||
def loadJsonFile(self,file_path):
|
||||
file = open(file_path)
|
||||
data = json.load(file)
|
||||
file.close()
|
||||
return data
|
||||
|
||||
def decryptFile(self,password,input_file_path,output_file_path):
|
||||
self.cli.executeCommand('gpg --batch --passphrase '+ shlex.quote(password) + ' -o "' + output_file_path +'" "'+ input_file_path+'"')
|
||||
|
||||
def decryptUserFile(self):
|
||||
input_file_path = self.paths.getUserFilePath(self.user_id,self.paths.TYPE_ENCRYPTED)
|
||||
self.decryptFile(self.user_password, input_file_path, self.user_file_decrypted_path)
|
||||
|
||||
def decryptGroupFile(self):
|
||||
input_file_path = self.paths.getGroupFilePath(self.group_name, self.paths.TYPE_ENCRYPTED)
|
||||
self.decryptFile(self.getGroupPassword(), input_file_path, self.encrypted_group_file_path)
|
||||
|
||||
def decryptAccumulatedFile(self):
|
||||
input_file_path = self.paths.getAccumulatedFilePath(self.paths.TYPE_ENCRYPTED)
|
||||
output_file_path = self.paths.getAccumulatedFilePath(self.paths.TYPE_DECRYPTED)
|
||||
self.decryptFile(self.user_password, input_file_path, output_file_path)
|
||||
|
||||
def decryptMainData(self):
|
||||
self.cli.executeCommand('gpg --batch --passphrase ' + shlex.quote(self.getMasterPassword()) + ' -d "' + self.paths.getEncryptedMainDataFile() + '" | tar --one-top-level="' + self.paths.getDecryptedMainDataStandartFolder() + '" -xvzf -')
|
153
scripts/classes/Encryption.py
Normal file
153
scripts/classes/Encryption.py
Normal file
@@ -0,0 +1,153 @@
|
||||
import random
|
||||
import string
|
||||
import math
|
||||
import numpy
|
||||
import re
|
||||
import json
|
||||
from .Paths import Paths
|
||||
import shlex
|
||||
|
||||
class Encryption():
|
||||
|
||||
USER_PASSWORD_LENGTHS = 64
|
||||
OVERALL_PASSWORD_LENGTHS = 128
|
||||
|
||||
# At the moment the programm can only deal with one digit numbers.
|
||||
MAXIMUM_SECRET_HOLDERS = 9
|
||||
MINIMUM_SECRET_HOLDERS = 2
|
||||
|
||||
def __init__(self, cli, paths, amount_of_secret_holders, decryption_quota,master_password):
|
||||
self.amount_of_secret_holders = amount_of_secret_holders
|
||||
self.decryption_quota = decryption_quota
|
||||
self.master_password = master_password
|
||||
self.quota_factor=self.decryption_quota/100
|
||||
self.group_members_amount=math.ceil(self.amount_of_secret_holders * self.quota_factor)
|
||||
self.initializeUserData()
|
||||
self.initializeGroupData()
|
||||
self.cli = cli
|
||||
self.paths = paths
|
||||
|
||||
def initializeUserData(self):
|
||||
self.user_mapped_data = {}
|
||||
user_count = 1
|
||||
while user_count <= self.amount_of_secret_holders:
|
||||
self.user_mapped_data[str(user_count)] = {"groups":{},"user_password":self.createPassword(self.USER_PASSWORD_LENGTHS),"about":{}}
|
||||
user_count += 1;
|
||||
|
||||
def initializeGroupData(self):
|
||||
self.group_mapped_data = {}
|
||||
|
||||
def addInformationToUser(self,user_id,label,content):
|
||||
self.user_mapped_data[user_id]['about'][label] = content;
|
||||
|
||||
def getCoSecretHoldersRange():
|
||||
return range(Encryption.MINIMUM_SECRET_HOLDERS,(Encryption.MAXIMUM_SECRET_HOLDERS+1))
|
||||
|
||||
def getSecretHoldersRange():
|
||||
return range(1,(Encryption.MAXIMUM_SECRET_HOLDERS+1))
|
||||
|
||||
def getStartnumber(self):
|
||||
index = 0
|
||||
start_number = ''
|
||||
while index < self.group_members_amount:
|
||||
start_number += '1'
|
||||
index += 1
|
||||
return int(start_number)
|
||||
|
||||
def getEndnumber(self):
|
||||
index = 0
|
||||
start_number = ''
|
||||
while index < self.group_members_amount:
|
||||
start_number += str(self.amount_of_secret_holders)
|
||||
index += 1
|
||||
return int(start_number)
|
||||
|
||||
def createPassword(self,length):
|
||||
characters = string.ascii_letters + string.digits
|
||||
return (''.join(random.choice(characters) for i in range(length)).upper())
|
||||
|
||||
def isGroupValid(self,password_group_name):
|
||||
secret_stakeholders_range=range(1,(self.amount_of_secret_holders+1))
|
||||
valid_numbers = re.compile("([" + ','.join([str(x) for x in secret_stakeholders_range]) + "]{" + str(self.group_members_amount) + "})")
|
||||
unvalid_sequenz = re.compile("(.)\\1+")
|
||||
return re.search(valid_numbers, password_group_name) and not re.search(unvalid_sequenz, password_group_name)
|
||||
|
||||
def compileContacts(self):
|
||||
contacts = {}
|
||||
for user_id in self.user_mapped_data:
|
||||
contacts[user_id] = self.user_mapped_data[user_id]['about']
|
||||
for user_id in self.user_mapped_data:
|
||||
self.user_mapped_data[user_id]['contacts'] = {}
|
||||
for contact_id in contacts:
|
||||
if contact_id != user_id:
|
||||
self.user_mapped_data[user_id]['contacts'][contact_id] = contacts[contact_id]
|
||||
|
||||
def compileData(self):
|
||||
self.compileContacts()
|
||||
index = self.getStartnumber()
|
||||
while index <= self.getEndnumber():
|
||||
password_group_name = ''.join(sorted(str(index)))
|
||||
if self.isGroupValid(password_group_name):
|
||||
password_group_index_int = int(password_group_name)
|
||||
if not password_group_index_int in self.group_mapped_data:
|
||||
self.group_mapped_data[password_group_index_int] = {}
|
||||
self.group_mapped_data[password_group_index_int]['members'] = {}
|
||||
self.group_mapped_data[password_group_index_int]['password'] = ''
|
||||
password = ''
|
||||
for secret_holder_index in password_group_name:
|
||||
self.group_mapped_data[password_group_index_int]['members'][secret_holder_index]={}
|
||||
particial_password_length= int(self.OVERALL_PASSWORD_LENGTHS*self.quota_factor);
|
||||
password_part = self.createPassword(particial_password_length)
|
||||
self.group_mapped_data[password_group_index_int]['members'][secret_holder_index] = password_part
|
||||
password += password_part
|
||||
self.user_mapped_data[secret_holder_index]['groups'][password_group_name] = password_part
|
||||
self.group_mapped_data[password_group_index_int]['password'] += password
|
||||
index += 1
|
||||
|
||||
def encryptStringToFile(self,text,output_file,password):
|
||||
self.cli.executeCommand('echo ' + shlex.quote(text) + ' | gpg --symmetric --armor --batch --passphrase ' + shlex.quote(password) + ' -o "' + output_file + '"')
|
||||
|
||||
def encryptGroupFiles(self):
|
||||
for password_group_index_int in self.group_mapped_data:
|
||||
encrypted_group_password_file_path = self.paths.getGroupFilePath(password_group_index_int,Paths.TYPE_ENCRYPTED)
|
||||
self.encryptStringToFile(self.master_password,encrypted_group_password_file_path,self.group_mapped_data[password_group_index_int]['password'])
|
||||
|
||||
def encryptToJsonFile(self,data,file_path,password):
|
||||
self.encryptStringToFile(json.dumps(data,ensure_ascii=False), file_path, password)
|
||||
|
||||
def encryptUserFile(self):
|
||||
for user_id in self.user_mapped_data:
|
||||
file_path=self.paths.getUserFilePath(user_id,Paths.TYPE_ENCRYPTED)
|
||||
data=self.user_mapped_data[user_id]
|
||||
password=self.user_mapped_data[user_id]['user_password']
|
||||
self.encryptToJsonFile(data,file_path,password)
|
||||
'''
|
||||
This saving as decrypted file is necessary due to the reason that the shell can't deal with unlimited string length.
|
||||
See: https://stackoverflow.com/questions/30650841/why-am-i-getting-errno-7-argument-list-too-long-and-oserror-errno-24-too-ma
|
||||
'''
|
||||
def encryptFileToFile(self,input_file,output_file,password):
|
||||
self.cli.executeCommand('cat \'' + input_file + '\' | gpg --symmetric --armor --batch --passphrase ' + shlex.quote(password) + ' -o "' + output_file + '"')
|
||||
|
||||
def deleteDecryptedAccumulatedFile(self):
|
||||
self.cli.executeCommand('rm ' + self.paths.getAccumulatedFilePath(Paths.TYPE_DECRYPTED))
|
||||
|
||||
def saveDecryptedAccumulatedFile(self):
|
||||
file_path=self.paths.getAccumulatedFilePath(Paths.TYPE_DECRYPTED)
|
||||
data={"user_mapped": self.user_mapped_data, "group_mapped": self.group_mapped_data}
|
||||
with open(file_path, 'w') as file:
|
||||
json.dump(data, file)
|
||||
|
||||
def encryptAccumulatedFile(self):
|
||||
self.saveDecryptedAccumulatedFile()
|
||||
encrypted_file_path=self.paths.getAccumulatedFilePath(Paths.TYPE_ENCRYPTED)
|
||||
decrypted_file_path=self.paths.getAccumulatedFilePath(Paths.TYPE_DECRYPTED)
|
||||
self.encryptFileToFile(decrypted_file_path,encrypted_file_path,self.master_password)
|
||||
self.deleteDecryptedAccumulatedFile()
|
||||
|
||||
def encryptMainData(self,input_directory):
|
||||
self.cli.executeCommand('tar -C"' + input_directory + '" -cvzf - ./ | gpg -c --batch --passphrase ' + shlex.quote(self.master_password) + ' > "' + self.paths.getEncryptedMainDataFile() + '"')
|
||||
|
||||
def encryptMetaData(self):
|
||||
self.encryptUserFile()
|
||||
self.encryptGroupFiles()
|
||||
self.encryptAccumulatedFile()
|
@@ -1,130 +0,0 @@
|
||||
import random
|
||||
import string
|
||||
import math
|
||||
import numpy
|
||||
import re
|
||||
import json
|
||||
from .AbstractSplittedSecret import AbstractSplittedSecret
|
||||
|
||||
class Generate(AbstractSplittedSecret):
|
||||
|
||||
def __init__(self, amount_of_secret_holders, decryption_quota,master_password):
|
||||
super(Generate, self).__init__()
|
||||
self.amount_of_secret_holders = amount_of_secret_holders
|
||||
self.decryption_quota = decryption_quota
|
||||
self.master_password = master_password
|
||||
self.quota_factor=self.decryption_quota/100
|
||||
self.group_members_amount=math.ceil(self.amount_of_secret_holders * self.quota_factor)
|
||||
|
||||
def getStartnumber(self):
|
||||
index = 0
|
||||
start_number = ''
|
||||
while index < self.group_members_amount:
|
||||
start_number += '1'
|
||||
index += 1
|
||||
return int(start_number)
|
||||
|
||||
def getEndnumber(self):
|
||||
index = 0
|
||||
start_number = ''
|
||||
while index < self.group_members_amount:
|
||||
start_number += str(self.amount_of_secret_holders)
|
||||
index += 1
|
||||
return int(start_number)
|
||||
|
||||
def savePassword(self,password,password_file_path):
|
||||
print("Saving password to: " + password_file_path)
|
||||
master_password_file = open(password_file_path, "a")
|
||||
master_password_file.seek(0)
|
||||
master_password_file.truncate()
|
||||
master_password_file.write(password)
|
||||
master_password_file.close()
|
||||
|
||||
def createPassword(self,length):
|
||||
characters = string.ascii_letters + string.digits
|
||||
return (''.join(random.choice(characters) for i in range(length)).upper())
|
||||
|
||||
def isGroupValid(self,password_group_index_str):
|
||||
secret_stakeholders_range=range(1,(self.amount_of_secret_holders+1))
|
||||
valid_numbers = re.compile("([" + ','.join([str(x) for x in secret_stakeholders_range]) + "]{" + str(self.group_members_amount) + "})")
|
||||
unvalid_sequenz = re.compile("(.)\\1+")
|
||||
return re.search(valid_numbers, password_group_index_str) and not re.search(unvalid_sequenz, password_group_index_str)
|
||||
|
||||
def createUserMappedDataFrame(self):
|
||||
self.user_mapped_data = {}
|
||||
user_count = 1
|
||||
while user_count <= self.amount_of_secret_holders:
|
||||
self.user_mapped_data[str(user_count)] = {"groups":{},"user_password":self.createPassword(64)}
|
||||
user_count += 1;
|
||||
|
||||
def createGroupMappedDataFrame(self):
|
||||
self.group_mapped_data = {}
|
||||
|
||||
def generateMappedData(self):
|
||||
self.createUserMappedDataFrame()
|
||||
self.createGroupMappedDataFrame()
|
||||
index = self.getStartnumber()
|
||||
while index < self.getEndnumber():
|
||||
password_group_index_str = ''.join(sorted(str(index)))
|
||||
if self.isGroupValid(password_group_index_str):
|
||||
password_group_index_int = int(password_group_index_str)
|
||||
if not password_group_index_int in self.group_mapped_data:
|
||||
self.group_mapped_data[password_group_index_int] = {}
|
||||
self.group_mapped_data[password_group_index_int]['members'] = {}
|
||||
self.group_mapped_data[password_group_index_int]['password'] = ''
|
||||
password = ''
|
||||
for secret_holder_index in password_group_index_str:
|
||||
self.group_mapped_data[password_group_index_int]['members'][secret_holder_index]={}
|
||||
particial_password_length= int(128*self.quota_factor);
|
||||
password_part = self.createPassword(particial_password_length)
|
||||
self.group_mapped_data[password_group_index_int]['members'][secret_holder_index] = password_part
|
||||
password += password_part
|
||||
self.user_mapped_data[secret_holder_index]['groups'][password_group_index_str] = password_part
|
||||
self.group_mapped_data[password_group_index_int]['password'] += password
|
||||
index += 1
|
||||
|
||||
def encryptStringToFile(self,text,output_file,password):
|
||||
self.executeCommand('echo \'' + text + '\' | gpg --symmetric --armor --batch --passphrase "' + password + '" -o "' + output_file + '.gpg"')
|
||||
print(self.getCommandString())
|
||||
|
||||
def generateEncryptedGroupFiles(self):
|
||||
for password_group_index_int in self.group_mapped_data:
|
||||
encrypted_splitted_password_file = AbstractSplittedSecret().encrypted_group_files_folder + str(password_group_index_int) + ".txt"
|
||||
self.encryptStringToFile(self.master_password,encrypted_splitted_password_file,self.group_mapped_data[password_group_index_int]['password'])
|
||||
|
||||
def encryptToJsonFile(self,data,file_path,password):
|
||||
self.encryptStringToFile(json.dumps(data,ensure_ascii=False), file_path, password)
|
||||
|
||||
def encryptUserMappedData(self):
|
||||
for user_id in self.user_mapped_data:
|
||||
file_path=self.encrypted_user_files_folder+user_id+'.json'
|
||||
self.encryptToJsonFile(self.user_mapped_data[user_id]['groups'],file_path,self.user_mapped_data[user_id]['user_password'])
|
||||
|
||||
def encryptAccumulatedMappedData(self):
|
||||
file_path=self.encrypted_folder+'accumulated.json'
|
||||
data={"user_mapped": self.user_mapped_data, "group_mapped": self.group_mapped_data}
|
||||
self.encryptToJsonFile(data,file_path,self.master_password)
|
||||
|
||||
def saveMappedData(self):
|
||||
self.encryptUserMappedData()
|
||||
self.encryptAccumulatedMappedData()
|
||||
|
||||
def encryptMappedUserData(self):
|
||||
self.user_passwords = {}
|
||||
for user_id in self.user_mapped_data:
|
||||
self.user_passwords[user_id] = self.createPassword(64)
|
||||
|
||||
def encryptMappedData(self):
|
||||
self.encryptMappedUserData()
|
||||
|
||||
def generate(self):
|
||||
self.generateMappedData()
|
||||
self.saveMappedData()
|
||||
self.encryptMappedData()
|
||||
self.generateEncryptedGroupFiles()
|
||||
|
||||
def getUserMappedData(self):
|
||||
return self.user_mapped_data
|
||||
|
||||
def getGroupMappedData(self):
|
||||
return self.group_mapped_data
|
43
scripts/classes/Paths.py
Normal file
43
scripts/classes/Paths.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
|
||||
class Paths():
|
||||
|
||||
TYPE_ENCRYPTED="encrypted"
|
||||
TYPE_DECRYPTED="decrypted"
|
||||
|
||||
ROOT_PATH= os.path.join(os.path.dirname(os.path.abspath(__file__)),"../","../")
|
||||
|
||||
def __init__(self):
|
||||
self.data_folder = os.path.join(self.ROOT_PATH,"data") + '/'
|
||||
|
||||
def getDataFolderPath(self,folder_type):
|
||||
return self.data_folder + folder_type + "/"
|
||||
|
||||
def getGroupFilesFolderPath(self,folder_type):
|
||||
return self.getDataFolderPath(folder_type) + "group_files/"
|
||||
|
||||
def getUserFilesPath(self,folder_type):
|
||||
return self.getDataFolderPath(folder_type) + "user_files/"
|
||||
|
||||
def getEncryptedMainDataFile(self):
|
||||
return self.getDataFolderPath(Paths.TYPE_ENCRYPTED) + "main_data.tar.gz.gpg"
|
||||
|
||||
def getDecryptedMainDataStandartFolder(self):
|
||||
return self.getDataFolderPath(Paths.TYPE_DECRYPTED) + "main_data/"
|
||||
|
||||
def getFileExtension(self,file_type):
|
||||
if file_type == Paths.TYPE_ENCRYPTED:
|
||||
return '.gpg'
|
||||
return ''
|
||||
|
||||
def getUserFileSuffix(self,file_type):
|
||||
return '.json' + self.getFileExtension(file_type)
|
||||
|
||||
def getUserFilePath(self,user_id,file_type):
|
||||
return self.getUserFilesPath(file_type) + user_id + self.getUserFileSuffix(file_type);
|
||||
|
||||
def getGroupFilePath(self,group_id,file_type):
|
||||
return self.getGroupFilesFolderPath(file_type) + str(group_id) + '.txt' + self.getFileExtension(file_type);
|
||||
|
||||
def getAccumulatedFilePath(self,file_type):
|
||||
return self.getDataFolderPath(file_type) + 'accumulated.json' + self.getFileExtension(file_type);
|
184
scripts/main.py
184
scripts/main.py
@@ -1,27 +1,179 @@
|
||||
import argparse
|
||||
from classes.Generate import Generate
|
||||
from classes.Encryption import Encryption
|
||||
from classes.Cleanup import Cleanup
|
||||
from classes.Decryption import Decryption, AutomaticIdentificationImpossibleException
|
||||
from getpass import getpass
|
||||
import traceback
|
||||
from classes.Cli import Cli
|
||||
from classes.Paths import Paths
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--mode',type=str, dest='mode',required=True,choices=['cleanup','generate'])
|
||||
parser.add_argument('--amount',type=int, dest='amount_of_secret_holders',required=False,choices=range(1,9))
|
||||
parser.add_argument('--quota', type=int, dest='decryption_quota', choices=range(1,101),required=False)
|
||||
parser.add_argument('--master-password',type=str, dest='master_password',required=False)
|
||||
args = parser.parse_args()
|
||||
cli = Cli()
|
||||
paths = Paths()
|
||||
cleanup = Cleanup(cli,paths)
|
||||
|
||||
if args.mode == 'cleanup':
|
||||
cleanup = Cleanup()
|
||||
cleanup.deleteAll()
|
||||
def clean_exit():
|
||||
print("Cleaning up.")
|
||||
try:
|
||||
cleanup.cleanupFiles(Paths.TYPE_DECRYPTED)
|
||||
except:
|
||||
pass
|
||||
standard_exit()
|
||||
|
||||
def dirty_exit():
|
||||
print("ATTENTION: SECURITY RISK !!!\nPROGRAM DIDN'T CLEAN UP DECRYPTED DATA. \nDECRYPTED DATA EXISTS AND CAN BE READ BY EVERYBODY!")
|
||||
print("TO REMOVE DECRYPTED DATA EXECUTE:\nmain.py --mode cleanup --file-types " + Paths.TYPE_DECRYPTED)
|
||||
standard_exit()
|
||||
|
||||
def standard_exit():
|
||||
print("Leaving program.")
|
||||
exit()
|
||||
|
||||
if args.mode == 'generate':
|
||||
try:
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--mode',type=str, dest='mode',required=True,choices=['cleanup','encrypt','decrypt'])
|
||||
parser.add_argument('--file-types',type=str, dest='file_types',required=False,choices=[Paths.TYPE_DECRYPTED, Paths.TYPE_ENCRYPTED])
|
||||
parser.add_argument('--secret-holders-amount',type=int, dest='amount_of_secret_holders',required=False,choices=Encryption.getCoSecretHoldersRange(),help="Needed for creating of encryption meta data.")
|
||||
parser.add_argument('--quota', type=int, dest='decryption_quota', choices=range(1,101),required=False)
|
||||
parser.add_argument('--master-password',type=str, dest='master_password',required=False)
|
||||
parser.add_argument('--user-password',type=str, dest='user_password',required=False)
|
||||
parser.add_argument('--user',type=int, dest='user',choices=Encryption.getSecretHoldersRange(),required=False)
|
||||
parser.add_argument('--add-user-information',type=bool, dest='add_user_information', default=False, required=False, action=argparse.BooleanOptionalAction, help="Add additional information to users.")
|
||||
parser.add_argument('--input-directory',type=str,dest='input_directory',required=False, help="The directory from which the data should be encrypted.")
|
||||
parser.add_argument('--meta-data',type=bool, dest='meta_data', default=False, required=False, action=argparse.BooleanOptionalAction, help="When mode is encrypt and this flag is set, the encrypted meta data is created. When mode is decrypt and this flag is set the accumulated file will be decrypted.")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("Application started.")
|
||||
print("To leave the appplication use the key kombination: <<Ctr>> + <<Alt>> + <<C>>")
|
||||
print("Cleaning up all decrypted files.")
|
||||
cleanup.cleanupFiles(Paths.TYPE_DECRYPTED)
|
||||
print("Selected Mode: " + args.mode)
|
||||
|
||||
if args.mode == 'cleanup':
|
||||
print("Cleaning up.")
|
||||
if args.file_types is None:
|
||||
if args.user is None:
|
||||
print("Deleting all encrypted and decrypted files.")
|
||||
cleanup.deleteAll()
|
||||
standard_exit()
|
||||
print("Deleting all files which aren't related to user: " + str(args.user));
|
||||
cleanup.cleanupForUser(args.user)
|
||||
standard_exit()
|
||||
print("Deleting all " + args.file_types + " files.")
|
||||
cleanup.cleanupFiles(args.file_types)
|
||||
standard_exit()
|
||||
|
||||
if args.mode == 'decrypt':
|
||||
decrypt = Decryption(cli,paths)
|
||||
if args.meta_data is True:
|
||||
if args.master_password is None:
|
||||
print("Please enter the master password:")
|
||||
print("Enter the master password:")
|
||||
master_password = getpass()
|
||||
else:
|
||||
master_password = args.master_password
|
||||
generate = Generate(args.amount_of_secret_holders, args.decryption_quota,master_password)
|
||||
generate.generate()
|
||||
exit()
|
||||
decrypt = Decryption(cli,paths)
|
||||
print("Decrypting accumulated data.")
|
||||
decrypt.setUserPassword(master_password)
|
||||
decrypt.decryptAccumulatedFile()
|
||||
dirty_exit()
|
||||
if args.user is None:
|
||||
try:
|
||||
print("Attempt to identify user.")
|
||||
user_id = decrypt.identifyUser()
|
||||
print("The user id is: " + user_id)
|
||||
except:
|
||||
print("A automatic user id identification wasn't possible.")
|
||||
print("Type in the user id:")
|
||||
user_id = input()
|
||||
decrypt.initializeUser(user_id)
|
||||
else:
|
||||
decrypt.initializeUser(args.user)
|
||||
if args.user_password is None:
|
||||
while True:
|
||||
print("Enter the user password:")
|
||||
decrypt.setUserPassword(getpass())
|
||||
print("Decrypting User File...")
|
||||
try:
|
||||
decrypt.initializeUserDataDecryption();
|
||||
break;
|
||||
except Exception as error:
|
||||
print("An error occured. Propably you typed in a wrong password :( The error is: " + str(error))
|
||||
else:
|
||||
decrypt.setUserPassword(args.user_password)
|
||||
print("Decrypting User File...")
|
||||
try:
|
||||
decrypt.initializeUserDataDecryption();
|
||||
except Exception as error:
|
||||
print("An error occured. Propably you passed a wrong password :( The error is: " + str(error))
|
||||
clean_exit()
|
||||
print("\nContact the following persons and request their password share: \n")
|
||||
for contact_id in decrypt.user_data['contacts']:
|
||||
print("user_id: " + contact_id)
|
||||
for label in decrypt.user_data['contacts'][contact_id]:
|
||||
print(label + ": " + decrypt.user_data['contacts'][contact_id][label])
|
||||
while True:
|
||||
print("\nReset password shares.\n")
|
||||
decrypt.resetDecrypterIds()
|
||||
try:
|
||||
password_shares_count = 1
|
||||
while password_shares_count < decrypt.getNeededDecryptersAmount():
|
||||
print(str(password_shares_count) + " password shares had been added.")
|
||||
print("Password shares for the the users " + str(decrypt.getDecrypterIds()) + " been added. ")
|
||||
print("You need to add " + str((decrypt.getNeededDecryptersAmount()-password_shares_count)) +" more password shares.")
|
||||
print("\nType in the user id of another decrypter:")
|
||||
decrypt.addDecrypterId(int(input()))
|
||||
password_shares_count += 1
|
||||
break
|
||||
except Exception as error:
|
||||
print("The following error occured <<" + str(error) + ">> :( \n Try again :)")
|
||||
print("\nYour data is:\n")
|
||||
print("FOR PASSWORD GROUP: " + decrypt.getDecryptersGroupName())
|
||||
print("FOR USER ID: " + decrypt.getUserId())
|
||||
print("PASSWORD SHARE IS: " + decrypt.getPasswordShare() + "\n")
|
||||
while True:
|
||||
try:
|
||||
decrypt.resetPasswordShare()
|
||||
co_decrypter_ids = decrypt.getCoDecrypterIds()
|
||||
for co_decrypter_id in decrypt.getCoDecrypterIds():
|
||||
print("Type in the password share for: \n")
|
||||
print("FOR PASSWORD GROUP: " + decrypt.getDecryptersGroupName())
|
||||
print("FOR USER: " + str(co_decrypter_id))
|
||||
print("PASSWORD SHARE IS: ")
|
||||
decrypt.addPasswordShare(co_decrypter_id, input())
|
||||
print("\nTHE GROUP PASSWORD IS: " + decrypt.getGroupPassword())
|
||||
print("\nDecrypting group password file.\n")
|
||||
decrypt.initializeGroupDataEncryption()
|
||||
print("THE MASTER PASSWORD IS: " + decrypt.getMasterPassword())
|
||||
break;
|
||||
except:
|
||||
print("An unexpected error occured: \n" + traceback.format_exc())
|
||||
print("Decrypting main data.")
|
||||
decrypt.decryptMainData()
|
||||
print("The data was decrypted to: " + paths.getDecryptedMainDataStandartFolder())
|
||||
dirty_exit()
|
||||
|
||||
if args.mode == 'encrypt':
|
||||
if args.master_password is None:
|
||||
print("Enter the master password:")
|
||||
master_password = getpass()
|
||||
else:
|
||||
master_password = args.master_password
|
||||
encrypt = Encryption(cli,paths,args.amount_of_secret_holders, args.decryption_quota, master_password)
|
||||
if args.add_user_information is True:
|
||||
for user_id in encrypt.user_mapped_data:
|
||||
for label in ['name','phone','email','address','notes']:
|
||||
print("Enter attribut <<" + label + ">> for user <<" + user_id+ ">>:" )
|
||||
encrypt.addInformationToUser(user_id, label, str(input()))
|
||||
encrypt.compileData()
|
||||
if args.meta_data is True:
|
||||
print('Cleaning up encrypted meta data.')
|
||||
cleanup.cleanupMetaData(Paths.TYPE_ENCRYPTED)
|
||||
print("Create and encrypt meta data.")
|
||||
encrypt.encryptMetaData()
|
||||
if args.input_directory is not None:
|
||||
print("Encrypt main data.")
|
||||
encrypt.encryptMainData(args.input_directory)
|
||||
dirty_exit()
|
||||
except KeyboardInterrupt:
|
||||
print("Program interrupted by user.")
|
||||
clean_exit()
|
Reference in New Issue
Block a user