Skip to content

Commit 72c566b

Browse files
Add P2P file sharing algorithm in Python
1 parent 788d95b commit 72c566b

4 files changed

Lines changed: 327 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import zlib
2+
from cryptography.fernet import Fernet
3+
4+
SKIP_COMPRESSION_EXTENSIONS = {
5+
'.zip', '.gz', '.bz2', '.xz', '.rar', '.7z',
6+
'.jpg', '.jpeg', '.png', '.gif', '.webp',
7+
'.mp3', '.mp4', '.avi', '.mkv', '.mov',
8+
'.pdf'
9+
}
10+
11+
def generate_aes_key():
12+
return Fernet.generate_key()
13+
14+
def encrypt_chunk_with_aes(chunk: bytes, aes_key: bytes, file_extension: str) -> bytes:
15+
fernet = Fernet(aes_key)
16+
if file_extension.lower() not in SKIP_COMPRESSION_EXTENSIONS:
17+
try:
18+
chunk = zlib.compress(chunk)
19+
except Exception as e:
20+
print(f"\n[!] Compression failed: {e}")
21+
22+
return fernet.encrypt(chunk)
23+
24+
def decrypt_chunk_with_aes(chunk: bytes, aes_key: bytes, file_extension: str) -> bytes:
25+
fernet = Fernet(aes_key)
26+
try:
27+
decrypted_data = fernet.decrypt(chunk)
28+
except Exception as e:
29+
raise Exception(f"\n[!] AES decryption failed: {e}")
30+
31+
if file_extension.lower() not in SKIP_COMPRESSION_EXTENSIONS:
32+
try:
33+
decrypted_data = zlib.decompress(decrypted_data)
34+
except zlib.error:
35+
print("\n[!] Warning: Failed to decompress — file may not be compressed")
36+
37+
return decrypted_data
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from cryptography.hazmat.primitives import serialization, hashes
2+
from cryptography.hazmat.primitives.asymmetric import rsa, padding
3+
4+
def generate_rsa_key_pair(private_path: str, public_path: str):
5+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
6+
7+
with open(private_path, "wb") as f:
8+
9+
f.write(private_key.private_bytes(
10+
serialization.Encoding.PEM,
11+
serialization.PrivateFormat.TraditionalOpenSSL,
12+
serialization.NoEncryption()
13+
))
14+
15+
with open(public_path, "wb") as f:
16+
17+
f.write(private_key.public_key().public_bytes(
18+
serialization.Encoding.PEM,
19+
serialization.PublicFormat.SubjectPublicKeyInfo
20+
))
21+
22+
def encrypt_with_rsa_public_key(data: bytes, public_key_path: str) -> bytes:
23+
24+
with open(public_key_path, "rb") as f:
25+
public_key = serialization.load_pem_public_key(f.read())
26+
27+
if not isinstance(public_key, rsa.RSAPublicKey):
28+
raise TypeError("\n[!] The loaded public key is not an RSA key.")
29+
30+
return public_key.encrypt(
31+
data,
32+
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
33+
)
34+
35+
def decrypt_with_rsa_private_key(ciphertext: bytes, private_key_path: str) -> bytes:
36+
37+
with open(private_key_path, "rb") as f:
38+
private_key = serialization.load_pem_private_key(f.read(), password=None)
39+
40+
if not isinstance(private_key, rsa.RSAPrivateKey):
41+
raise TypeError("\n[!] The loaded private key is not an RSA key.")
42+
43+
return private_key.decrypt(
44+
ciphertext,
45+
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
46+
)
47+
48+
def is_valid_pem_key(file_path: str, is_private: bool = False) -> bool:
49+
50+
try:
51+
with open(file_path, "rb") as f:
52+
data = f.read()
53+
if not data:
54+
return False
55+
if is_private:
56+
serialization.load_pem_private_key(data, password=None)
57+
else:
58+
serialization.load_pem_public_key(data)
59+
return True
60+
61+
except Exception:
62+
return False
63+

