securefilehandler-abs 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- securefilehandler_abs-0.1.4/PKG-INFO +7 -0
- securefilehandler_abs-0.1.4/README.md +0 -0
- securefilehandler_abs-0.1.4/pyproject.toml +15 -0
- securefilehandler_abs-0.1.4/securefilehandler/__init__.py +110 -0
- securefilehandler_abs-0.1.4/securefilehandler/handlers/__init__.py +25 -0
- securefilehandler_abs-0.1.4/securefilehandler/handlers/localfilehandler.py +39 -0
- securefilehandler_abs-0.1.4/securefilehandler/handlers/virtualfilehandler.py +39 -0
- securefilehandler_abs-0.1.4/securefilehandler_abs.egg-info/PKG-INFO +7 -0
- securefilehandler_abs-0.1.4/securefilehandler_abs.egg-info/SOURCES.txt +10 -0
- securefilehandler_abs-0.1.4/securefilehandler_abs.egg-info/dependency_links.txt +1 -0
- securefilehandler_abs-0.1.4/securefilehandler_abs.egg-info/top_level.txt +1 -0
- securefilehandler_abs-0.1.4/setup.cfg +4 -0
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "securefilehandler-abs"
|
|
7
|
+
version = "0.1.4"
|
|
8
|
+
description = "Librería de manejo seguro de datos (criptografia) sobre archivos"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name="Specter" }]
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
|
|
13
|
+
[tool.setuptools.packages.find]
|
|
14
|
+
include = ["securefilehandler*"]
|
|
15
|
+
exclude = ["docs*"]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Library import
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any
|
|
4
|
+
import os
|
|
5
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
6
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
7
|
+
from cryptography.hazmat.primitives import hashes
|
|
8
|
+
from cryptography.hazmat.backends import default_backend
|
|
9
|
+
import handlers
|
|
10
|
+
from handlers import FileHandlerInterface
|
|
11
|
+
from handlers import *
|
|
12
|
+
|
|
13
|
+
# Classes definition
|
|
14
|
+
class SecureFileHandler(FileHandlerInterface):
|
|
15
|
+
# Class properties definition
|
|
16
|
+
MAGIC: bytes = b"SFILEv1"
|
|
17
|
+
SALT_SIZE: int = 16
|
|
18
|
+
NONCE_SIZE: int = 12
|
|
19
|
+
ITERATIONS: int = 200_000
|
|
20
|
+
|
|
21
|
+
def __init__(self,
|
|
22
|
+
file_handler: FileHandlerInterface,
|
|
23
|
+
password: str
|
|
24
|
+
) -> None:
|
|
25
|
+
# Instance properties assignment
|
|
26
|
+
self._file_handler = file_handler
|
|
27
|
+
self._password = password.encode("UTF-8")
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_opened(self) -> bool:
|
|
31
|
+
return self._file_handler.is_opened
|
|
32
|
+
|
|
33
|
+
# Private methods
|
|
34
|
+
def _derive_key(self, salt: bytes) -> bytes:
|
|
35
|
+
KDF = PBKDF2HMAC(
|
|
36
|
+
algorithm=hashes.SHA256(),
|
|
37
|
+
length=32, # AES-256
|
|
38
|
+
salt=salt,
|
|
39
|
+
iterations=self.ITERATIONS,
|
|
40
|
+
backend=default_backend()
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return KDF.derive(self._password)
|
|
44
|
+
|
|
45
|
+
# Public methods
|
|
46
|
+
def open(self):
|
|
47
|
+
self._file_handler.open()
|
|
48
|
+
|
|
49
|
+
def close(self):
|
|
50
|
+
self._file_handler.close()
|
|
51
|
+
|
|
52
|
+
def write(self, data: bytes):
|
|
53
|
+
if not self.is_opened:
|
|
54
|
+
raise RuntimeError("File not opened")
|
|
55
|
+
|
|
56
|
+
encrypted = self.encrypt(data)
|
|
57
|
+
self._file_handler.write(encrypted)
|
|
58
|
+
|
|
59
|
+
def append_write(self, data: bytes):
|
|
60
|
+
if not self.is_opened:
|
|
61
|
+
raise RuntimeError("File not opened")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
decrypted = self.read()
|
|
65
|
+
except:
|
|
66
|
+
decrypted = b""
|
|
67
|
+
|
|
68
|
+
new_encrypted_data = self.encrypt(decrypted + data)
|
|
69
|
+
self._file_handler.write(new_encrypted_data)
|
|
70
|
+
|
|
71
|
+
def read(self) -> bytes:
|
|
72
|
+
if not self.is_opened:
|
|
73
|
+
raise RuntimeError("File not opened")
|
|
74
|
+
|
|
75
|
+
encrypted = self._file_handler.read()
|
|
76
|
+
return self.decrypt(encrypted)
|
|
77
|
+
|
|
78
|
+
def encrypt(self, data: bytes) -> bytes:
|
|
79
|
+
salt = os.urandom(self.SALT_SIZE)
|
|
80
|
+
key = self._derive_key(salt)
|
|
81
|
+
|
|
82
|
+
aesgcm = AESGCM(key)
|
|
83
|
+
nonce = os.urandom(self.NONCE_SIZE)
|
|
84
|
+
|
|
85
|
+
ciphertext = aesgcm.encrypt(nonce, data, self.MAGIC)
|
|
86
|
+
|
|
87
|
+
return self.MAGIC + salt + nonce + ciphertext
|
|
88
|
+
|
|
89
|
+
def decrypt(self, data: bytes) -> bytes:
|
|
90
|
+
if not data.startswith(self.MAGIC):
|
|
91
|
+
raise ValueError("Invalid or corrupted file format")
|
|
92
|
+
|
|
93
|
+
min_size = len(self.MAGIC) + self.SALT_SIZE + self.NONCE_SIZE + 16 # tag mínimo
|
|
94
|
+
if len(data) < min_size:
|
|
95
|
+
raise ValueError("File too small or corrupted")
|
|
96
|
+
|
|
97
|
+
offset = len(self.MAGIC)
|
|
98
|
+
|
|
99
|
+
salt = data[offset:offset + self.SALT_SIZE]
|
|
100
|
+
offset += self.SALT_SIZE
|
|
101
|
+
|
|
102
|
+
nonce = data[offset:offset + self.NONCE_SIZE]
|
|
103
|
+
offset += self.NONCE_SIZE
|
|
104
|
+
|
|
105
|
+
ciphertext = data[offset:]
|
|
106
|
+
|
|
107
|
+
key = self._derive_key(salt)
|
|
108
|
+
aesgcm = AESGCM(key)
|
|
109
|
+
|
|
110
|
+
return aesgcm.decrypt(nonce, ciphertext, self.MAGIC)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Library import
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
# Classes definition
|
|
5
|
+
class FileHandlerInterface(ABC):
|
|
6
|
+
# Properties
|
|
7
|
+
@property
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def is_opened(self) -> bool: raise NotImplementedError
|
|
10
|
+
|
|
11
|
+
# Public methods
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def open(self) -> None: raise NotImplementedError
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def close(self) -> None: raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def write(self, data: bytes) -> None: raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def read(self) -> bytes: raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
from localfilehandler import *
|
|
25
|
+
from virtualfilehandler import *
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Library import
|
|
2
|
+
from typing import Optional, BinaryIO
|
|
3
|
+
from . import FileHandlerInterface
|
|
4
|
+
|
|
5
|
+
# Classes definition
|
|
6
|
+
class LocalFileHandler(FileHandlerInterface):
|
|
7
|
+
def __init__(self,
|
|
8
|
+
filepath: str,
|
|
9
|
+
mode: str
|
|
10
|
+
) -> None:
|
|
11
|
+
# Instance properties assignment
|
|
12
|
+
self._filepath = filepath
|
|
13
|
+
self._mode = mode
|
|
14
|
+
self._file_handler: Optional[BinaryIO] = None
|
|
15
|
+
|
|
16
|
+
# Properties
|
|
17
|
+
@property
|
|
18
|
+
def is_opened(self) -> bool:
|
|
19
|
+
return self._file_handler is not None and not self._file_handler.closed
|
|
20
|
+
|
|
21
|
+
# Public methods
|
|
22
|
+
def open(self):
|
|
23
|
+
self._file_handler = open(self._filepath, self._mode)
|
|
24
|
+
|
|
25
|
+
def close(self):
|
|
26
|
+
if self._file_handler and not self._file_handler.closed:
|
|
27
|
+
self._file_handler.close()
|
|
28
|
+
|
|
29
|
+
def write(self, data: bytes):
|
|
30
|
+
self._file_handler.seek(0)
|
|
31
|
+
self._file_handler.truncate(0)
|
|
32
|
+
|
|
33
|
+
self._file_handler.write(data)
|
|
34
|
+
self._file_handler.flush()
|
|
35
|
+
|
|
36
|
+
def read(self):
|
|
37
|
+
self._file_handler.seek(0)
|
|
38
|
+
data = self._file_handler.read()
|
|
39
|
+
return data
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Library import
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from . import FileHandlerInterface
|
|
5
|
+
|
|
6
|
+
class VirtualFileHandler(FileHandlerInterface):
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self._buffer: Optional[BytesIO] = None
|
|
9
|
+
|
|
10
|
+
# Properties
|
|
11
|
+
@property
|
|
12
|
+
def is_opened(self) -> bool:
|
|
13
|
+
return self._buffer is not None
|
|
14
|
+
|
|
15
|
+
# Public methods
|
|
16
|
+
def open(self):
|
|
17
|
+
if not self.is_opened:
|
|
18
|
+
self._buffer = BytesIO()
|
|
19
|
+
|
|
20
|
+
def close(self):
|
|
21
|
+
if self._buffer is not None:
|
|
22
|
+
self._buffer.close()
|
|
23
|
+
self._buffer = None
|
|
24
|
+
|
|
25
|
+
def write(self, data: bytes):
|
|
26
|
+
if not self.is_opened:
|
|
27
|
+
raise RuntimeError("File not opened")
|
|
28
|
+
|
|
29
|
+
# Sobrescritura completa
|
|
30
|
+
self._buffer.seek(0)
|
|
31
|
+
self._buffer.truncate(0)
|
|
32
|
+
self._buffer.write(data)
|
|
33
|
+
|
|
34
|
+
def read(self) -> bytes:
|
|
35
|
+
if not self.is_opened:
|
|
36
|
+
raise RuntimeError("File not opened")
|
|
37
|
+
|
|
38
|
+
self._buffer.seek(0)
|
|
39
|
+
return self._buffer.read()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
securefilehandler/__init__.py
|
|
4
|
+
securefilehandler/handlers/__init__.py
|
|
5
|
+
securefilehandler/handlers/localfilehandler.py
|
|
6
|
+
securefilehandler/handlers/virtualfilehandler.py
|
|
7
|
+
securefilehandler_abs.egg-info/PKG-INFO
|
|
8
|
+
securefilehandler_abs.egg-info/SOURCES.txt
|
|
9
|
+
securefilehandler_abs.egg-info/dependency_links.txt
|
|
10
|
+
securefilehandler_abs.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
securefilehandler
|