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/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,11 @@
1
+ from typing import Protocol
2
+
3
+ __all__ = ["Encoder"]
4
+
5
+
6
+ class Encoder(Protocol):
7
+ @staticmethod
8
+ def encode(data: bytes) -> str: ...
9
+
10
+ @staticmethod
11
+ def decode(data: str) -> bytes: ...
@@ -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: ...
@@ -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)
@@ -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)