fastcrypter 2.3.0__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.
@@ -0,0 +1,309 @@
1
+ """
2
+ Custom Encoder for Encrypter package.
3
+
4
+ This module provides custom encoding functionality that converts binary data
5
+ to text using only specified characters for steganography and obfuscation.
6
+ """
7
+
8
+ import secrets
9
+ from typing import Union, Optional, Tuple
10
+ from ..exceptions import ValidationError, ErrorCodes
11
+
12
+
13
+ class CustomEncoder:
14
+ """
15
+ Custom encoder that converts binary data to text using only specified characters.
16
+
17
+ This encoder is useful for creating encrypted output that looks like normal text
18
+ or follows specific character constraints.
19
+ """
20
+
21
+ # Default character set (can be customized)
22
+ DEFAULT_CHARSET = "abcdef98Xvbvii"
23
+
24
+ def __init__(self, charset: str = DEFAULT_CHARSET, padding_char: Optional[str] = None):
25
+ """
26
+ Initialize the CustomEncoder.
27
+
28
+ Args:
29
+ charset (str): Characters to use for encoding.
30
+ padding_char (str, optional): Character for padding. Uses last char if None.
31
+
32
+ Raises:
33
+ ValidationError: If charset is invalid.
34
+ """
35
+ if not charset or len(charset) < 2:
36
+ raise ValidationError(
37
+ "Charset must contain at least 2 characters",
38
+ ErrorCodes.INVALID_CONFIGURATION
39
+ )
40
+
41
+ # Remove duplicates while preserving order
42
+ seen = set()
43
+ self.charset = ''.join(char for char in charset if not (char in seen or seen.add(char)))
44
+
45
+ if len(self.charset) < 2:
46
+ raise ValidationError(
47
+ "Charset must contain at least 2 unique characters",
48
+ ErrorCodes.INVALID_CONFIGURATION
49
+ )
50
+
51
+ self.base = len(self.charset)
52
+ self.padding_char = padding_char or self.charset[-1]
53
+
54
+ # Create lookup tables for fast encoding/decoding
55
+ self.char_to_value = {char: i for i, char in enumerate(self.charset)}
56
+ self.value_to_char = {i: char for i, char in enumerate(self.charset)}
57
+
58
+ def encode(self, data: Union[bytes, bytearray]) -> str:
59
+ """
60
+ Encode binary data to custom character set.
61
+
62
+ Args:
63
+ data: Binary data to encode.
64
+
65
+ Returns:
66
+ str: Encoded string using custom charset.
67
+
68
+ Raises:
69
+ ValidationError: If data is invalid.
70
+ """
71
+ if not isinstance(data, (bytes, bytearray)):
72
+ raise ValidationError(
73
+ "Data must be bytes or bytearray",
74
+ ErrorCodes.INVALID_INPUT_FORMAT
75
+ )
76
+
77
+ if len(data) == 0:
78
+ return ""
79
+
80
+ # Convert bytes to big integer
81
+ number = int.from_bytes(data, byteorder='big')
82
+
83
+ if number == 0:
84
+ return self.charset[0]
85
+
86
+ # Convert to custom base
87
+ result = []
88
+ while number > 0:
89
+ result.append(self.value_to_char[number % self.base])
90
+ number //= self.base
91
+
92
+ # Reverse to get correct order
93
+ encoded = ''.join(reversed(result))
94
+
95
+ # Add length prefix to handle leading zeros
96
+ length_prefix = self._encode_length(len(data))
97
+
98
+ return length_prefix + encoded
99
+
100
+ def decode(self, encoded: str) -> bytes:
101
+ """
102
+ Decode custom encoded string back to binary data.
103
+
104
+ Args:
105
+ encoded: Encoded string to decode.
106
+
107
+ Returns:
108
+ bytes: Original binary data.
109
+
110
+ Raises:
111
+ ValidationError: If encoded string is invalid.
112
+ """
113
+ if not isinstance(encoded, str):
114
+ raise ValidationError(
115
+ "Encoded data must be string",
116
+ ErrorCodes.INVALID_INPUT_FORMAT
117
+ )
118
+
119
+ if len(encoded) == 0:
120
+ return b""
121
+
122
+ # Validate characters
123
+ for char in encoded:
124
+ if char not in self.char_to_value:
125
+ raise ValidationError(
126
+ f"Invalid character '{char}' in encoded data",
127
+ ErrorCodes.INVALID_INPUT_FORMAT
128
+ )
129
+
130
+ # Extract length prefix
131
+ original_length, data_start = self._decode_length(encoded)
132
+ encoded_data = encoded[data_start:]
133
+
134
+ if len(encoded_data) == 0:
135
+ return b'\x00' * original_length
136
+
137
+ # Convert from custom base to integer
138
+ number = 0
139
+ for char in encoded_data:
140
+ number = number * self.base + self.char_to_value[char]
141
+
142
+ # Convert to bytes with correct length
143
+ if number == 0:
144
+ return b'\x00' * original_length
145
+
146
+ # Calculate required bytes
147
+ byte_length = (number.bit_length() + 7) // 8
148
+ result = number.to_bytes(byte_length, byteorder='big')
149
+
150
+ # Pad with leading zeros if necessary
151
+ if len(result) < original_length:
152
+ result = b'\x00' * (original_length - len(result)) + result
153
+
154
+ return result
155
+
156
+ def _encode_length(self, length: int) -> str:
157
+ """Encode length as prefix using custom charset."""
158
+ if length == 0:
159
+ return self.charset[0] + self.padding_char
160
+
161
+ result = []
162
+ while length > 0:
163
+ result.append(self.value_to_char[length % self.base])
164
+ length //= self.base
165
+
166
+ # Add separator
167
+ return ''.join(reversed(result)) + self.padding_char
168
+
169
+ def _decode_length(self, encoded: str) -> Tuple[int, int]:
170
+ """Decode length prefix and return (length, data_start_index)."""
171
+ separator_pos = encoded.find(self.padding_char)
172
+ if separator_pos == -1:
173
+ raise ValidationError(
174
+ "Invalid encoded format: missing length separator",
175
+ ErrorCodes.INVALID_INPUT_FORMAT
176
+ )
177
+
178
+ length_part = encoded[:separator_pos]
179
+ if len(length_part) == 0:
180
+ return 0, separator_pos + 1
181
+
182
+ # Decode length
183
+ length = 0
184
+ for char in length_part:
185
+ length = length * self.base + self.char_to_value[char]
186
+
187
+ return length, separator_pos + 1
188
+
189
+ def encode_with_noise(self, data: Union[bytes, bytearray], noise_ratio: float = 0.1) -> str:
190
+ """
191
+ Encode data with random noise characters for obfuscation.
192
+
193
+ Args:
194
+ data: Binary data to encode.
195
+ noise_ratio: Ratio of noise characters to add (0.0 to 1.0).
196
+
197
+ Returns:
198
+ str: Encoded string with noise.
199
+ """
200
+ if not 0.0 <= noise_ratio <= 1.0:
201
+ raise ValidationError(
202
+ "Noise ratio must be between 0.0 and 1.0",
203
+ ErrorCodes.INVALID_CONFIGURATION
204
+ )
205
+
206
+ # Encode normally
207
+ encoded = self.encode(data)
208
+
209
+ if noise_ratio == 0.0:
210
+ return encoded
211
+
212
+ # Add noise characters
213
+ noise_count = int(len(encoded) * noise_ratio)
214
+ result = list(encoded)
215
+
216
+ for _ in range(noise_count):
217
+ # Insert random character at random position
218
+ pos = secrets.randbelow(len(result) + 1)
219
+ noise_char = secrets.choice(self.charset)
220
+ result.insert(pos, noise_char)
221
+
222
+ # Mark noise positions (simple approach - use pattern)
223
+ # In real implementation, you might want more sophisticated noise marking
224
+ return ''.join(result)
225
+
226
+ def create_steganographic_text(self, data: Union[bytes, bytearray],
227
+ template: str = "The quick brown fox jumps over the lazy dog") -> str:
228
+ """
229
+ Create steganographic text that hides data in character substitutions.
230
+
231
+ Args:
232
+ data: Binary data to hide.
233
+ template: Template text to use as base.
234
+
235
+ Returns:
236
+ str: Text with hidden data.
237
+ """
238
+ encoded = self.encode(data)
239
+
240
+ # Simple steganography: replace characters in template
241
+ result = list(template.lower())
242
+ encoded_pos = 0
243
+
244
+ for i, char in enumerate(result):
245
+ if char.isalpha() and encoded_pos < len(encoded):
246
+ # Map alphabet to our charset
247
+ if char in 'abcdef':
248
+ result[i] = encoded[encoded_pos]
249
+ encoded_pos += 1
250
+ elif char in 'ghijklmnop':
251
+ # Map to numbers in our charset
252
+ if '9' in self.charset and '8' in self.charset:
253
+ result[i] = '9' if (ord(char) % 2) else '8'
254
+ if encoded_pos < len(encoded):
255
+ encoded_pos += 1
256
+
257
+ return ''.join(result)
258
+
259
+ def get_charset_info(self) -> dict:
260
+ """
261
+ Get information about the current charset.
262
+
263
+ Returns:
264
+ dict: Charset information.
265
+ """
266
+ return {
267
+ 'charset': self.charset,
268
+ 'base': self.base,
269
+ 'padding_char': self.padding_char,
270
+ 'efficiency': f"{(8 * 8) / (len(self.charset).bit_length() * 8):.2%}",
271
+ 'characters': list(self.charset),
272
+ 'unique_count': len(self.charset)
273
+ }
274
+
275
+ def benchmark_encoding(self, data_size: int = 1024) -> dict:
276
+ """
277
+ Benchmark encoding performance.
278
+
279
+ Args:
280
+ data_size: Size of test data in bytes.
281
+
282
+ Returns:
283
+ dict: Benchmark results.
284
+ """
285
+ import time
286
+
287
+ # Generate test data
288
+ test_data = secrets.token_bytes(data_size)
289
+
290
+ # Benchmark encoding
291
+ start_time = time.time()
292
+ encoded = self.encode(test_data)
293
+ encode_time = time.time() - start_time
294
+
295
+ # Benchmark decoding
296
+ start_time = time.time()
297
+ decoded = self.decode(encoded)
298
+ decode_time = time.time() - start_time
299
+
300
+ return {
301
+ 'data_size': data_size,
302
+ 'encoded_size': len(encoded),
303
+ 'expansion_ratio': len(encoded) / data_size,
304
+ 'encode_time': encode_time,
305
+ 'decode_time': decode_time,
306
+ 'encode_speed_mbps': (data_size / (1024 * 1024)) / encode_time,
307
+ 'decode_speed_mbps': (data_size / (1024 * 1024)) / decode_time,
308
+ 'correctness': test_data == decoded
309
+ }