rtty-soda 0.3.2__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.
- rtty_soda/__init__.py +0 -0
- rtty_soda/archivers.py +59 -0
- rtty_soda/cli/__init__.py +6 -0
- rtty_soda/cli/cli_options.py +115 -0
- rtty_soda/cli/cli_reader.py +25 -0
- rtty_soda/cli/cli_writer.py +41 -0
- rtty_soda/cli/main.py +417 -0
- rtty_soda/cli/main.pyi +111 -0
- rtty_soda/cryptography/__init__.py +0 -0
- rtty_soda/cryptography/kdf.py +49 -0
- rtty_soda/cryptography/public.py +20 -0
- rtty_soda/cryptography/secret.py +20 -0
- rtty_soda/encoders/__init__.py +30 -0
- rtty_soda/encoders/base26_encoder.py +18 -0
- rtty_soda/encoders/base31_encoder.py +16 -0
- rtty_soda/encoders/base36_encoder.py +18 -0
- rtty_soda/encoders/base64_encoder.py +16 -0
- rtty_soda/encoders/base94_encoder.py +16 -0
- rtty_soda/encoders/encoder.py +11 -0
- rtty_soda/encoders/functions.py +56 -0
- rtty_soda/encoders/scsu.py +19 -0
- rtty_soda/encoders/scsu_stubs.pyi +7 -0
- rtty_soda/formatters.py +46 -0
- rtty_soda/interfaces.py +19 -0
- rtty_soda/py.typed +0 -0
- rtty_soda/services/__init__.py +14 -0
- rtty_soda/services/encoding_service.py +29 -0
- rtty_soda/services/encryption_service.py +137 -0
- rtty_soda/services/key_service.py +43 -0
- rtty_soda/services/service.py +52 -0
- rtty_soda-0.3.2.dist-info/METADATA +362 -0
- rtty_soda-0.3.2.dist-info/RECORD +34 -0
- rtty_soda-0.3.2.dist-info/WHEEL +4 -0
- rtty_soda-0.3.2.dist-info/entry_points.txt +3 -0
rtty_soda/cli/main.pyi
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from click import Command
|
|
4
|
+
|
|
5
|
+
cli: Command
|
|
6
|
+
|
|
7
|
+
def genkey_cmd(
|
|
8
|
+
encoding: str,
|
|
9
|
+
output_file: Path | None,
|
|
10
|
+
group_len: int,
|
|
11
|
+
line_len: int,
|
|
12
|
+
padding: int,
|
|
13
|
+
verbose: bool,
|
|
14
|
+
) -> None: ...
|
|
15
|
+
def pubkey_cmd(
|
|
16
|
+
private_key_file: Path,
|
|
17
|
+
encoding: str,
|
|
18
|
+
output_file: Path | None,
|
|
19
|
+
group_len: int,
|
|
20
|
+
line_len: int,
|
|
21
|
+
padding: int,
|
|
22
|
+
verbose: bool,
|
|
23
|
+
) -> None: ...
|
|
24
|
+
def kdf_cmd(
|
|
25
|
+
password_file: Path,
|
|
26
|
+
encoding: str,
|
|
27
|
+
profile: str,
|
|
28
|
+
output_file: Path | None,
|
|
29
|
+
group_len: int,
|
|
30
|
+
line_len: int,
|
|
31
|
+
padding: int,
|
|
32
|
+
verbose: bool,
|
|
33
|
+
) -> None: ...
|
|
34
|
+
def encrypt_public_cmd(
|
|
35
|
+
private_key_file: Path,
|
|
36
|
+
public_key_file: Path,
|
|
37
|
+
message_file: Path,
|
|
38
|
+
text: bool,
|
|
39
|
+
key_encoding: str,
|
|
40
|
+
data_encoding: str,
|
|
41
|
+
compression: str,
|
|
42
|
+
output_file: Path | None,
|
|
43
|
+
group_len: int,
|
|
44
|
+
line_len: int,
|
|
45
|
+
padding: int,
|
|
46
|
+
verbose: bool,
|
|
47
|
+
) -> None: ...
|
|
48
|
+
def encrypt_secret_cmd(
|
|
49
|
+
key_file: Path,
|
|
50
|
+
message_file: Path,
|
|
51
|
+
text: bool,
|
|
52
|
+
key_encoding: str,
|
|
53
|
+
data_encoding: str,
|
|
54
|
+
compression: str,
|
|
55
|
+
output_file: Path | None,
|
|
56
|
+
group_len: int,
|
|
57
|
+
line_len: int,
|
|
58
|
+
padding: int,
|
|
59
|
+
verbose: bool,
|
|
60
|
+
) -> None: ...
|
|
61
|
+
def encrypt_password_cmd(
|
|
62
|
+
password_file: Path,
|
|
63
|
+
message_file: Path,
|
|
64
|
+
text: bool,
|
|
65
|
+
kdf_profile: str,
|
|
66
|
+
data_encoding: str,
|
|
67
|
+
compression: str,
|
|
68
|
+
output_file: Path | None,
|
|
69
|
+
group_len: int,
|
|
70
|
+
line_len: int,
|
|
71
|
+
padding: int,
|
|
72
|
+
verbose: bool,
|
|
73
|
+
) -> None: ...
|
|
74
|
+
def decrypt_public_cmd(
|
|
75
|
+
private_key_file: Path,
|
|
76
|
+
public_key_file: Path,
|
|
77
|
+
message_file: Path,
|
|
78
|
+
text: bool,
|
|
79
|
+
key_encoding: str,
|
|
80
|
+
data_encoding: str,
|
|
81
|
+
compression: str,
|
|
82
|
+
output_file: Path | None,
|
|
83
|
+
) -> None: ...
|
|
84
|
+
def decrypt_secret_cmd(
|
|
85
|
+
key_file: Path,
|
|
86
|
+
message_file: Path,
|
|
87
|
+
text: bool,
|
|
88
|
+
key_encoding: str,
|
|
89
|
+
data_encoding: str,
|
|
90
|
+
compression: str,
|
|
91
|
+
output_file: Path | None,
|
|
92
|
+
) -> None: ...
|
|
93
|
+
def decrypt_password_cmd(
|
|
94
|
+
password_file: Path,
|
|
95
|
+
message_file: Path,
|
|
96
|
+
text: bool,
|
|
97
|
+
kdf_profile: str,
|
|
98
|
+
data_encoding: str,
|
|
99
|
+
compression: str,
|
|
100
|
+
output_file: Path | None,
|
|
101
|
+
) -> None: ...
|
|
102
|
+
def encode_cmd(
|
|
103
|
+
in_encoding: str,
|
|
104
|
+
out_encoding: str,
|
|
105
|
+
file: Path,
|
|
106
|
+
output_file: Path | None,
|
|
107
|
+
group_len: int,
|
|
108
|
+
line_len: int,
|
|
109
|
+
padding: int,
|
|
110
|
+
verbose: bool,
|
|
111
|
+
) -> None: ...
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from nacl.encoding import RawEncoder
|
|
2
|
+
from nacl.hash import blake2b
|
|
3
|
+
from nacl.public import PrivateKey
|
|
4
|
+
from nacl.pwhash import argon2id
|
|
5
|
+
from nacl.pwhash.argon2id import (
|
|
6
|
+
MEMLIMIT_INTERACTIVE,
|
|
7
|
+
MEMLIMIT_MODERATE,
|
|
8
|
+
MEMLIMIT_SENSITIVE,
|
|
9
|
+
OPSLIMIT_INTERACTIVE,
|
|
10
|
+
OPSLIMIT_MODERATE,
|
|
11
|
+
OPSLIMIT_SENSITIVE,
|
|
12
|
+
SALTBYTES,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = ["KDF_PROFILES", "KdfProfile", "hash_salt", "kdf"]
|
|
16
|
+
|
|
17
|
+
type KdfProfile = tuple[int, int]
|
|
18
|
+
|
|
19
|
+
KDF_PROFILES: dict[str, KdfProfile] = {
|
|
20
|
+
"interactive": (OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE),
|
|
21
|
+
"moderate": (OPSLIMIT_MODERATE, MEMLIMIT_MODERATE),
|
|
22
|
+
"sensitive": (OPSLIMIT_SENSITIVE, MEMLIMIT_SENSITIVE),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
SALT_MOD = b"""And here I solemnly protest I have no intention to vilify or asperse any
|
|
26
|
+
one; for though everything is copied from the book of nature, and scarce a character or
|
|
27
|
+
action produced which I have not taken from my own observations or experience; yet I
|
|
28
|
+
have used the utmost care to obscure the persons by such different circumstances,
|
|
29
|
+
degrees, and colors, that it will be impossible to guess at them with any degree of
|
|
30
|
+
certainty;
|
|
31
|
+
(c) Henry Fielding
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def hash_salt(salt: bytes) -> bytes:
|
|
36
|
+
return blake2b(data=salt, digest_size=SALTBYTES, encoder=RawEncoder)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def kdf(password: bytes, profile: KdfProfile) -> bytes:
|
|
40
|
+
salt = hash_salt(password + SALT_MOD)
|
|
41
|
+
ops, mem = profile
|
|
42
|
+
return argon2id.kdf(
|
|
43
|
+
size=PrivateKey.SIZE,
|
|
44
|
+
password=password,
|
|
45
|
+
salt=salt,
|
|
46
|
+
opslimit=ops,
|
|
47
|
+
memlimit=mem,
|
|
48
|
+
encoder=RawEncoder,
|
|
49
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from nacl.encoding import RawEncoder
|
|
4
|
+
from nacl.public import Box, PrivateKey, PublicKey
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from nacl.utils import EncryptedMessage
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = ["decrypt", "encrypt"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def encrypt(private: PrivateKey, public: PublicKey, data: bytes) -> EncryptedMessage:
|
|
14
|
+
box = Box(private_key=private, public_key=public)
|
|
15
|
+
return box.encrypt(plaintext=data, encoder=RawEncoder)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def decrypt(private: PrivateKey, public: PublicKey, data: bytes) -> bytes:
|
|
19
|
+
box = Box(private_key=private, public_key=public)
|
|
20
|
+
return box.decrypt(ciphertext=data, encoder=RawEncoder)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from nacl.encoding import RawEncoder
|
|
4
|
+
from nacl.secret import SecretBox
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from nacl.utils import EncryptedMessage
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = ["decrypt", "encrypt"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def encrypt(key: bytes, data: bytes) -> EncryptedMessage:
|
|
14
|
+
box = SecretBox(key=key, encoder=RawEncoder)
|
|
15
|
+
return box.encrypt(plaintext=data, encoder=RawEncoder)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def decrypt(key: bytes, data: bytes) -> bytes:
|
|
19
|
+
box = SecretBox(key=key, encoder=RawEncoder)
|
|
20
|
+
return box.decrypt(ciphertext=data, encoder=RawEncoder)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .base26_encoder import Base26Encoder
|
|
2
|
+
from .base31_encoder import Base31Encoder
|
|
3
|
+
from .base36_encoder import Base36Encoder
|
|
4
|
+
from .base64_encoder import Base64Encoder
|
|
5
|
+
from .base94_encoder import Base94Encoder
|
|
6
|
+
from .encoder import Encoder
|
|
7
|
+
from .functions import decode_bytes, encode_str
|
|
8
|
+
from .scsu import scsu_decode, scsu_encode
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ENCODERS",
|
|
12
|
+
"Base26Encoder",
|
|
13
|
+
"Base31Encoder",
|
|
14
|
+
"Base36Encoder",
|
|
15
|
+
"Base64Encoder",
|
|
16
|
+
"Base94Encoder",
|
|
17
|
+
"Encoder",
|
|
18
|
+
"decode_bytes",
|
|
19
|
+
"encode_str",
|
|
20
|
+
"scsu_decode",
|
|
21
|
+
"scsu_encode",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
ENCODERS: dict[str, Encoder] = {
|
|
25
|
+
"base26": Base26Encoder(),
|
|
26
|
+
"base31": Base31Encoder(),
|
|
27
|
+
"base36": Base36Encoder(),
|
|
28
|
+
"base64": Base64Encoder(),
|
|
29
|
+
"base94": Base94Encoder(),
|
|
30
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import string
|
|
2
|
+
|
|
3
|
+
from .encoder import Encoder
|
|
4
|
+
from .functions import base_to_bytes, bytes_to_base
|
|
5
|
+
|
|
6
|
+
__all__ = ["ALPHABET", "Base26Encoder"]
|
|
7
|
+
|
|
8
|
+
ALPHABET = string.ascii_uppercase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Base26Encoder(Encoder):
|
|
12
|
+
@staticmethod
|
|
13
|
+
def encode(data: bytes) -> str:
|
|
14
|
+
return bytes_to_base(data, ALPHABET)
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def decode(data: str) -> bytes:
|
|
18
|
+
return base_to_bytes(data, ALPHABET)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .encoder import Encoder
|
|
2
|
+
from .functions import base_to_bytes, bytes_to_base
|
|
3
|
+
|
|
4
|
+
__all__ = ["ALPHABET", "Base31Encoder"]
|
|
5
|
+
|
|
6
|
+
ALPHABET = "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЭЮЯ"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base31Encoder(Encoder):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def encode(data: bytes) -> str:
|
|
12
|
+
return bytes_to_base(data, ALPHABET)
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def decode(data: str) -> bytes:
|
|
16
|
+
return base_to_bytes(data, ALPHABET)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import string
|
|
2
|
+
|
|
3
|
+
from .encoder import Encoder
|
|
4
|
+
from .functions import base_to_bytes, bytes_to_base
|
|
5
|
+
|
|
6
|
+
__all__ = ["ALPHABET", "Base36Encoder"]
|
|
7
|
+
|
|
8
|
+
ALPHABET = string.digits + string.ascii_uppercase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Base36Encoder(Encoder):
|
|
12
|
+
@staticmethod
|
|
13
|
+
def encode(data: bytes) -> str:
|
|
14
|
+
return bytes_to_base(data, ALPHABET)
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def decode(data: str) -> bytes:
|
|
18
|
+
return base_to_bytes(data, ALPHABET)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
from .encoder import Encoder
|
|
4
|
+
from .functions import decode_bytes, encode_str
|
|
5
|
+
|
|
6
|
+
__all__ = ["Base64Encoder"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base64Encoder(Encoder):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def encode(data: bytes) -> str:
|
|
12
|
+
return decode_bytes(base64.b64encode(data))
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def decode(data: str) -> bytes:
|
|
16
|
+
return base64.b64decode(encode_str(data))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .encoder import Encoder
|
|
2
|
+
from .functions import base_to_bytes, bytes_to_base
|
|
3
|
+
|
|
4
|
+
__all__ = ["ALPHABET", "Base94Encoder"]
|
|
5
|
+
|
|
6
|
+
ALPHABET = "".join([chr(i) for i in range(33, 127)])
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base94Encoder(Encoder):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def encode(data: bytes) -> str:
|
|
12
|
+
return bytes_to_base(data, ALPHABET)
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def decode(data: str) -> bytes:
|
|
16
|
+
return base_to_bytes(data, ALPHABET)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
3
|
+
__all__ = ["base_to_bytes", "bytes_to_base", "decode_bytes", "encode_str"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def encode_str(data: str) -> bytes:
|
|
7
|
+
return data.encode(encoding="utf-8", errors="strict")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def decode_bytes(data: bytes) -> str:
|
|
11
|
+
return data.decode(encoding="utf-8", errors="strict")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def int_to_base(number: int, alphabet: str) -> str:
|
|
15
|
+
if number == 0:
|
|
16
|
+
return alphabet[0]
|
|
17
|
+
|
|
18
|
+
result: list[str] = []
|
|
19
|
+
base = len(alphabet)
|
|
20
|
+
abs_number = abs(number)
|
|
21
|
+
while abs_number:
|
|
22
|
+
abs_number, remainder = divmod(abs_number, base)
|
|
23
|
+
result.append(alphabet[remainder])
|
|
24
|
+
|
|
25
|
+
return "".join(reversed(result))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def base_to_int(source: str, alphabet: str) -> int:
|
|
29
|
+
number = 0
|
|
30
|
+
base = len(alphabet)
|
|
31
|
+
for index, digit in enumerate(reversed(source)):
|
|
32
|
+
number += alphabet.index(digit) * cast("int", base**index)
|
|
33
|
+
|
|
34
|
+
return number
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def bytes_to_base(source: bytes, alphabet: str) -> str:
|
|
38
|
+
number = int.from_bytes(source, byteorder="big", signed=False)
|
|
39
|
+
return int_to_base(number, alphabet)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def int_to_bytes(number: int) -> bytes:
|
|
43
|
+
if number == 0:
|
|
44
|
+
return b"\x00"
|
|
45
|
+
|
|
46
|
+
buffer = bytearray()
|
|
47
|
+
while number:
|
|
48
|
+
buffer.append(number & 0xFF)
|
|
49
|
+
number //= 256
|
|
50
|
+
|
|
51
|
+
buffer.reverse()
|
|
52
|
+
return bytes(buffer)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def base_to_bytes(source: str, alphabet: str) -> bytes:
|
|
56
|
+
return int_to_bytes(base_to_int(source, alphabet))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from .scsu_stubs import SCSUIncrementalDecoder, SCSUIncrementalEncoder
|
|
5
|
+
else:
|
|
6
|
+
from scsu.codecs.decoder import SCSUIncrementalDecoder
|
|
7
|
+
from scsu.codecs.encoder import SCSUIncrementalEncoder
|
|
8
|
+
|
|
9
|
+
__all__ = ["scsu_decode", "scsu_encode"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def scsu_encode(data: str) -> bytes:
|
|
13
|
+
encoder = SCSUIncrementalEncoder(errors="strict")
|
|
14
|
+
return encoder.encode(s=data, final=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def scsu_decode(data: bytes) -> str:
|
|
18
|
+
decoder = SCSUIncrementalDecoder(errors="strict")
|
|
19
|
+
return decoder.decode(s=data, final=True)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class SCSUIncrementalEncoder:
|
|
2
|
+
def __init__(self, errors: str = "strict") -> None: ...
|
|
3
|
+
def encode(self, s: str, final: bool = False) -> bytes: ...
|
|
4
|
+
|
|
5
|
+
class SCSUIncrementalDecoder:
|
|
6
|
+
def __init__(self, errors: str = "strict") -> None: ...
|
|
7
|
+
def decode(self, s: bytes, final: bool = False) -> str: ...
|
rtty_soda/formatters.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import NamedTuple, Protocol
|
|
3
|
+
|
|
4
|
+
__all__ = ["FixedFormatter", "FormattedText", "Formatter", "remove_whitespace"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FormattedText(NamedTuple):
|
|
8
|
+
text: str
|
|
9
|
+
groups: int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Formatter(Protocol):
|
|
13
|
+
def format(self, text: str) -> FormattedText: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FixedFormatter(Formatter):
|
|
17
|
+
def __init__(self, group_len: int, line_len: int, pad_count: int) -> None:
|
|
18
|
+
self.group_len = group_len
|
|
19
|
+
self.line_len = line_len
|
|
20
|
+
self.pad_count = pad_count
|
|
21
|
+
|
|
22
|
+
def format(self, text: str) -> FormattedText:
|
|
23
|
+
groups = 0
|
|
24
|
+
|
|
25
|
+
if 0 < self.group_len < self.line_len:
|
|
26
|
+
text, groups = self.split_groups(text)
|
|
27
|
+
|
|
28
|
+
if self.pad_count > 0:
|
|
29
|
+
text = self.pad_newlines(text)
|
|
30
|
+
|
|
31
|
+
return FormattedText(text, groups)
|
|
32
|
+
|
|
33
|
+
def split_groups(self, data: str) -> FormattedText:
|
|
34
|
+
step = self.group_len
|
|
35
|
+
gpl = self.line_len // (step + 1)
|
|
36
|
+
groups = [data[i : i + step] for i in range(0, len(data), step)]
|
|
37
|
+
lines = [" ".join(groups[i : i + gpl]) for i in range(0, len(groups), gpl)]
|
|
38
|
+
return FormattedText("\n".join(lines), len(groups))
|
|
39
|
+
|
|
40
|
+
def pad_newlines(self, text: str) -> str:
|
|
41
|
+
padding = "\n" * self.pad_count
|
|
42
|
+
return padding + text.strip() + "\n" + padding
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remove_whitespace(data: str) -> str:
|
|
46
|
+
return re.sub(r"\s", "", data)
|
rtty_soda/interfaces.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
__all__ = ["Reader", "Writer"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Reader(Protocol):
|
|
7
|
+
"""Reads data source."""
|
|
8
|
+
|
|
9
|
+
def read_str(self) -> str: ...
|
|
10
|
+
|
|
11
|
+
def read_bytes(self) -> bytes: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Writer(Protocol):
|
|
15
|
+
"""Outputs data and prints diagnostics."""
|
|
16
|
+
|
|
17
|
+
def write_bytes(self, data: bytes) -> None: ...
|
|
18
|
+
|
|
19
|
+
def write_diag(self, message: str) -> None: ...
|
rtty_soda/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .encoding_service import EncodingService
|
|
2
|
+
from .encryption_service import EncryptionService, Keypair, Pipe
|
|
3
|
+
from .key_service import KeyService
|
|
4
|
+
from .service import FormattedOutput, Service
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"EncodingService",
|
|
8
|
+
"EncryptionService",
|
|
9
|
+
"FormattedOutput",
|
|
10
|
+
"KeyService",
|
|
11
|
+
"Keypair",
|
|
12
|
+
"Pipe",
|
|
13
|
+
"Service",
|
|
14
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from rtty_soda.encoders import ENCODERS
|
|
4
|
+
|
|
5
|
+
from .service import Service
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from rtty_soda.formatters import Formatter
|
|
9
|
+
from rtty_soda.interfaces import Reader, Writer
|
|
10
|
+
|
|
11
|
+
__all__ = ["EncodingService"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EncodingService(Service):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
in_encoding: str,
|
|
18
|
+
out_encoding: str,
|
|
19
|
+
formatter: Formatter,
|
|
20
|
+
writer: Writer,
|
|
21
|
+
verbose: bool,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(formatter, writer, verbose)
|
|
24
|
+
self.in_encoder = ENCODERS.get(in_encoding)
|
|
25
|
+
self.out_encoder = ENCODERS.get(out_encoding)
|
|
26
|
+
|
|
27
|
+
def encode(self, data: Reader) -> None:
|
|
28
|
+
buff = self.read_input(data, self.in_encoder)
|
|
29
|
+
self.write_output(buff, self.out_encoder)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from nacl.public import PrivateKey, PublicKey
|
|
4
|
+
|
|
5
|
+
from rtty_soda.archivers import ARCHIVERS, UNARCHIVERS
|
|
6
|
+
from rtty_soda.cryptography import public, secret
|
|
7
|
+
from rtty_soda.encoders import ENCODERS, encode_str, scsu_decode, scsu_encode
|
|
8
|
+
|
|
9
|
+
from .key_service import KeyService
|
|
10
|
+
from .service import Service
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
from rtty_soda.formatters import Formatter
|
|
16
|
+
from rtty_soda.interfaces import Reader, Writer
|
|
17
|
+
|
|
18
|
+
__all__ = ["EncryptionService", "Keypair", "Pipe"]
|
|
19
|
+
|
|
20
|
+
type Keypair = tuple[PrivateKey, PublicKey]
|
|
21
|
+
type Pipe = Callable[[bytes], bytes]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EncryptionService(Service):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
text_mode: bool,
|
|
28
|
+
key_encoding: str,
|
|
29
|
+
data_encoding: str,
|
|
30
|
+
compression: str,
|
|
31
|
+
formatter: Formatter | None,
|
|
32
|
+
writer: Writer,
|
|
33
|
+
verbose: bool,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(formatter, writer, verbose)
|
|
36
|
+
self.text_mode = text_mode
|
|
37
|
+
self.key_encoder = ENCODERS.get(key_encoding)
|
|
38
|
+
self.data_encoder = ENCODERS.get(data_encoding)
|
|
39
|
+
self.archiver = ARCHIVERS.get(compression)
|
|
40
|
+
self.unarchiver = UNARCHIVERS.get(compression)
|
|
41
|
+
|
|
42
|
+
def read_keypair(self, private_key: Reader, public_key: Reader) -> Keypair:
|
|
43
|
+
priv_seed = self.read_input(private_key, self.key_encoder)
|
|
44
|
+
priv = PrivateKey(private_key=priv_seed)
|
|
45
|
+
pub_seed = self.read_input(public_key, self.key_encoder)
|
|
46
|
+
pub = PublicKey(public_key=pub_seed)
|
|
47
|
+
return priv, pub
|
|
48
|
+
|
|
49
|
+
def encryption_flow(self, message: Reader, encrypt: Pipe) -> None:
|
|
50
|
+
if self.text_mode:
|
|
51
|
+
text = message.read_str().strip()
|
|
52
|
+
plaintext_len = len(text)
|
|
53
|
+
data = scsu_encode(text)
|
|
54
|
+
else:
|
|
55
|
+
data = message.read_bytes()
|
|
56
|
+
plaintext_len = len(data)
|
|
57
|
+
|
|
58
|
+
if self.archiver is not None:
|
|
59
|
+
data = self.archiver(data)
|
|
60
|
+
|
|
61
|
+
data = encrypt(data)
|
|
62
|
+
buff = self.format_data(data, self.data_encoder)
|
|
63
|
+
writer = self.writer
|
|
64
|
+
writer.write_bytes(buff.data)
|
|
65
|
+
if self.verbose:
|
|
66
|
+
overhead = buff.chars / plaintext_len
|
|
67
|
+
writer.write_diag(f"Groups: {buff.groups}")
|
|
68
|
+
writer.write_diag(f"Plaintext: {plaintext_len}")
|
|
69
|
+
writer.write_diag(f"Ciphertext: {buff.chars}")
|
|
70
|
+
writer.write_diag(f"Overhead: {overhead:.3f}")
|
|
71
|
+
|
|
72
|
+
def encrypt_public(
|
|
73
|
+
self, private_key: Reader, public_key: Reader, message: Reader
|
|
74
|
+
) -> None:
|
|
75
|
+
priv, pub = self.read_keypair(private_key, public_key)
|
|
76
|
+
|
|
77
|
+
def encrypt(data: bytes) -> bytes:
|
|
78
|
+
return public.encrypt(private=priv, public=pub, data=data)
|
|
79
|
+
|
|
80
|
+
self.encryption_flow(message, encrypt)
|
|
81
|
+
|
|
82
|
+
def encrypt_secret(self, key: Reader, message: Reader) -> None:
|
|
83
|
+
sk = self.read_input(key, self.key_encoder)
|
|
84
|
+
|
|
85
|
+
def encrypt(data: bytes) -> bytes:
|
|
86
|
+
return secret.encrypt(key=sk, data=data)
|
|
87
|
+
|
|
88
|
+
self.encryption_flow(message, encrypt)
|
|
89
|
+
|
|
90
|
+
def encrypt_password(
|
|
91
|
+
self, password: Reader, message: Reader, kdf_profile: str
|
|
92
|
+
) -> None:
|
|
93
|
+
key = KeyService.derive_key(password, kdf_profile)
|
|
94
|
+
|
|
95
|
+
def encrypt(data: bytes) -> bytes:
|
|
96
|
+
return secret.encrypt(key, data)
|
|
97
|
+
|
|
98
|
+
self.encryption_flow(message, encrypt)
|
|
99
|
+
|
|
100
|
+
def decryption_flow(self, message: Reader, decrypt: Pipe) -> None:
|
|
101
|
+
data = self.read_input(message, self.data_encoder)
|
|
102
|
+
data = decrypt(data)
|
|
103
|
+
if self.unarchiver is not None:
|
|
104
|
+
data = self.unarchiver(data)
|
|
105
|
+
|
|
106
|
+
if self.text_mode:
|
|
107
|
+
data = encode_str(scsu_decode(data))
|
|
108
|
+
|
|
109
|
+
self.writer.write_bytes(data)
|
|
110
|
+
|
|
111
|
+
def decrypt_public(
|
|
112
|
+
self, private_key: Reader, public_key: Reader, message: Reader
|
|
113
|
+
) -> None:
|
|
114
|
+
priv, pub = self.read_keypair(private_key, public_key)
|
|
115
|
+
|
|
116
|
+
def decrypt(data: bytes) -> bytes:
|
|
117
|
+
return public.decrypt(private=priv, public=pub, data=data)
|
|
118
|
+
|
|
119
|
+
self.decryption_flow(message, decrypt)
|
|
120
|
+
|
|
121
|
+
def decrypt_secret(self, key: Reader, message: Reader) -> None:
|
|
122
|
+
sk = self.read_input(key, self.key_encoder)
|
|
123
|
+
|
|
124
|
+
def decrypt(data: bytes) -> bytes:
|
|
125
|
+
return secret.decrypt(key=sk, data=data)
|
|
126
|
+
|
|
127
|
+
self.decryption_flow(message, decrypt)
|
|
128
|
+
|
|
129
|
+
def decrypt_password(
|
|
130
|
+
self, password: Reader, message: Reader, kdf_profile: str
|
|
131
|
+
) -> None:
|
|
132
|
+
key = KeyService.derive_key(password, kdf_profile)
|
|
133
|
+
|
|
134
|
+
def decrypt(data: bytes) -> bytes:
|
|
135
|
+
return secret.decrypt(key, data)
|
|
136
|
+
|
|
137
|
+
self.decryption_flow(message, decrypt)
|