Compare commits

..

15 Commits

9 changed files with 215 additions and 162 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

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
__pycache__ __pycache__
testcases.txt 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,73 +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
### system
This software is developed for and on an [Arch Linux](https://archlinux.org/) system.
### setup
Before executing the script it may be necessary to install the following software packages:
```bash
pacman -S gpg tar python pip python-pip
pip install numpy
```
## commands
## cleanup data
### delete all data
To delete all data execute:
```bash
python scripts/main.py --mode cleanup
```
### delete decrypted data
To delete all encrypted data execute:
```bash
python scripts/main.py --mode cleanup --file-types decrypted
```
### delete all encrypted data
To delete all encrypted data execute:
```bash
python scripts/main.py --mode cleanup --file-types encrypted
```
## decrypt
### decrypt automatic
To decrypt the data execute:
```bash
python scripts/main.py --mode decrypt
```
### decrypt defined user
To decrypt the data for a defined user execute:
```bash
python scripts/main.py --mode decrypt --user "<<user_id>>"
```
## encrypt
### encrypt main data
```bash
python scripts/main.py --secret-holders-amount "<<amount>>" --quota "<<quota>>" --mode encrypt --master-password "<<master_password>>" --input-directory "<<input_directory>>"
```
### encrypt master password
To encrypt the master-password file and to create the neccessary encrypted meta data execute:
```bash
python scripts/main.py --secret-holders-amount "<<amount>>" --quota "<<quota>>" --mode encrypt --add-user-information --master-password "<<master_password>>" --create-meta-data
```

2
requirements.txt Normal file
View File

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

View File

@@ -5,12 +5,15 @@ class Cleanup():
self.cli = cli self.cli = cli
self.paths = paths self.paths = paths
def getAllFilePaths(self,file_type): def getMetaFilePaths(self,file_type):
all_file_paths = [ return [
self.paths.getGroupFilesFolderPath(file_type), self.paths.getGroupFilesFolderPath(file_type),
self.paths.getUserFilesPath(file_type), self.paths.getUserFilesPath(file_type),
self.paths.getAccumulatedFilePath(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: if file_type == Paths.TYPE_DECRYPTED:
all_file_paths.append(self.paths.getDecryptedMainDataStandartFolder()) all_file_paths.append(self.paths.getDecryptedMainDataStandartFolder())
return all_file_paths return all_file_paths
@@ -32,6 +35,10 @@ class Cleanup():
pass pass
self.cleanupFiles(Paths.TYPE_DECRYPTED) 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): def deleteAll(self):
self.cleanupFiles(Paths.TYPE_ENCRYPTED) self.cleanupFiles(Paths.TYPE_ENCRYPTED)
self.cleanupFiles(Paths.TYPE_DECRYPTED) self.cleanupFiles(Paths.TYPE_DECRYPTED)

View File

@@ -1,6 +1,7 @@
import json import json
import os import os
from pathlib import Path from pathlib import Path
import shlex
class AutomaticIdentificationImpossibleException(Exception): class AutomaticIdentificationImpossibleException(Exception):
pass pass
@@ -35,12 +36,18 @@ class Decryption():
self.initializeNeededDecryptersAmount() self.initializeNeededDecryptersAmount()
self.initializeValidDecrypterIds() self.initializeValidDecrypterIds()
def getEscapedMasterPassword(self):
return shlex.quote(self.master_password)
def initializeGroupDataEncryption(self): def initializeGroupDataEncryption(self):
self.group_name = self.getDecryptersGroupName() self.group_name = self.getDecryptersGroupName()
self.encrypted_group_file_path = self.paths.getGroupFilePath(self.group_name, self.paths.TYPE_DECRYPTED) self.encrypted_group_file_path = self.paths.getGroupFilePath(self.group_name, self.paths.TYPE_DECRYPTED)
self.decryptGroupFile() self.decryptGroupFile()
self.master_password = self.loadTxtFile(self.encrypted_group_file_path).strip() self.master_password = self.loadTxtFile(self.encrypted_group_file_path).strip()
def getMasterPassword(self):
return self.master_password
def initializeNeededDecryptersAmount(self): def initializeNeededDecryptersAmount(self):
self.needed_decrypters_amount = len(str(list(self.user_data['groups'].keys())[0])) self.needed_decrypters_amount = len(str(list(self.user_data['groups'].keys())[0]))
@@ -70,9 +77,6 @@ class Decryption():
shared_password += str(self.password_parts[password_share_index]) shared_password += str(self.password_parts[password_share_index])
return shared_password return shared_password
def getMasterPassword(self):
return self.master_password
def addDecrypterId(self,decrypter_id): def addDecrypterId(self,decrypter_id):
decrypter_id = int(decrypter_id) decrypter_id = int(decrypter_id)
if decrypter_id not in self.valid_decrypter_ids: if decrypter_id not in self.valid_decrypter_ids:
@@ -114,7 +118,7 @@ class Decryption():
return data return data
def decryptFile(self,password,input_file_path,output_file_path): def decryptFile(self,password,input_file_path,output_file_path):
self.cli.executeCommand('gpg --batch --passphrase "'+ password + '" -o "' + output_file_path +'" "'+ input_file_path+'"') self.cli.executeCommand('gpg --batch --passphrase '+ shlex.quote(password) + ' -o "' + output_file_path +'" "'+ input_file_path+'"')
def decryptUserFile(self): def decryptUserFile(self):
input_file_path = self.paths.getUserFilePath(self.user_id,self.paths.TYPE_ENCRYPTED) input_file_path = self.paths.getUserFilePath(self.user_id,self.paths.TYPE_ENCRYPTED)
@@ -130,4 +134,4 @@ class Decryption():
self.decryptFile(self.user_password, input_file_path, output_file_path) self.decryptFile(self.user_password, input_file_path, output_file_path)
def decryptMainData(self): def decryptMainData(self):
self.cli.executeCommand('gpg --batch --passphrase "' + self.getMasterPassword() + '" -d "' + self.paths.getEncryptedMainDataFile() + '" | tar --one-top-level="' + self.paths.getDecryptedMainDataStandartFolder() + '" -xvzf -') self.cli.executeCommand('gpg --batch --passphrase ' + shlex.quote(self.getMasterPassword()) + ' -d "' + self.paths.getEncryptedMainDataFile() + '" | tar --one-top-level="' + self.paths.getDecryptedMainDataStandartFolder() + '" -xvzf -')

View File

@@ -5,6 +5,7 @@ import numpy
import re import re
import json import json
from .Paths import Paths from .Paths import Paths
import shlex
class Encryption(): class Encryption():
@@ -104,7 +105,7 @@ class Encryption():
index += 1 index += 1
def encryptStringToFile(self,text,output_file,password): def encryptStringToFile(self,text,output_file,password):
self.cli.executeCommand('echo \'' + text + '\' | gpg --symmetric --armor --batch --passphrase "' + password + '" -o "' + output_file + '"') self.cli.executeCommand('echo ' + shlex.quote(text) + ' | gpg --symmetric --armor --batch --passphrase ' + shlex.quote(password) + ' -o "' + output_file + '"')
def encryptGroupFiles(self): def encryptGroupFiles(self):
for password_group_index_int in self.group_mapped_data: for password_group_index_int in self.group_mapped_data:
@@ -125,7 +126,7 @@ class Encryption():
See: https://stackoverflow.com/questions/30650841/why-am-i-getting-errno-7-argument-list-too-long-and-oserror-errno-24-too-ma 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): def encryptFileToFile(self,input_file,output_file,password):
self.cli.executeCommand('cat \'' + input_file + '\' | gpg --symmetric --armor --batch --passphrase "' + password + '" -o "' + output_file + '"') self.cli.executeCommand('cat \'' + input_file + '\' | gpg --symmetric --armor --batch --passphrase ' + shlex.quote(password) + ' -o "' + output_file + '"')
def deleteDecryptedAccumulatedFile(self): def deleteDecryptedAccumulatedFile(self):
self.cli.executeCommand('rm ' + self.paths.getAccumulatedFilePath(Paths.TYPE_DECRYPTED)) self.cli.executeCommand('rm ' + self.paths.getAccumulatedFilePath(Paths.TYPE_DECRYPTED))
@@ -144,7 +145,7 @@ class Encryption():
self.deleteDecryptedAccumulatedFile() self.deleteDecryptedAccumulatedFile()
def encryptMainData(self,input_directory): def encryptMainData(self,input_directory):
self.cli.executeCommand('tar -C"' + input_directory + '" -cvzf - ./ | gpg -c --batch --passphrase "' + self.master_password +'" > "' + self.paths.getEncryptedMainDataFile() + '"') self.cli.executeCommand('tar -C"' + input_directory + '" -cvzf - ./ | gpg -c --batch --passphrase ' + shlex.quote(self.master_password) + ' > "' + self.paths.getEncryptedMainDataFile() + '"')
def encryptMetaData(self): def encryptMetaData(self):
self.encryptUserFile() self.encryptUserFile()

View File

@@ -40,11 +40,13 @@ try:
parser.add_argument('--user',type=int, dest='user',choices=Encryption.getSecretHoldersRange(),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('--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('--input-directory',type=str,dest='input_directory',required=False, help="The directory from which the data should be encrypted.")
parser.add_argument('--create-meta-data',type=bool, dest='create_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.") 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() args = parser.parse_args()
print("Application started.") print("Application started.")
print("To leave the appplication use the key kombination: <<Ctr>> + <<Alt>> + <<C>>") 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) print("Selected Mode: " + args.mode)
if args.mode == 'cleanup': if args.mode == 'cleanup':
@@ -63,85 +65,91 @@ try:
if args.mode == 'decrypt': if args.mode == 'decrypt':
decrypt = Decryption(cli,paths) decrypt = Decryption(cli,paths)
if args.master_password is None: if args.meta_data is True:
if args.user is None: if args.master_password is None:
try: print("Enter the master password:")
print("Attempt to identify user.") master_password = getpass()
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: else:
decrypt.initializeUser(args.user) master_password = args.master_password
if args.user_password is None: decrypt = Decryption(cli,paths)
while True: print("Decrypting accumulated data.")
print("Enter the user password:") decrypt.setUserPassword(master_password)
decrypt.setUserPassword(getpass()) decrypt.decryptAccumulatedFile()
print("Decrypting User File...") dirty_exit()
try: if args.user is None:
decrypt.initializeUserDataDecryption(); try:
break; print("Attempt to identify user.")
except Exception as error: user_id = decrypt.identifyUser()
print("An error occured. Propably you typed in a wrong password :( The error is: " + str(error)) print("The user id is: " + user_id)
else: except:
decrypt.setUserPassword(args.user_password) 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...") print("Decrypting User File...")
try: try:
decrypt.initializeUserDataDecryption(); 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; break;
except: except Exception as error:
print("An unexpected error occured: \n" + traceback.format_exc()) print("An error occured. Propably you typed in a wrong password :( The error is: " + str(error))
print("Decrypting main data.") else:
decrypt.decryptMainData() decrypt.setUserPassword(args.user_password)
print("All data decrypted.") print("Decrypting User File...")
dirty_exit() try:
print("Decrypting accumulated data.") decrypt.initializeUserDataDecryption();
decrypt.setUserPassword(args.master_password) except Exception as error:
decrypt.decryptAccumulatedFile() 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() dirty_exit()
if args.mode == 'encrypt': if args.mode == 'encrypt':
@@ -157,7 +165,9 @@ try:
print("Enter attribut <<" + label + ">> for user <<" + user_id+ ">>:" ) print("Enter attribut <<" + label + ">> for user <<" + user_id+ ">>:" )
encrypt.addInformationToUser(user_id, label, str(input())) encrypt.addInformationToUser(user_id, label, str(input()))
encrypt.compileData() encrypt.compileData()
if args.create_meta_data is True: if args.meta_data is True:
print('Cleaning up encrypted meta data.')
cleanup.cleanupMetaData(Paths.TYPE_ENCRYPTED)
print("Create and encrypt meta data.") print("Create and encrypt meta data.")
encrypt.encryptMetaData() encrypt.encryptMetaData()
if args.input_directory is not None: if args.input_directory is not None: