cryptovault-py 0.2.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.
- cryptovault/__init__.py +97 -0
- cryptovault/_blob.py +63 -0
- cryptovault/cipher.py +138 -0
- cryptovault/constants.py +85 -0
- cryptovault/error.py +76 -0
- cryptovault/fec/__init__.py +285 -0
- cryptovault/fec/_reedsolomon.py +302 -0
- cryptovault/fec/_viterbi.py +163 -0
- cryptovault/fec/interleaver.py +266 -0
- cryptovault/fec/rs.py +87 -0
- cryptovault/fec/viterbi.py +154 -0
- cryptovault/kdf.py +120 -0
- cryptovault/py.typed +0 -0
- cryptovault/vault.py +458 -0
- cryptovault_py-0.2.0.dist-info/METADATA +259 -0
- cryptovault_py-0.2.0.dist-info/RECORD +19 -0
- cryptovault_py-0.2.0.dist-info/WHEEL +4 -0
- cryptovault_py-0.2.0.dist-info/licenses/LICENSE-APACHE +201 -0
- cryptovault_py-0.2.0.dist-info/licenses/LICENSE-MIT +21 -0
cryptovault/__init__.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Author: Julian Bolivar
|
|
2
|
+
# Version: 0.2.0
|
|
3
|
+
# Date: 2026-10-16
|
|
4
|
+
"""Authenticated encryption byte-compatible with the ``cryptovault`` Rust crate.
|
|
5
|
+
|
|
6
|
+
A blob produced here opens with the crate and the other way round, for data
|
|
7
|
+
and for wrapped keys, as long as both sides use the same FEC strategy. The
|
|
8
|
+
payload is sealed with AES-256-GCM-SIV under an HKDF-SHA256 sub-key, with the
|
|
9
|
+
header bound as associated data, and then protected for the channel by one of
|
|
10
|
+
the crate's FEC strategies (see :mod:`cryptovault.fec`).
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
>>> from cryptovault import CryptoVault, generate_dek
|
|
14
|
+
>>> vault = CryptoVault.default()
|
|
15
|
+
>>> key = generate_dek()
|
|
16
|
+
>>> blob = vault.encrypt_with_key(key, "sk-secret")
|
|
17
|
+
>>> vault.decrypt_with_key(key, blob)
|
|
18
|
+
'sk-secret'
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from cryptovault.cipher import Aes256GcmSivCipher, AuthenticatedCipher
|
|
22
|
+
from cryptovault.constants import (
|
|
23
|
+
ARGON2_M_KIB,
|
|
24
|
+
ARGON2_P,
|
|
25
|
+
ARGON2_T,
|
|
26
|
+
BLOB_VERSION,
|
|
27
|
+
HEADER_LEN,
|
|
28
|
+
KEY_LEN,
|
|
29
|
+
MAX_B64_LEN,
|
|
30
|
+
MAX_BLOB_LEN,
|
|
31
|
+
MAX_PLAINTEXT_LEN,
|
|
32
|
+
NONCE_LEN,
|
|
33
|
+
RECOMMENDED_MAX_PAYLOAD,
|
|
34
|
+
RS_BLOCK,
|
|
35
|
+
RS_DATA,
|
|
36
|
+
RS_INTERLEAVE_MAX,
|
|
37
|
+
RS_PARITY,
|
|
38
|
+
SALT_LEN,
|
|
39
|
+
TAG_LEN,
|
|
40
|
+
TERMINATION_OVERHEAD,
|
|
41
|
+
VITERBI_CHUNK,
|
|
42
|
+
)
|
|
43
|
+
from cryptovault.error import (
|
|
44
|
+
CipherError,
|
|
45
|
+
CryptoVaultError,
|
|
46
|
+
EncodingError,
|
|
47
|
+
ErrorCorrectionError,
|
|
48
|
+
InvalidInputError,
|
|
49
|
+
KeyDerivationError,
|
|
50
|
+
)
|
|
51
|
+
from cryptovault.fec import ConcatenatedFec, ErrorCorrection, NoFec
|
|
52
|
+
from cryptovault.kdf import Argon2Kdf, KeyDerivation
|
|
53
|
+
from cryptovault.vault import (
|
|
54
|
+
CryptoVault,
|
|
55
|
+
constant_time_eq,
|
|
56
|
+
generate_dek,
|
|
57
|
+
generate_salt,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"ARGON2_M_KIB",
|
|
62
|
+
"ARGON2_P",
|
|
63
|
+
"ARGON2_T",
|
|
64
|
+
"BLOB_VERSION",
|
|
65
|
+
"HEADER_LEN",
|
|
66
|
+
"KEY_LEN",
|
|
67
|
+
"MAX_B64_LEN",
|
|
68
|
+
"MAX_BLOB_LEN",
|
|
69
|
+
"MAX_PLAINTEXT_LEN",
|
|
70
|
+
"NONCE_LEN",
|
|
71
|
+
"RECOMMENDED_MAX_PAYLOAD",
|
|
72
|
+
"RS_BLOCK",
|
|
73
|
+
"RS_DATA",
|
|
74
|
+
"RS_INTERLEAVE_MAX",
|
|
75
|
+
"RS_PARITY",
|
|
76
|
+
"SALT_LEN",
|
|
77
|
+
"TAG_LEN",
|
|
78
|
+
"TERMINATION_OVERHEAD",
|
|
79
|
+
"VITERBI_CHUNK",
|
|
80
|
+
"Aes256GcmSivCipher",
|
|
81
|
+
"Argon2Kdf",
|
|
82
|
+
"AuthenticatedCipher",
|
|
83
|
+
"CipherError",
|
|
84
|
+
"ConcatenatedFec",
|
|
85
|
+
"CryptoVault",
|
|
86
|
+
"CryptoVaultError",
|
|
87
|
+
"EncodingError",
|
|
88
|
+
"ErrorCorrection",
|
|
89
|
+
"ErrorCorrectionError",
|
|
90
|
+
"InvalidInputError",
|
|
91
|
+
"KeyDerivation",
|
|
92
|
+
"KeyDerivationError",
|
|
93
|
+
"NoFec",
|
|
94
|
+
"constant_time_eq",
|
|
95
|
+
"generate_dek",
|
|
96
|
+
"generate_salt",
|
|
97
|
+
]
|
cryptovault/_blob.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Author: Julian Bolivar
|
|
2
|
+
# Version: 0.1.0
|
|
3
|
+
# Date: 2026-10-16
|
|
4
|
+
"""Blob wire layout and the structural checks made before decryption.
|
|
5
|
+
|
|
6
|
+
Private, as in the crate: the public doors are the vault's methods. Layout::
|
|
7
|
+
|
|
8
|
+
protected = version (1) ‖ plaintext_len (u32 LE, 4) ‖ nonce ‖ ciphertext ‖ tag
|
|
9
|
+
blob = fec.encode(protected)
|
|
10
|
+
|
|
11
|
+
Every rejection uses a fixed message that repeats nothing of the input.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import struct
|
|
15
|
+
from typing import Final
|
|
16
|
+
|
|
17
|
+
from cryptovault.constants import (
|
|
18
|
+
BLOB_VERSION,
|
|
19
|
+
HEADER_LEN,
|
|
20
|
+
MAX_PLAINTEXT_LEN,
|
|
21
|
+
NONCE_LEN,
|
|
22
|
+
TAG_LEN,
|
|
23
|
+
)
|
|
24
|
+
from cryptovault.error import InvalidInputError
|
|
25
|
+
from cryptovault.fec import ErrorCorrection
|
|
26
|
+
|
|
27
|
+
_MALFORMED: Final = "malformed blob"
|
|
28
|
+
_LEN_FIELD: Final = struct.Struct("<I")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def decode_blob(fec: ErrorCorrection, received: bytes) -> tuple[int, int, bytes]:
|
|
32
|
+
"""Undo the FEC layer and split the protected payload, checking structure.
|
|
33
|
+
|
|
34
|
+
Checks run in the crate's order: the FEC length gate, then the header
|
|
35
|
+
length, the version, the declared plaintext length against
|
|
36
|
+
``MAX_PLAINTEXT_LEN``, and that length against the bytes received. Bytes
|
|
37
|
+
after the tag are ignored, as in the crate.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
fec: The strategy that encoded the blob.
|
|
41
|
+
received: The base64-decoded blob.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
``(version, plaintext_len, body)`` with ``body = nonce ‖ ciphertext ‖ tag``.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
InvalidInputError: ``"malformed blob"`` for a short or inconsistent blob,
|
|
48
|
+
``"unsupported blob version"`` for a version other than 1, or the
|
|
49
|
+
FEC strategy's own rejection.
|
|
50
|
+
"""
|
|
51
|
+
protected = fec.decode(received, fec.validate_pre_fec(received))
|
|
52
|
+
if len(protected) < HEADER_LEN:
|
|
53
|
+
raise InvalidInputError(_MALFORMED)
|
|
54
|
+
version = protected[0]
|
|
55
|
+
if version != BLOB_VERSION:
|
|
56
|
+
raise InvalidInputError("unsupported blob version")
|
|
57
|
+
(plaintext_len,) = _LEN_FIELD.unpack_from(protected, 1)
|
|
58
|
+
if plaintext_len > MAX_PLAINTEXT_LEN:
|
|
59
|
+
raise InvalidInputError(_MALFORMED)
|
|
60
|
+
protected_len = HEADER_LEN + NONCE_LEN + plaintext_len + TAG_LEN
|
|
61
|
+
if protected_len > len(protected):
|
|
62
|
+
raise InvalidInputError(_MALFORMED)
|
|
63
|
+
return version, plaintext_len, protected[HEADER_LEN:protected_len]
|
cryptovault/cipher.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Author: Julian Bolivar
|
|
2
|
+
# Version: 0.1.0
|
|
3
|
+
# Date: 2026-10-16
|
|
4
|
+
"""AEAD strategy: AES-256-GCM-SIV (RFC 8452) from ``cryptography``."""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import Final, Protocol
|
|
8
|
+
|
|
9
|
+
from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm
|
|
10
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV
|
|
11
|
+
|
|
12
|
+
from cryptovault.constants import KEY_LEN, NONCE_LEN
|
|
13
|
+
from cryptovault.error import CipherError
|
|
14
|
+
|
|
15
|
+
_AUTH_FAILED: Final = "AES-256-GCM-SIV authentication failed"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def sample_nonce() -> bytes:
|
|
19
|
+
"""Draw a fresh random nonce from the operating-system CSPRNG.
|
|
20
|
+
|
|
21
|
+
Every message gets its own nonce; a counter or a fixed nonce is never used.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
``NONCE_LEN`` (12) random bytes.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
CipherError: If the operating system cannot supply entropy. Weak bytes
|
|
28
|
+
are never returned instead.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
return os.urandom(NONCE_LEN)
|
|
32
|
+
except OSError as exc:
|
|
33
|
+
raise CipherError(f"OsRng nonce sampling failed: {exc}") from exc
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuthenticatedCipher(Protocol):
|
|
37
|
+
"""AEAD strategy, the crate's ``AuthenticatedCipher`` trait."""
|
|
38
|
+
|
|
39
|
+
def encrypt(self, key: bytes, nonce: bytes, aad: bytes, plaintext: bytes) -> bytes:
|
|
40
|
+
"""Encrypt and authenticate ``plaintext`` under ``aad``.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
key: The AEAD key.
|
|
44
|
+
nonce: The per-message nonce.
|
|
45
|
+
aad: Associated data, authenticated but not encrypted.
|
|
46
|
+
plaintext: The data to encrypt.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
``ciphertext ‖ tag``.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
CipherError: If the key or nonce is invalid.
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def decrypt(self, key: bytes, nonce: bytes, aad: bytes, sealed: bytes) -> bytes:
|
|
57
|
+
"""Verify and decrypt ``ciphertext ‖ tag``.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
key: The AEAD key.
|
|
61
|
+
nonce: The per-message nonce.
|
|
62
|
+
aad: The associated data the blob was sealed with.
|
|
63
|
+
sealed: ``ciphertext ‖ tag``.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The plaintext.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
CipherError: If the tag does not verify or the inputs are invalid.
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Aes256GcmSivCipher:
|
|
75
|
+
"""AES-256-GCM-SIV, the crate's ``Aes256GcmSivCipher``.
|
|
76
|
+
|
|
77
|
+
Stateless: an instance holds no key and may be shared between threads.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _aead_for(key: bytes, nonce: bytes) -> AESGCMSIV:
|
|
82
|
+
"""Check the key and nonce lengths, then build the primitive.
|
|
83
|
+
|
|
84
|
+
The key is checked first, as in the crate. ``cryptography`` would accept
|
|
85
|
+
a 16- or 24-byte key and silently run AES-128 or AES-192.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
CipherError: If the key is not 32 bytes, the nonce not 12, or the
|
|
89
|
+
OpenSSL backend has no AES-GCM-SIV (it needs OpenSSL 3.2+).
|
|
90
|
+
"""
|
|
91
|
+
if len(key) != KEY_LEN:
|
|
92
|
+
raise CipherError(f"AES-256 key must be {KEY_LEN} bytes, got {len(key)}")
|
|
93
|
+
if len(nonce) != NONCE_LEN:
|
|
94
|
+
raise CipherError(f"nonce must be {NONCE_LEN} bytes, got {len(nonce)}")
|
|
95
|
+
try:
|
|
96
|
+
return AESGCMSIV(key)
|
|
97
|
+
except UnsupportedAlgorithm as exc:
|
|
98
|
+
raise CipherError(f"AES-256-GCM-SIV is unavailable: {exc}") from exc
|
|
99
|
+
|
|
100
|
+
def encrypt(self, key: bytes, nonce: bytes, aad: bytes, plaintext: bytes) -> bytes:
|
|
101
|
+
"""Encrypt and authenticate ``plaintext`` under ``aad``.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
key: The 32-byte AES-256 key.
|
|
105
|
+
nonce: The 12-byte nonce.
|
|
106
|
+
aad: Associated data, authenticated but not encrypted.
|
|
107
|
+
plaintext: The data to encrypt.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
``ciphertext ‖ tag``, 16 bytes longer than ``plaintext``.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
CipherError: If the key is not 32 bytes or the nonce not 12.
|
|
114
|
+
"""
|
|
115
|
+
return self._aead_for(key, nonce).encrypt(nonce, plaintext, aad)
|
|
116
|
+
|
|
117
|
+
def decrypt(self, key: bytes, nonce: bytes, aad: bytes, sealed: bytes) -> bytes:
|
|
118
|
+
"""Verify and decrypt ``sealed`` (``ciphertext ‖ tag``).
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
key: The 32-byte AES-256 key.
|
|
122
|
+
nonce: The 12-byte nonce.
|
|
123
|
+
aad: The associated data the blob was sealed with.
|
|
124
|
+
sealed: ``ciphertext ‖ tag``.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The plaintext.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
CipherError: If the key is not 32 bytes, the nonce not 12, or the
|
|
131
|
+
tag does not verify (wrong key, wrong AAD or tampered data; the
|
|
132
|
+
message does not say which).
|
|
133
|
+
"""
|
|
134
|
+
aead = self._aead_for(key, nonce)
|
|
135
|
+
try:
|
|
136
|
+
return aead.decrypt(nonce, sealed, aad)
|
|
137
|
+
except InvalidTag as exc:
|
|
138
|
+
raise CipherError(_AUTH_FAILED) from exc
|
cryptovault/constants.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Author: Julian Bolivar
|
|
2
|
+
# Version: 0.2.0
|
|
3
|
+
# Date: 2026-10-16
|
|
4
|
+
"""Wire-format constants, with the values of the ``cryptovault`` crate 0.3.0.
|
|
5
|
+
|
|
6
|
+
The blob size limits are derived exactly as the crate derives them. The crate
|
|
7
|
+
sizes them for its concatenated FEC stack, and they apply unchanged to
|
|
8
|
+
``NoFec`` blobs, so a blob is rejected on the same lengths in both languages.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Final
|
|
12
|
+
|
|
13
|
+
KEY_LEN: Final = 32
|
|
14
|
+
"""AES-256 key length and master-key length, in bytes."""
|
|
15
|
+
|
|
16
|
+
SALT_LEN: Final = 16
|
|
17
|
+
"""Argon2id salt length, in bytes."""
|
|
18
|
+
|
|
19
|
+
NONCE_LEN: Final = 12
|
|
20
|
+
"""AES-256-GCM-SIV nonce length, in bytes."""
|
|
21
|
+
|
|
22
|
+
TAG_LEN: Final = 16
|
|
23
|
+
"""AEAD authentication tag length, in bytes."""
|
|
24
|
+
|
|
25
|
+
HEADER_LEN: Final = 5
|
|
26
|
+
"""Blob header length: ``version (1)`` + ``plaintext_len (u32 LE, 4)``."""
|
|
27
|
+
|
|
28
|
+
BLOB_VERSION: Final = 1
|
|
29
|
+
"""The only blob format version accepted."""
|
|
30
|
+
|
|
31
|
+
MAX_PLAINTEXT_LEN: Final = 10 * 1024 * 1024
|
|
32
|
+
"""Largest plaintext accepted in one blob (10 MiB)."""
|
|
33
|
+
|
|
34
|
+
RECOMMENDED_MAX_PAYLOAD: Final = 128 * 1024
|
|
35
|
+
"""Recommended largest plaintext per blob (128 KiB); frame larger data."""
|
|
36
|
+
|
|
37
|
+
ARGON2_M_KIB: Final = 65536
|
|
38
|
+
"""Argon2id memory cost, KiB (OWASP 2025: 64 MiB)."""
|
|
39
|
+
|
|
40
|
+
ARGON2_T: Final = 3
|
|
41
|
+
"""Argon2id time cost (iterations)."""
|
|
42
|
+
|
|
43
|
+
ARGON2_P: Final = 4
|
|
44
|
+
"""Argon2id parallelism (lanes)."""
|
|
45
|
+
|
|
46
|
+
RS_DATA: Final = 223
|
|
47
|
+
"""Reed-Solomon data bytes per codeword."""
|
|
48
|
+
|
|
49
|
+
RS_PARITY: Final = 32
|
|
50
|
+
"""Reed-Solomon parity bytes per codeword."""
|
|
51
|
+
|
|
52
|
+
RS_BLOCK: Final = RS_DATA + RS_PARITY
|
|
53
|
+
"""Reed-Solomon codeword length, in bytes (255)."""
|
|
54
|
+
|
|
55
|
+
RS_INTERLEAVE_MAX: Final = 16
|
|
56
|
+
"""Largest interleave depth: codewords per interleaver window."""
|
|
57
|
+
|
|
58
|
+
VITERBI_CHUNK: Final = 490 * RS_BLOCK
|
|
59
|
+
"""Reed-Solomon stream bytes per Viterbi sub-block (124 950)."""
|
|
60
|
+
|
|
61
|
+
TERMINATION_OVERHEAD: Final = 2
|
|
62
|
+
"""Bytes the Viterbi zero tail adds to each sub-block (``2L + 2``)."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _max_blob_len() -> int:
|
|
66
|
+
"""Derive the crate's ``MAX_BLOB_LEN``.
|
|
67
|
+
|
|
68
|
+
The largest protected payload is expanded by Reed-Solomon (``255/223``,
|
|
69
|
+
whole codewords), doubled by the Viterbi inner code, and given a two-byte
|
|
70
|
+
zero tail per Viterbi sub-block.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The largest decoded blob length accepted, in bytes.
|
|
74
|
+
"""
|
|
75
|
+
protected_max = MAX_PLAINTEXT_LEN + HEADER_LEN + NONCE_LEN + TAG_LEN
|
|
76
|
+
rs_max = -(-protected_max // RS_DATA) * RS_BLOCK
|
|
77
|
+
viterbi_chunks = -(-rs_max // VITERBI_CHUNK)
|
|
78
|
+
return rs_max * 2 + TERMINATION_OVERHEAD * viterbi_chunks
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
MAX_BLOB_LEN: Final = _max_blob_len()
|
|
82
|
+
"""Largest decoded blob accepted, in bytes (23 981 412)."""
|
|
83
|
+
|
|
84
|
+
MAX_B64_LEN: Final = MAX_BLOB_LEN * 4 // 3 + 4
|
|
85
|
+
"""Largest base64 input accepted, checked before decoding (31 975 220)."""
|
cryptovault/error.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Author: Julian Bolivar
|
|
2
|
+
# Version: 0.1.0
|
|
3
|
+
# Date: 2026-10-16
|
|
4
|
+
"""Typed error domain: one exception class per ``CryptoError`` variant.
|
|
5
|
+
|
|
6
|
+
Every failure the package reports is a :class:`CryptoVaultError`. The subclass
|
|
7
|
+
names the stage that failed, so a caller can tell a structural rejection from a
|
|
8
|
+
failed authentication tag; the message stays generic and never repeats the
|
|
9
|
+
caller's input. ``str(error)`` matches the ``Display`` output of the Rust crate
|
|
10
|
+
for the same failure, prefix included.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
>>> from cryptovault.error import CipherError, CryptoVaultError
|
|
14
|
+
>>> error = CipherError("AES-256-GCM-SIV authentication failed")
|
|
15
|
+
>>> str(error)
|
|
16
|
+
'Cipher error: AES-256-GCM-SIV authentication failed'
|
|
17
|
+
>>> isinstance(error, CryptoVaultError)
|
|
18
|
+
True
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import ClassVar
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CryptoVaultError(Exception):
|
|
25
|
+
"""Base of every error the package raises.
|
|
26
|
+
|
|
27
|
+
Subclasses set ``PREFIX``, the stage label the crate prints before the
|
|
28
|
+
message. Catch this class to handle every failure of the package at once.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
message: The fixed, oracle-free description of the failure.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
message: The description without the stage prefix.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
PREFIX: ClassVar[str] = "CryptoVault error"
|
|
38
|
+
|
|
39
|
+
def __init__(self, message: str) -> None:
|
|
40
|
+
"""Store the message and pass it to :class:`Exception`."""
|
|
41
|
+
super().__init__(message)
|
|
42
|
+
self.message = message
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
"""Return ``"<PREFIX>: <message>"``, the crate's ``Display`` form."""
|
|
46
|
+
return f"{self.PREFIX}: {self.message}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class KeyDerivationError(CryptoVaultError):
|
|
50
|
+
"""Argon2id or HKDF failure, or no entropy for a salt or DEK."""
|
|
51
|
+
|
|
52
|
+
PREFIX = "Key derivation error"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CipherError(CryptoVaultError):
|
|
56
|
+
"""AEAD failure: a tag that does not verify, or no entropy for a nonce."""
|
|
57
|
+
|
|
58
|
+
PREFIX = "Cipher error"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ErrorCorrectionError(CryptoVaultError):
|
|
62
|
+
"""Corruption beyond the capacity of a forward-error-correction codec."""
|
|
63
|
+
|
|
64
|
+
PREFIX = "Error correction error"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class EncodingError(CryptoVaultError):
|
|
68
|
+
"""Non-canonical base64, or text that is not valid UTF-8."""
|
|
69
|
+
|
|
70
|
+
PREFIX = "Encoding error"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class InvalidInputError(CryptoVaultError):
|
|
74
|
+
"""Structural or input-validation failure, raised before any decryption."""
|
|
75
|
+
|
|
76
|
+
PREFIX = "Invalid input"
|