dictature 0.9.2__py3-none-any.whl → 0.9.4__py3-none-any.whl
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.
- dictature/backend/directory.py +7 -1
- dictature/backend/misp.py +126 -0
- dictature/backend/mock.py +38 -0
- dictature/backend/sqlite.py +4 -1
- dictature/dictature.py +138 -9
- dictature/transformer/__init__.py +1 -0
- dictature/transformer/aes.py +11 -9
- dictature/transformer/hmac.py +28 -0
- dictature/transformer/mock.py +16 -0
- dictature/transformer/passthrough.py +7 -0
- dictature/transformer/pipeline.py +26 -0
- {dictature-0.9.2.dist-info → dictature-0.9.4.dist-info}/METADATA +8 -1
- dictature-0.9.4.dist-info/RECORD +18 -0
- dictature-0.9.2.dist-info/RECORD +0 -15
- {dictature-0.9.2.dist-info → dictature-0.9.4.dist-info}/LICENSE +0 -0
- {dictature-0.9.2.dist-info → dictature-0.9.4.dist-info}/WHEEL +0 -0
- {dictature-0.9.2.dist-info → dictature-0.9.4.dist-info}/top_level.txt +0 -0
dictature/backend/directory.py
CHANGED
@@ -9,6 +9,11 @@ from .mock import DictatureTableMock, DictatureBackendMock, Value, ValueMode
|
|
9
9
|
|
10
10
|
class DictatureBackendDirectory(DictatureBackendMock):
|
11
11
|
def __init__(self, directory: Union[Path, str], dir_prefix: str = 'db_') -> None:
|
12
|
+
"""
|
13
|
+
Create a new directory backend
|
14
|
+
:param directory: directory to store the data
|
15
|
+
:param dir_prefix: prefix for the directories of the tables
|
16
|
+
"""
|
12
17
|
if isinstance(directory, str):
|
13
18
|
directory = Path(directory)
|
14
19
|
self.__directory = directory
|
@@ -71,7 +76,8 @@ class DictatureTableDirectory(DictatureTableMock):
|
|
71
76
|
def _filename_encode(name: str, suffix: str = '.txt') -> str:
|
72
77
|
if name == sub(r'[^\w_. -]', '_', name):
|
73
78
|
return f"d_{name}{suffix}"
|
74
|
-
|
79
|
+
name = name.encode('utf-8').hex()
|
80
|
+
return f'e_{name}{suffix}'
|
75
81
|
|
76
82
|
@staticmethod
|
77
83
|
def _filename_decode(name: str, suffix: str = '.txt') -> str:
|
@@ -0,0 +1,126 @@
|
|
1
|
+
from json import dumps, loads
|
2
|
+
from re import sub
|
3
|
+
from typing import Iterable, Optional
|
4
|
+
|
5
|
+
from .mock import DictatureTableMock, DictatureBackendMock, Value, ValueMode
|
6
|
+
|
7
|
+
try:
|
8
|
+
from pymisp import PyMISP, MISPEvent, MISPAttribute
|
9
|
+
except ImportError as e:
|
10
|
+
raise ImportError("Please install the 'pymisp' package to use the 'DictatureBackendMISP' backend.") from e
|
11
|
+
|
12
|
+
|
13
|
+
class DictatureBackendMISP(DictatureBackendMock):
|
14
|
+
def __init__(self, misp: PyMISP, tag_name: str = 'storage:dictature', prefix: str = 'Dictature storage: ') -> None:
|
15
|
+
"""
|
16
|
+
Create a new MISP backend
|
17
|
+
:param misp: PyMISP instance
|
18
|
+
:param tag_name: tag name to use for the tables
|
19
|
+
:param prefix: prefix for the event names
|
20
|
+
"""
|
21
|
+
self.__misp = misp
|
22
|
+
self.__tag_name = tag_name
|
23
|
+
self.__prefix = prefix
|
24
|
+
|
25
|
+
def keys(self) -> Iterable[str]:
|
26
|
+
for event in self.__misp.search(tags=[self.__tag_name], pythonify=True):
|
27
|
+
name = event.info
|
28
|
+
if not name.startswith(self.__prefix):
|
29
|
+
continue
|
30
|
+
yield name[len(self.__prefix):]
|
31
|
+
|
32
|
+
def table(self, name: str) -> 'DictatureTableMock':
|
33
|
+
return DictatureTableMISP(self.__misp, self.__prefix + name, self.__tag_name)
|
34
|
+
|
35
|
+
|
36
|
+
class DictatureTableMISP(DictatureTableMock):
|
37
|
+
def __init__(self, misp: PyMISP, event_description: str, tag: str) -> None:
|
38
|
+
self.__misp = misp
|
39
|
+
self.__event_description = event_description
|
40
|
+
self.__tag = tag
|
41
|
+
self.__event: Optional[MISPEvent] = None
|
42
|
+
|
43
|
+
def keys(self) -> Iterable[str]:
|
44
|
+
for attribute in self.__event_attributes():
|
45
|
+
yield attribute.value
|
46
|
+
|
47
|
+
def drop(self) -> None:
|
48
|
+
self.__misp.delete_event(self.__get_event())
|
49
|
+
|
50
|
+
def create(self) -> None:
|
51
|
+
self.__get_event()
|
52
|
+
|
53
|
+
def set(self, item: str, value: Value) -> None:
|
54
|
+
save_as_json = value.mode != ValueMode.string.value or value.value.startswith('{')
|
55
|
+
save_data = dumps({'value': value.value, 'mode': value.mode}, indent=1) if save_as_json else value.value
|
56
|
+
|
57
|
+
for attribute in self.__event_attributes():
|
58
|
+
if attribute.value == item:
|
59
|
+
attribute.value = item
|
60
|
+
attribute.comment = save_data
|
61
|
+
self.__misp.update_attribute(attribute)
|
62
|
+
break
|
63
|
+
else:
|
64
|
+
attribute = MISPAttribute()
|
65
|
+
attribute.value = item
|
66
|
+
attribute.comment = save_data
|
67
|
+
attribute.type = 'comment'
|
68
|
+
attribute.to_ids = False
|
69
|
+
attribute.disable_correlation = True
|
70
|
+
self.__misp.add_attribute(self.__get_event(), attribute)
|
71
|
+
self.__get_event().attributes.append(attribute)
|
72
|
+
|
73
|
+
def get(self, item: str) -> Value:
|
74
|
+
for attribute in self.__event_attributes():
|
75
|
+
if attribute.value == item:
|
76
|
+
if attribute.comment.startswith('{'):
|
77
|
+
data = loads(attribute.comment)
|
78
|
+
return Value(data['value'], data['mode'])
|
79
|
+
return Value(attribute.comment, ValueMode.string.value)
|
80
|
+
raise KeyError(item)
|
81
|
+
|
82
|
+
def delete(self, item: str) -> None:
|
83
|
+
for attribute in self.__event_attributes():
|
84
|
+
if attribute.value == item:
|
85
|
+
# First update the attribute as deletion is not recognized immediately
|
86
|
+
attribute.type = 'other'
|
87
|
+
self.__misp.update_attribute(attribute)
|
88
|
+
self.__misp.delete_attribute(attribute)
|
89
|
+
break
|
90
|
+
|
91
|
+
def __get_event(self) -> MISPEvent:
|
92
|
+
if self.__event is None:
|
93
|
+
for event in self.__misp.search(tags=[self.__tag], eventinfo=self.__event_description, pythonify=True):
|
94
|
+
if event.info == self.__event_description:
|
95
|
+
self.__event = event
|
96
|
+
break
|
97
|
+
else:
|
98
|
+
event = MISPEvent()
|
99
|
+
event.info = self.__event_description
|
100
|
+
event.distribution = 0
|
101
|
+
event.threat_level_id = 4
|
102
|
+
event.analysis = 0
|
103
|
+
event.add_tag(self.__tag)
|
104
|
+
self.__misp.add_event(event)
|
105
|
+
self.__event = event
|
106
|
+
return self.__event
|
107
|
+
|
108
|
+
def __event_attributes(self) -> Iterable[MISPAttribute]:
|
109
|
+
for attribute in self.__get_event().attributes:
|
110
|
+
if attribute.type != 'comment' or (hasattr(attribute, 'deleted') and attribute.deleted):
|
111
|
+
continue
|
112
|
+
yield attribute
|
113
|
+
|
114
|
+
@staticmethod
|
115
|
+
def _record_encode(name: str, suffix: str = '.txt') -> str:
|
116
|
+
if name == sub(r'[^\w_. -]', '_', name):
|
117
|
+
return f"d_{name}{suffix}"
|
118
|
+
name = name.encode('utf-8').hex()
|
119
|
+
return f'e_{name}{suffix}'
|
120
|
+
|
121
|
+
@staticmethod
|
122
|
+
def _record_decode(name: str, suffix: str = '.txt') -> str:
|
123
|
+
encoded_name = name[2:-len(suffix) if suffix else len(name)]
|
124
|
+
if name.startswith('d_'):
|
125
|
+
return encoded_name
|
126
|
+
return bytes.fromhex(encoded_name).decode('utf-8')
|
dictature/backend/mock.py
CHANGED
@@ -15,27 +15,65 @@ class Value(NamedTuple):
|
|
15
15
|
|
16
16
|
class DictatureBackendMock:
|
17
17
|
def keys(self) -> Iterable[str]:
|
18
|
+
"""
|
19
|
+
Return all table names
|
20
|
+
:return: all table names
|
21
|
+
"""
|
18
22
|
raise NotImplementedError("This method should be implemented by the subclass")
|
19
23
|
|
20
24
|
def table(self, name: str) -> 'DictatureTableMock':
|
25
|
+
"""
|
26
|
+
Create a table object based on the name
|
27
|
+
:param name: name of the table
|
28
|
+
:return: table object
|
29
|
+
"""
|
21
30
|
raise NotImplementedError("This method should be implemented by the subclass")
|
22
31
|
|
23
32
|
|
24
33
|
class DictatureTableMock:
|
25
34
|
def keys(self) -> Iterable[str]:
|
35
|
+
"""
|
36
|
+
Return all keys in the table
|
37
|
+
:return: all keys in the table
|
38
|
+
"""
|
26
39
|
raise NotImplementedError("This method should be implemented by the subclass")
|
27
40
|
|
28
41
|
def drop(self) -> None:
|
42
|
+
"""
|
43
|
+
Delete the table
|
44
|
+
:return: None
|
45
|
+
"""
|
29
46
|
raise NotImplementedError("This method should be implemented by the subclass")
|
30
47
|
|
31
48
|
def create(self) -> None:
|
49
|
+
"""
|
50
|
+
Create the table in the backend
|
51
|
+
:return: None
|
52
|
+
"""
|
32
53
|
raise NotImplementedError("This method should be implemented by the subclass")
|
33
54
|
|
34
55
|
def set(self, item: str, value: Value) -> None:
|
56
|
+
"""
|
57
|
+
Set a value in the table
|
58
|
+
:param item: key to set
|
59
|
+
:param value: value to set
|
60
|
+
:return: None
|
61
|
+
"""
|
35
62
|
raise NotImplementedError("This method should be implemented by the subclass")
|
36
63
|
|
37
64
|
def get(self, item: str) -> Value:
|
65
|
+
"""
|
66
|
+
Get a value from the table
|
67
|
+
:param item: key to get
|
68
|
+
:return: value
|
69
|
+
:raises KeyError: if the key does not exist
|
70
|
+
"""
|
38
71
|
raise NotImplementedError("This method should be implemented by the subclass")
|
39
72
|
|
40
73
|
def delete(self, item: str) -> None:
|
74
|
+
"""
|
75
|
+
Delete a value from the table
|
76
|
+
:param item: key to delete
|
77
|
+
:return: None
|
78
|
+
"""
|
41
79
|
raise NotImplementedError("This method should be implemented by the subclass")
|
dictature/backend/sqlite.py
CHANGED
@@ -7,6 +7,10 @@ from .mock import DictatureBackendMock, DictatureTableMock, Value, ValueMode
|
|
7
7
|
|
8
8
|
class DictatureBackendSQLite(DictatureBackendMock):
|
9
9
|
def __init__(self, file: Union[str, Path]) -> None:
|
10
|
+
"""
|
11
|
+
Create a new SQLite backend
|
12
|
+
:param file: file to store the database
|
13
|
+
"""
|
10
14
|
if isinstance(file, str):
|
11
15
|
file = Path(file)
|
12
16
|
self.__file = file
|
@@ -33,7 +37,6 @@ class DictatureBackendSQLite(DictatureBackendMock):
|
|
33
37
|
self.__connection.close()
|
34
38
|
|
35
39
|
|
36
|
-
|
37
40
|
class DictatureTableSQLite(DictatureTableMock):
|
38
41
|
def __init__(self, parent: "DictatureBackendSQLite", name: str) -> None:
|
39
42
|
self.__parent = parent
|
dictature/dictature.py
CHANGED
@@ -15,30 +15,65 @@ class Dictature:
|
|
15
15
|
backend: DictatureBackendMock,
|
16
16
|
name_transformer: MockTransformer = PassthroughTransformer(),
|
17
17
|
value_transformer: MockTransformer = PassthroughTransformer(),
|
18
|
+
table_name_transformer: Optional[MockTransformer] = None,
|
18
19
|
) -> None:
|
20
|
+
"""
|
21
|
+
Create a new Dictature object
|
22
|
+
:param backend: backend to use
|
23
|
+
:param name_transformer: transformer to use for table and key names
|
24
|
+
:param value_transformer: transformer to use for values
|
25
|
+
:param table_name_transformer: transformer to use for table names, if None, name_transformer is used
|
26
|
+
"""
|
19
27
|
self.__backend = backend
|
20
28
|
self.__table_cache: Dict[str, "DictatureTable"] = {}
|
21
29
|
self.__name_transformer = name_transformer
|
22
30
|
self.__value_transformer = value_transformer
|
31
|
+
self.__table_name_transformer = table_name_transformer or name_transformer
|
32
|
+
self.__cache_size = 4096
|
23
33
|
|
24
34
|
def keys(self) -> Set[str]:
|
35
|
+
"""
|
36
|
+
Return all table names
|
37
|
+
:return: all table names
|
38
|
+
"""
|
25
39
|
return set(map(self.__name_transformer.backward, self.__backend.keys()))
|
26
40
|
|
27
41
|
def values(self) -> Iterator["DictatureTable"]:
|
42
|
+
"""
|
43
|
+
Return all tables
|
44
|
+
:return: all tables
|
45
|
+
"""
|
28
46
|
return map(lambda x: x[1], self.items())
|
29
47
|
|
30
48
|
def items(self) -> Iterator[Tuple[str, "DictatureTable"]]:
|
49
|
+
"""
|
50
|
+
Return all tables with their instances
|
51
|
+
:return: all tables with their instances
|
52
|
+
"""
|
31
53
|
for k in self.keys():
|
32
54
|
yield k, self[k]
|
33
55
|
|
34
56
|
def to_dict(self) -> Dict[str, Any]:
|
57
|
+
"""
|
58
|
+
Return all tables as a dictionary
|
59
|
+
:return: all tables as a dictionary
|
60
|
+
"""
|
35
61
|
return {k: v.to_dict() for k, v in self.items()}
|
36
62
|
|
37
63
|
def __str__(self):
|
64
|
+
"""
|
65
|
+
Return all tables as a string
|
66
|
+
:return: all tables as a string
|
67
|
+
"""
|
38
68
|
return str(self.to_dict())
|
39
69
|
|
40
70
|
def __getitem__(self, item: str) -> "DictatureTable":
|
41
|
-
|
71
|
+
"""
|
72
|
+
Get a table by name
|
73
|
+
:param item: name of the table
|
74
|
+
:return: table instance
|
75
|
+
"""
|
76
|
+
if len(self.__table_cache) > self.__cache_size:
|
42
77
|
del self.__table_cache[choice(list(self.__table_cache.keys()))]
|
43
78
|
if item not in self.__table_cache:
|
44
79
|
self.__table_cache[item] = DictatureTable(
|
@@ -50,12 +85,26 @@ class Dictature:
|
|
50
85
|
return self.__table_cache[item]
|
51
86
|
|
52
87
|
def __delitem__(self, key: str) -> None:
|
88
|
+
"""
|
89
|
+
Delete a table
|
90
|
+
:param key: name of the table
|
91
|
+
:return: None
|
92
|
+
"""
|
53
93
|
self[key].drop()
|
54
94
|
|
55
95
|
def __contains__(self, item: str) -> bool:
|
96
|
+
"""
|
97
|
+
Check if a table exists
|
98
|
+
:param item: name of the table
|
99
|
+
:return: True if the table exists, False otherwise
|
100
|
+
"""
|
56
101
|
return item in self.keys()
|
57
102
|
|
58
103
|
def __bool__(self) -> bool:
|
104
|
+
"""
|
105
|
+
Check if there are any tables
|
106
|
+
:return: True if there are tables, False otherwise
|
107
|
+
"""
|
59
108
|
return not not self.keys()
|
60
109
|
|
61
110
|
|
@@ -67,6 +116,13 @@ class DictatureTable:
|
|
67
116
|
name_transformer: MockTransformer = PassthroughTransformer(),
|
68
117
|
value_transformer: MockTransformer = PassthroughTransformer()
|
69
118
|
):
|
119
|
+
"""
|
120
|
+
Create a new DictatureTable object
|
121
|
+
:param backend: backend to use
|
122
|
+
:param table_name: name of the table
|
123
|
+
:param name_transformer: transformer to use for key names
|
124
|
+
:param value_transformer: transformer to use for values
|
125
|
+
"""
|
70
126
|
self.__backend = backend
|
71
127
|
self.__name_transformer = name_transformer
|
72
128
|
self.__value_transformer = value_transformer
|
@@ -74,51 +130,96 @@ class DictatureTable:
|
|
74
130
|
self.__table_created = False
|
75
131
|
|
76
132
|
def get(self, item: str, default: Optional[Any] = None) -> Any:
|
133
|
+
"""
|
134
|
+
Get a value from the table
|
135
|
+
:param item: key to get
|
136
|
+
:param default: default value to return if the key does not exist
|
137
|
+
:return: value or default
|
138
|
+
"""
|
77
139
|
try:
|
78
140
|
return self[item]
|
79
141
|
except KeyError:
|
80
142
|
return default
|
81
143
|
|
82
144
|
def key_exists(self, item: str) -> bool:
|
145
|
+
"""
|
146
|
+
Check if a key exists
|
147
|
+
:param item: key to check
|
148
|
+
:return: True if the key exists, False otherwise
|
149
|
+
"""
|
83
150
|
self.__create_table()
|
84
151
|
return item in self.keys()
|
85
152
|
|
86
153
|
def keys(self) -> Set[str]:
|
154
|
+
"""
|
155
|
+
Return all keys in the table
|
156
|
+
:return: all keys in the table
|
157
|
+
"""
|
87
158
|
self.__create_table()
|
88
159
|
return set(map(self.__name_transformer.backward, self.__table.keys()))
|
89
160
|
|
90
161
|
def values(self) -> Iterator[Any]:
|
162
|
+
"""
|
163
|
+
Return all values in the table
|
164
|
+
:return: all values in the table
|
165
|
+
"""
|
91
166
|
return map(lambda x: x[1], self.items())
|
92
167
|
|
93
168
|
def items(self) -> Iterator[Tuple[str, Any]]:
|
169
|
+
"""
|
170
|
+
Return all items in the table
|
171
|
+
:return: all items in the table
|
172
|
+
"""
|
94
173
|
for k in self.keys():
|
95
174
|
yield k, self[k]
|
96
175
|
|
97
176
|
def drop(self) -> None:
|
177
|
+
"""
|
178
|
+
Delete the table
|
179
|
+
:return: None
|
180
|
+
"""
|
98
181
|
self.__create_table()
|
99
182
|
self.__table.drop()
|
100
183
|
|
101
184
|
def to_dict(self) -> Dict[str, Any]:
|
185
|
+
"""
|
186
|
+
Return all items as a dictionary
|
187
|
+
:return: all items as a dictionary
|
188
|
+
"""
|
102
189
|
return {k: v for k, v in self.items()}
|
103
190
|
|
104
191
|
def __str__(self):
|
192
|
+
"""
|
193
|
+
Return all items as a string
|
194
|
+
:return: all items as a string
|
195
|
+
"""
|
105
196
|
return str(self.to_dict())
|
106
197
|
|
107
198
|
def __getitem__(self, item: str) -> Any:
|
199
|
+
"""
|
200
|
+
Get a value from the table
|
201
|
+
:param item: key to get
|
202
|
+
:return: value
|
203
|
+
"""
|
108
204
|
self.__create_table()
|
109
205
|
saved_value = self.__table.get(self.__item_key(item))
|
110
206
|
mode = ValueMode(saved_value.mode)
|
111
207
|
value = self.__value_transformer.backward(saved_value.value)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
return pickle.loads(decompress(b64decode(value.encode('ascii'))))
|
208
|
+
if mode == ValueMode.string:
|
209
|
+
return value
|
210
|
+
elif mode == ValueMode.json:
|
211
|
+
return json.loads(value)
|
212
|
+
elif mode == ValueMode.pickle:
|
213
|
+
return pickle.loads(decompress(b64decode(value.encode('ascii'))))
|
119
214
|
raise ValueError(f"Unknown mode '{mode}'")
|
120
215
|
|
121
216
|
def __setitem__(self, key: str, value: Any) -> None:
|
217
|
+
"""
|
218
|
+
Set a value in the table
|
219
|
+
:param key: key to set
|
220
|
+
:param value: value to set
|
221
|
+
:return: None
|
222
|
+
"""
|
122
223
|
self.__create_table()
|
123
224
|
value_mode = ValueMode.string
|
124
225
|
|
@@ -135,21 +236,44 @@ class DictatureTable:
|
|
135
236
|
self.__table.set(key, Value(value=value, mode=value_mode.value))
|
136
237
|
|
137
238
|
def __delitem__(self, key: str) -> None:
|
138
|
-
|
239
|
+
"""
|
240
|
+
Delete a key from the table
|
241
|
+
:param key: key to delete
|
242
|
+
:return: None
|
243
|
+
"""
|
244
|
+
self.__table.delete(self.__item_key(key))
|
139
245
|
|
140
246
|
def __contains__(self, item: str):
|
247
|
+
"""
|
248
|
+
Check if a key exists
|
249
|
+
:param item: key to check
|
250
|
+
:return: True if the key exists, False otherwise
|
251
|
+
"""
|
141
252
|
return item in self.keys()
|
142
253
|
|
143
254
|
def __bool__(self) -> bool:
|
255
|
+
"""
|
256
|
+
Check if there are any items in the table
|
257
|
+
:return: True if there are items, False otherwise
|
258
|
+
"""
|
144
259
|
return not not self.keys()
|
145
260
|
|
146
261
|
def __create_table(self) -> None:
|
262
|
+
"""
|
263
|
+
Create the table if it does not exist
|
264
|
+
:return: None
|
265
|
+
"""
|
147
266
|
if self.__table_created:
|
148
267
|
return
|
149
268
|
self.__table.create()
|
150
269
|
self.__table_created = True
|
151
270
|
|
152
271
|
def __item_key(self, item: str) -> str:
|
272
|
+
"""
|
273
|
+
Transform the key for storage
|
274
|
+
:param item: key to transform
|
275
|
+
:return: transformed key
|
276
|
+
"""
|
153
277
|
if not self.__name_transformer.static:
|
154
278
|
for key in self.__table.keys():
|
155
279
|
if self.__name_transformer.backward(key) == item:
|
@@ -157,6 +281,11 @@ class DictatureTable:
|
|
157
281
|
return self.__name_transformer.forward(item)
|
158
282
|
|
159
283
|
def __table_key(self, table_name: str) -> str:
|
284
|
+
"""
|
285
|
+
Transform the table name for storage
|
286
|
+
:param table_name: table name to transform
|
287
|
+
:return: transformed table name
|
288
|
+
"""
|
160
289
|
if not self.__name_transformer.static:
|
161
290
|
for key in self.__backend.keys():
|
162
291
|
if self.__name_transformer.backward(key) == table_name:
|
dictature/transformer/aes.py
CHANGED
@@ -10,12 +10,18 @@ from .mock import MockTransformer
|
|
10
10
|
|
11
11
|
class AESTransformer(MockTransformer):
|
12
12
|
def __init__(self, passphrase: str, static_names_mode: bool, salt: str = 'dictature') -> None:
|
13
|
+
"""
|
14
|
+
Create a new AES transformer
|
15
|
+
:param passphrase: secret passphrase to encrypt/decrypt the data
|
16
|
+
:param static_names_mode: if True, the transformer will use ECB mode instead of GCM (True decreases security, increases speed)
|
17
|
+
:param salt: salt to use for the key derivation
|
18
|
+
"""
|
13
19
|
self.__key = scrypt(passphrase, salt, 16, N=2 ** 14, r=8, p=1)
|
14
20
|
self.__mode = AES.MODE_GCM if not static_names_mode else AES.MODE_ECB
|
15
21
|
self.__static = static_names_mode
|
16
22
|
|
17
23
|
def forward(self, text: str) -> str:
|
18
|
-
cipher = self.__cipher
|
24
|
+
cipher = self.__cipher()
|
19
25
|
if self.__mode == AES.MODE_GCM:
|
20
26
|
ciphertext, tag = cipher.encrypt_and_digest(pad(text.encode('utf8'), AES.block_size))
|
21
27
|
return (cipher.nonce + tag + ciphertext).hex()
|
@@ -24,19 +30,15 @@ class AESTransformer(MockTransformer):
|
|
24
30
|
|
25
31
|
def backward(self, text: str) -> str:
|
26
32
|
data = bytes.fromhex(text)
|
27
|
-
cipher = self.__cipher
|
28
33
|
if self.__mode == AES.MODE_GCM:
|
29
34
|
nonce, tag, ciphertext = data[:16], data[16:32], data[32:]
|
30
|
-
|
31
|
-
cipher = AES.new(self.__key, self.__mode, nonce=nonce)
|
32
|
-
return unpad(cipher.decrypt_and_verify(ciphertext, tag), AES.block_size).decode('utf8')
|
35
|
+
return unpad(self.__cipher(nonce=nonce).decrypt_and_verify(ciphertext, tag), AES.block_size).decode('utf8')
|
33
36
|
else:
|
34
|
-
return unpad(
|
37
|
+
return unpad(self.__cipher().decrypt(data), AES.block_size).decode('utf8')
|
35
38
|
|
36
|
-
|
37
|
-
def __cipher(self) -> AES:
|
39
|
+
def __cipher(self, **kwargs) -> AES:
|
38
40
|
# noinspection PyTypeChecker
|
39
|
-
return AES.new(self.__key, self.__mode)
|
41
|
+
return AES.new(self.__key, self.__mode, **kwargs)
|
40
42
|
|
41
43
|
@property
|
42
44
|
def static(self) -> bool:
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import hmac
|
2
|
+
from hashlib import sha256
|
3
|
+
from .mock import MockTransformer
|
4
|
+
|
5
|
+
|
6
|
+
class HmacTransformer(MockTransformer):
|
7
|
+
def __init__(self, secret: str = 'dictature') -> None:
|
8
|
+
"""
|
9
|
+
Perform HMAC on the text.
|
10
|
+
:param secret: secret key to use for HMAC, if not provided works as a simple hash function
|
11
|
+
"""
|
12
|
+
self.__secret = secret
|
13
|
+
|
14
|
+
def forward(self, text: str) -> str:
|
15
|
+
return f"{self.__hmac(text)}-{text}"
|
16
|
+
|
17
|
+
def backward(self, text: str) -> str:
|
18
|
+
mac, text = text.split('-', 1)
|
19
|
+
if mac != self.__hmac(text):
|
20
|
+
raise ValueError('Invalid HMAC')
|
21
|
+
return text
|
22
|
+
|
23
|
+
def __hmac(self, text: str) -> str:
|
24
|
+
return hmac.new(self.__secret.encode('utf8'), text.encode('utf8'), sha256).hexdigest()
|
25
|
+
|
26
|
+
@property
|
27
|
+
def static(self) -> bool:
|
28
|
+
return True
|
dictature/transformer/mock.py
CHANGED
@@ -1,9 +1,25 @@
|
|
1
1
|
|
2
2
|
class MockTransformer:
|
3
3
|
def forward(self, text: str) -> str:
|
4
|
+
"""
|
5
|
+
Transform the text in some way to the data format in data storage
|
6
|
+
:param text: text to transform
|
7
|
+
:return: transformed text
|
8
|
+
"""
|
4
9
|
raise NotImplementedError("This method should be implemented by the child class")
|
10
|
+
|
5
11
|
def backward(self, text: str) -> str:
|
12
|
+
"""
|
13
|
+
Transform the data format in data storage to the text
|
14
|
+
:param text: text to transform
|
15
|
+
:return: original text
|
16
|
+
"""
|
6
17
|
raise NotImplementedError("This method should be implemented by the child class")
|
18
|
+
|
7
19
|
@property
|
8
20
|
def static(self) -> bool:
|
21
|
+
"""
|
22
|
+
Returns True only if when the forward transformation is applied to the same text, the result is always the same
|
23
|
+
:return: True if the transformation is static
|
24
|
+
"""
|
9
25
|
raise NotImplementedError("This method should be implemented by the child class")
|
@@ -1,10 +1,17 @@
|
|
1
1
|
from .mock import MockTransformer
|
2
2
|
|
3
|
+
|
3
4
|
class PassthroughTransformer(MockTransformer):
|
5
|
+
"""
|
6
|
+
Passthrough transformer, does not modify the text.
|
7
|
+
"""
|
8
|
+
|
4
9
|
def forward(self, text: str) -> str:
|
5
10
|
return text
|
11
|
+
|
6
12
|
def backward(self, text: str) -> str:
|
7
13
|
return text
|
14
|
+
|
8
15
|
@property
|
9
16
|
def static(self) -> bool:
|
10
17
|
return True
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from .mock import MockTransformer
|
4
|
+
|
5
|
+
|
6
|
+
class PipelineTransformer(MockTransformer):
|
7
|
+
def __init__(self, transformers: List[MockTransformer]) -> None:
|
8
|
+
"""
|
9
|
+
Create a pipeline of transformers. The text is passed through each transformer in the order they are provided.
|
10
|
+
:param transformers: list of transformers to use
|
11
|
+
"""
|
12
|
+
self.__transformers = transformers
|
13
|
+
|
14
|
+
def forward(self, text: str) -> str:
|
15
|
+
for transformer in self.__transformers:
|
16
|
+
text = transformer.forward(text)
|
17
|
+
return text
|
18
|
+
|
19
|
+
def backward(self, text: str) -> str:
|
20
|
+
for transformer in reversed(self.__transformers):
|
21
|
+
text = transformer.backward(text)
|
22
|
+
return text
|
23
|
+
|
24
|
+
@property
|
25
|
+
def static(self) -> bool:
|
26
|
+
return all(t.static for t in self.__transformers)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dictature
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.4
|
4
4
|
Summary: dictature -- A generic wrapper around dict-like interface with mulitple backends
|
5
5
|
Author-email: Adam Hlavacek <git@adamhlavacek.com>
|
6
6
|
Project-URL: Homepage, https://github.com/esoadamo/dictature
|
@@ -53,6 +53,7 @@ del dictionary['test'] # drops whole table
|
|
53
53
|
Currently, the following backends are supported:
|
54
54
|
- `DictatureBackendDirectory`: stores the data in a directory as json files
|
55
55
|
- `DictatureBackendSQLite`: stores the data in a SQLite database
|
56
|
+
- `DictatureBackendMISP`: stores the data in a MISP instance
|
56
57
|
|
57
58
|
### Transformers
|
58
59
|
|
@@ -73,3 +74,9 @@ dictionary = Dictature(
|
|
73
74
|
value_transformer=value_transformer
|
74
75
|
)
|
75
76
|
```
|
77
|
+
|
78
|
+
Currently, the following transformers are supported:
|
79
|
+
- `AESTransformer`: encrypts/decrypts the data using AES
|
80
|
+
- `HmacTransformer`: signs the data using HMAC or performs hash integrity checks
|
81
|
+
- `PassthroughTransformer`: does nothing
|
82
|
+
- `PipelineTransformer`: chains multiple transformers
|
@@ -0,0 +1,18 @@
|
|
1
|
+
dictature/__init__.py,sha256=UCPJKHeyirRZ0pCYoyeat-rwXa8pDezOJ3UWCipDdyc,33
|
2
|
+
dictature/dictature.py,sha256=VM4RcXftQl2cFY8GxsRl7h3K8tsH7urNoT7GyGsxGuo,9314
|
3
|
+
dictature/backend/__init__.py,sha256=d5s6QCJOUzFglVNg8Cqqx_8b61S-AOTGjEUIF6FS69U,149
|
4
|
+
dictature/backend/directory.py,sha256=Wf6dtllkqVA70DsnFoFzEHi_-RrTeOwqX5TFU-mYjrk,3451
|
5
|
+
dictature/backend/misp.py,sha256=ZT392TkAkoP1fB5ZXjArD1Vsm00hvvq08msCv6QXbh8,4971
|
6
|
+
dictature/backend/mock.py,sha256=Bllai6uZ1cRL8mKLbB3YQQo26mxTFepqzGu3FfOL1d4,2183
|
7
|
+
dictature/backend/sqlite.py,sha256=zyphYEeLY4eGuBCor16i80_-brdipMpXZ3_kONwErsE,5237
|
8
|
+
dictature/transformer/__init__.py,sha256=JIFJpXU6iB9hIUM8L7HL2o9Nqjm_YbMEuQBQC8ZJ6b4,124
|
9
|
+
dictature/transformer/aes.py,sha256=ZhC1dT9QpnziErkDLriWLgXDEFNGQW0KG4aqSN2AZpA,1926
|
10
|
+
dictature/transformer/hmac.py,sha256=vURsB0HlzRPn_Vkl7lGmZV9OKempQuds8AanmadDxIo,834
|
11
|
+
dictature/transformer/mock.py,sha256=7zu65ZqUV_AVRaPSzNd73cVMXixXt31SeuX9OKZxaJQ,948
|
12
|
+
dictature/transformer/passthrough.py,sha256=Pt3N6G_Qh6HJ_q75ETL5nfAwYHLB-SjkVwUwbbbMik8,344
|
13
|
+
dictature/transformer/pipeline.py,sha256=OaQaJeJ5NpICetJe08r8ontqstsXGuW8jDbKw1zxYs4,842
|
14
|
+
dictature-0.9.4.dist-info/LICENSE,sha256=n1U9DKr8sM5EY2QHcvxSGiKTDWUT8MyXsOC79w94MT0,1072
|
15
|
+
dictature-0.9.4.dist-info/METADATA,sha256=QklI5x9PnsuJZ-6jIcFskCDxSVveI3le5IJSIh_8E4Q,2826
|
16
|
+
dictature-0.9.4.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
17
|
+
dictature-0.9.4.dist-info/top_level.txt,sha256=-RO39WWCF44lqiXhSUcACVqbk6SkgReZTz7ZmHKH3-U,10
|
18
|
+
dictature-0.9.4.dist-info/RECORD,,
|
dictature-0.9.2.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
dictature/__init__.py,sha256=UCPJKHeyirRZ0pCYoyeat-rwXa8pDezOJ3UWCipDdyc,33
|
2
|
-
dictature/dictature.py,sha256=nfoCcsI6pcXVZ6IoJLRFH7g42OJrUSVZFMf9jqCX0BQ,5551
|
3
|
-
dictature/backend/__init__.py,sha256=d5s6QCJOUzFglVNg8Cqqx_8b61S-AOTGjEUIF6FS69U,149
|
4
|
-
dictature/backend/directory.py,sha256=u_AIJnwjytTqwdjSz7DPp5aU3qX4kJyNp8dkzupSn_0,3246
|
5
|
-
dictature/backend/mock.py,sha256=Qd7KSh-qM763Jc7biDf5xYFWdgDax30dUHh2gXWwTZE,1266
|
6
|
-
dictature/backend/sqlite.py,sha256=aExNxDtx1kiPrZn-jfCzbpV4alEXyGc6f12tuCJK1tk,5130
|
7
|
-
dictature/transformer/__init__.py,sha256=H3-ySHD-yZz9Zin2H9P619IdQiXh1e-yma669K4V_go,82
|
8
|
-
dictature/transformer/aes.py,sha256=I1Nhr3pKIZ5K4fb4D1m1q4plrRU7Yllqid-ysv78KNk,1703
|
9
|
-
dictature/transformer/mock.py,sha256=osETvYZjlgos0trJy0YvXcmtNy0L6x2h2099t1aHMFc,421
|
10
|
-
dictature/transformer/passthrough.py,sha256=63hZCPQMUJa-G6ZKdv_xt2fMiMZpmPoL84PY5eb2ueE,269
|
11
|
-
dictature-0.9.2.dist-info/LICENSE,sha256=n1U9DKr8sM5EY2QHcvxSGiKTDWUT8MyXsOC79w94MT0,1072
|
12
|
-
dictature-0.9.2.dist-info/METADATA,sha256=ScBeTxvFHcr1EyGz5w2kFg6t-aXPBTDt0pkbfz-mhhY,2478
|
13
|
-
dictature-0.9.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
14
|
-
dictature-0.9.2.dist-info/top_level.txt,sha256=-RO39WWCF44lqiXhSUcACVqbk6SkgReZTz7ZmHKH3-U,10
|
15
|
-
dictature-0.9.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|