peer2peer_file_sharing/peer.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"""
2+
P2P File Sharing Implementation
3+
Author: Nikhil Karoriya
4+
Description: This module implements a secure peer-to-peer file sharing algorithm
5+
using socket programming, AES for data encryption, RSA for key exchange and Zlib for data compression
6+
7+
Usage:
8+
python peer.py --listen-port 5001
9+
10+
You will be prompted for:
11+
12+
Send file (y/n)? y
13+
Enter file path to send: sample.txt
14+
Enter receiver's IP address: localhost (or receiver's IP address)
15+
Enter receiver's port: 6001
16+
"""
17+
18+
19+
import os
20+
import socket
21+
import threading
22+
import traceback
23+
from tqdm import tqdm
24+
import argparse
25+
from time import sleep
26+
from crypto.rsa_crypto import (
27+
decrypt_with_rsa_private_key,
28+
encrypt_with_rsa_public_key,
29+
generate_rsa_key_pair,
30+
is_valid_pem_key
31+
)
32+
from crypto.aes_crypto import generate_aes_key, encrypt_chunk_with_aes, decrypt_chunk_with_aes
33+
from utils.file_utils import ensure_dir, sha256_digest_stream, is_valid_ip, is_valid_port
34+
from utils.file_utils import CHUNK_SIZE
35+
36+
PRIVATE_KEY_PATH = 'keys/private_key.pem'
37+
PUBLIC_KEY_PATH = 'keys/public_keys.pem'
38+
RECEIVE_DIR = 'received_files'
39+
40+
ensure_dir(RECEIVE_DIR)
41+
ensure_dir("keys")
42+
43+
if not is_valid_pem_key(PRIVATE_KEY_PATH, is_private=True) or not is_valid_pem_key(PUBLIC_KEY_PATH, is_private=False):
44+
print("\n[!] RSA key missing or invalid. Regenerating...")
45+
generate_rsa_key_pair(PRIVATE_KEY_PATH, PUBLIC_KEY_PATH)
46+
print("\n[+] RSA key pair regenerated.")
47+
48+
parser = argparse.ArgumentParser()
49+
parser.add_argument("--listen-port", type=int, required=True)
50+
args = parser.parse_args()
51+
52+
LISTEN_PORT = args.listen_port
53+
54+
def peer_listener():
55+
try:
56+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
57+
server_socket.bind(('0.0.0.0', LISTEN_PORT))
58+
server_socket.listen(1)
59+
print(f"\n[+] Listening on port {LISTEN_PORT}...")
60+
61+
while True:
62+
client_socket, addr = server_socket.accept()
63+
print(f"\n\n[+] Incoming connection from {addr}\n")
64+
65+
try:
66+
key_size = int.from_bytes(client_socket.recv(4), 'big')
67+
encrypted_key = client_socket.recv(key_size)
68+
69+
name_len = int.from_bytes(client_socket.recv(4), 'big')
70+
file_name = client_socket.recv(name_len).decode()
71+
72+
extension = int.from_bytes(client_socket.recv(4), 'big')
73+
file_extension = client_socket.recv(extension).decode()
74+
75+
total_size = int.from_bytes(client_socket.recv(8), 'big')
76+
77+
aes_key = decrypt_with_rsa_private_key(encrypted_key, PRIVATE_KEY_PATH)
78+
79+
file_path = os.path.join(RECEIVE_DIR, file_name)
80+
81+
with open(file_path, 'wb') as f_out, tqdm(total=total_size, desc=f"[+] Receiving {file_name}", unit="B", unit_scale=True) as pbar:
82+
received_bytes = 0
83+
while received_bytes < total_size:
84+
rcv_chunk_size = int.from_bytes(client_socket.recv(4), 'big')
85+
encrypted_chunk = b""
86+
87+
while len(encrypted_chunk) < rcv_chunk_size:
88+
part = client_socket.recv(rcv_chunk_size - len(encrypted_chunk))
89+
if not part:
90+
raise Exception("\n[!] Connection lost during transfer.")
91+
encrypted_chunk += part
92+
93+
decrypted_chunk = decrypt_chunk_with_aes(encrypted_chunk, aes_key, file_extension)
94+
f_out.write(decrypted_chunk)
95+
received_bytes += len(decrypted_chunk)
96+
pbar.update(len(decrypted_chunk))
97+
98+
print(f"\n[+] File saved to {file_path}")
99+
file_hash = sha256_digest_stream(file_path)
100+
print(f"\n[+] SHA256 Hash: {file_hash}")
101+
102+
except Exception as e:
103+
print(f"\n[!] ERROR receiving file: {str(e)}\n{traceback.format_exc()}")
104+
finally:
105+
client_socket.close()
106+
107+
except Exception as e:
108+
print(f"\n[!] Server failed: {str(e)}\n{traceback.format_exc()}")
109+
110+
def send_file(file_path, peer_ip, peer_port):
111+
try:
112+
113+
file_name = os.path.basename(file_path)
114+
file_extension = os.path.splitext(file_name)[1].lower()
115+
116+
aes_key = generate_aes_key()
117+
encrypted_key = encrypt_with_rsa_public_key(aes_key, PUBLIC_KEY_PATH)
118+
file_size = os.path.getsize(file_path)
119+
120+
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
121+
client_socket.connect((peer_ip, peer_port))
122+
123+
client_socket.sendall(len(encrypted_key).to_bytes(4, 'big'))
124+
client_socket.sendall(encrypted_key)
125+
126+
client_socket.sendall(len(file_name.encode()).to_bytes(4, 'big'))
127+
client_socket.sendall(file_name.encode())
128+
129+
client_socket.sendall(len(file_extension.encode()).to_bytes(4, 'big'))
130+
client_socket.sendall(file_extension.encode())
131+
132+
client_socket.sendall(file_size.to_bytes(8, 'big'))
133+
134+
print()
135+
136+
with open(file_path, 'rb') as f, tqdm(total=file_size, desc=f"[+] Sending {file_name}", unit="B", unit_scale=True) as pbar:
137+
for chunk in iter(lambda: f.read(CHUNK_SIZE), b''):
138+
encrypted_chunk = encrypt_chunk_with_aes(chunk, aes_key, file_extension)
139+
client_socket.sendall(len(encrypted_chunk).to_bytes(4, 'big'))
140+
client_socket.sendall(encrypted_chunk)
141+
pbar.update(len(chunk))
142+
143+
print(f"\n[+] File '{file_name}' sent successfully to {peer_ip}:{peer_port}.")
144+
client_socket.close()
145+
146+
except Exception as e:
147+
print(f"\n[!] Failed to send file: {str(e)}\n{traceback.format_exc()}")
148+
149+
150+
if __name__ == '__main__':
151+
152+
try:
153+
threading.Thread(target=peer_listener, daemon=True).start()
154+
sleep(0.5)
155+
156+
while True:
157+
user_input = input("\nSend file [y], wait [w], or exit [e]? ").strip().lower()
158+
159+
if user_input == 'e':
160+
print("\n[INFO] Exiting...")
161+
break
162+
163+
elif user_input == 'y':
164+
file_path = input("\nEnter file path to send: ").strip()
165+
peer_ip = input("\nEnter receiver's IP address: ").strip()
166+
peer_port_input = input("\nEnter receiver's port: ").strip()
167+
168+
if not is_valid_port(peer_port_input) or not is_valid_ip(peer_ip):
169+
print("\n[!] Receiver IP or port not correct/specified.")
170+
continue
171+
172+
if not os.path.isfile(file_path):
173+
print("\n[!] File does not exist.")
174+
continue
175+
176+
peer_port = int(peer_port_input)
177+
send_file(file_path, peer_ip, peer_port)
178+
179+
elif user_input == 'w':
180+
print("\n[INFO] Waiting for incoming transfers...")
181+
182+
else:
183+
print("\n[!] Invalid input. Use 'y', 'w', or 'e'.")
184+
185+
except KeyboardInterrupt:
186+
print("\n[INFO] Exiting by keyboard interrupt.")
187+
188+
except Exception as e:
189+
print(f"\n[!] An error occurred: {str(e)}\n{traceback.format_exc()}")
190+
191+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
import hashlib
3+
import ipaddress
4+
5+
CHUNK_SIZE = 64 * 1024
6+
7+
def sha256_digest_stream(path: str) -> str:
8+
9+
hasher = hashlib.sha256()
10+
with open(path, 'rb') as f:
11+
for chunk in iter(lambda: f.read(CHUNK_SIZE), b''):
12+
hasher.update(chunk)
13+
14+
return hasher.hexdigest()
15+
16+
def ensure_dir(directory: str):
17+
if not os.path.exists(directory):
18+
os.makedirs(directory)
19+
20+
def is_valid_ip(ip_str):
21+
try:
22+
if ip_str.strip().lower() == "localhost":
23+
return True
24+
ipaddress.ip_address(ip_str)
25+
return True
26+
27+
except ValueError:
28+
return False
29+
30+
def is_valid_port(port_str):
31+
try:
32+
port = int(port_str)
33+
return 1 <= port <= 65535
34+
35+
except ValueError:
36+
return False

0 commit comments

Comments
 (0)