Compare commits

...

45 Commits

Author SHA1 Message Date
6f5c04ba11 Added Funding 2025-03-12 20:52:47 +01:00
a5ada012b6 Merge branch 'main' of github.com:kevinveenbirkenbach/splitted-secret 2025-03-12 11:14:39 +01:00
69d0592b05 Update README.md 2025-03-12 10:49:37 +01:00
5e4fe2c01a Update README.md 2025-03-04 21:27:09 +01:00
b939870c1d Create requirements.txt 2025-03-04 21:25:14 +01:00
44b2db0cbc Added funnier test tada for hack'n tell 2023-03-28 19:57:06 +02:00
960e14d59e Fixed error in documentation 2023-03-28 19:43:23 +02:00
62f51b449d Updated README.md 2022-12-15 09:33:30 +01:00
09bb6d1e31 Optimized cleanup for encryption 2022-12-14 00:31:46 +01:00
a0c17c9fd6 Refactored parameters to meta 2022-12-13 16:23:24 +01:00
b3bd3a0095 Merge branch 'main' of github.com:kevinveenbirkenbach/splitted-secret 2022-12-13 15:56:29 +01:00
11848ba222 Implemented escaping for passwords on bash level 2022-12-13 15:55:01 +01:00
a88ca6904a Update Readme.md 2022-12-12 13:12:44 +01:00
5ad38ab566 Solved variable naming bug 2022-12-12 13:09:03 +01:00
565c5d0961 Optimized decryption message 2022-12-12 11:45:29 +01:00
07ba9b2164 Added gitignore file 2022-12-11 23:55:39 +01:00
63f2d9fd18 Removed todos 2022-12-11 23:53:42 +01:00
cc943c8c12 Solved sh restrictions for accumulated file 2022-12-11 23:50:52 +01:00
42a5b93d67 Optimized code 2022-12-11 18:46:32 +01:00
591af85d82 Optimized usability 2022-12-11 16:47:07 +01:00
080b8d66e4 Implemented automatic user identification 2022-12-11 14:55:17 +01:00
260d7dfbb7 Updated Readme file 2022-12-11 13:55:55 +01:00
4caec4e1dc Removed debugging commands 2022-12-11 10:20:18 +01:00
4c3661de76 Solved decryption encryption bug 2022-12-11 10:19:35 +01:00
c85c108db5 Moved functions from Paths to Encryption 2022-12-10 22:27:28 +01:00
baefc461fa Changed paths to dependency injection 2022-12-10 22:19:57 +01:00
bfcda49660 Refactored class cli 2022-12-10 22:03:29 +01:00
c06d1d34d2 Implemented absolut paths 2022-12-10 21:48:24 +01:00
7b33c5420d Finished full encryption implementation 2022-12-10 21:20:26 +01:00
2b3e4932c2 In between commit implementation of main data encryption 2022-12-10 20:19:26 +01:00
a00550e0b6 Optimized decryption 2022-12-10 18:31:49 +01:00
7a7fddee81 Added decryption for master password 2022-12-10 17:30:27 +01:00
b54723448d Implemented further decription steps 2022-12-10 14:42:11 +01:00
47af457162 Implemented destructor for main 2022-12-10 13:22:09 +01:00
f182e3226a Continued implementation of decryption 2022-12-10 12:21:43 +01:00
e5806992ce Finished work for today 2022-12-10 00:00:42 +01:00
e4217afe64 optimized cli workflow 2022-12-09 23:43:28 +01:00
72765e280d Implemented loading of json file 2022-12-09 22:55:33 +01:00
0831ab3448 Added information to members 2022-12-09 22:37:29 +01:00
09f494804f Refactored Encryption class 2022-12-09 21:49:06 +01:00
284fec6c15 Cleaned code up 2022-12-09 20:54:33 +01:00
b3c31c0013 Implemented decryption of user file 2022-12-09 20:39:00 +01:00
10689122c0 Renamed classes 2022-12-09 20:13:48 +01:00
f2b2fd1b07 refactoring and implementation 2022-12-09 20:10:12 +01:00
4860282de1 Implemented cleanup for user 2022-12-09 18:16:31 +01:00
20 changed files with 658 additions and 211 deletions

7
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
github: kevinveenbirkenbach
patreon: kevinveenbirkenbach
buy_me_a_coffee: kevinveenbirkenbach
custom: https://s.veen.world/paypaldonate

6
.gitignore vendored
View File

@@ -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
View File

@@ -0,0 +1,95 @@
# Split Secret (sisec) 🔐
[![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-blue?logo=github)](https://github.com/sponsors/kevinveenbirkenbach) [![Patreon](https://img.shields.io/badge/Support-Patreon-orange?logo=patreon)](https://www.patreon.com/c/kevinveenbirkenbach) [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20Coffee-Funding-yellow?logo=buymeacoffee)](https://buymeacoffee.com/kevinveenbirkenbach) [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://s.veen.world/paypaldonate)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![GitHub stars](https://img.shields.io/github/stars/kevinveenbirkenbach/split-secret.svg?style=social)](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! 😊

View File

@@ -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
View File

@@ -0,0 +1,2 @@
*.gpg
*.txt

3
data/decrypted/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
main_data/*
*.json
*.txt

1
data/decrypted/group_files/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

1
data/decrypted/user_files/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.json

1
data/encrypted/group_files/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.gpg

1
data/encrypted/user_files/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.json.pgp

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
pacman -S gpg tar python pip python-pip
pip install numpy

View File

@@ -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/"

View File

@@ -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)

View File

@@ -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):

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

View 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()

View File

@@ -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
View 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);

View File

@@ -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()