Coverage for src / derivepassphrase / exporter / vault_native.py: 100.000%
204 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 21:34 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 21:34 +0200
1# SPDX-FileCopyrightText: 2026 Marco Ricci <software@the13thletter.info>
2#
3# SPDX-License-Identifier: Zlib
5"""Exporter for the vault native configuration format (v0.2 or v0.3).
7The vault native formats are the configuration formats used by vault
8v0.2 and v0.3. The configuration is stored as a single encrypted file,
9which is encrypted and authenticated. v0.2 and v0.3 differ in some
10details concerning key derivation and expected format of internal
11structures, so they are *not* compatible. v0.2 additionally contains
12cryptographic weaknesses (API misuse of a key derivation function, and
13a low-entropy method of generating initialization vectors for CBC block
14encryption mode) and should thus be avoided if possible.
16The public interface is the [`export_vault_native_data`][] function.
17Multiple *non-public* classes are additionally documented here for
18didactical and educational reasons, but they are not part of the module
19API, are subject to change without notice (including removal), and
20should *not* be used or relied on.
22"""
24# ruff: noqa: S303
26from __future__ import annotations
28import abc
29import base64
30import importlib
31import json
32import logging
33import os
34import pathlib
35import threading
36import warnings
37from typing import TYPE_CHECKING
39from derivepassphrase import exporter, vault
40from derivepassphrase._internals import cli_messages as _msg
42if TYPE_CHECKING:
43 from typing import Any
45 from typing_extensions import Buffer
47if TYPE_CHECKING:
48 from cryptography import exceptions as crypt_exceptions
49 from cryptography import utils as crypt_utils
50 from cryptography.hazmat.primitives import ciphers, hashes, hmac, padding
51 from cryptography.hazmat.primitives.ciphers import algorithms, modes
52 from cryptography.hazmat.primitives.kdf import pbkdf2
54 STUBBED = False
55else:
56 try:
57 importlib.import_module("cryptography")
58 except ModuleNotFoundError as exc:
60 class _DummyModule:
61 def __init__(self, exc: type[Exception]) -> None:
62 self.exc = exc
64 def __getattr__(self, name: str) -> Any: # noqa: ANN401
65 def func(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401,ARG001
66 raise self.exc
68 return func
70 crypt_exceptions = crypt_utils = _DummyModule(exc)
71 ciphers = hashes = hmac = padding = _DummyModule(exc)
72 algorithms = modes = pbkdf2 = _DummyModule(exc)
73 STUBBED = True
74 else:
75 from cryptography import exceptions as crypt_exceptions
76 from cryptography import utils as crypt_utils
77 from cryptography.hazmat.primitives import (
78 ciphers,
79 hashes,
80 hmac,
81 padding,
82 )
83 from cryptography.hazmat.primitives.ciphers import algorithms, modes
84 from cryptography.hazmat.primitives.kdf import pbkdf2
86 STUBBED = False
88__all__ = ("export_vault_native_data",)
90logger = logging.getLogger(__name__)
93@exporter.register_export_vault_config_data_handler("v0.2", "v0.3")
94def export_vault_native_data( # noqa: D417
95 path: str | bytes | os.PathLike | None = None,
96 key: str | Buffer | None = None,
97 *,
98 format: str, # noqa: A002
99) -> Any: # noqa: ANN401
100 """Export the full configuration stored in vault native format.
102 See [`exporter.ExportVaultConfigDataFunction`][] for an explanation
103 of the call signature, and the exceptions to expect.
105 Other Args:
106 format:
107 The only supported formats are `v0.2` and `v0.3`.
109 """ # noqa: DOC201,DOC501
110 # Trigger import errors if necessary.
111 importlib.import_module("cryptography") 1ehljmgbfdao
112 if path is None: 1ehljmgbfda
113 path = exporter.get_vault_path() 1ebfda
114 else:
115 path = pathlib.Path(os.fsdecode(path)) 1hljmgd
116 with path.open("rb") as infile: 1ehljmgbfda
117 contents = base64.standard_b64decode(infile.read()) 1ehjgbfda
118 if key is None: 1ehjgbfda
119 key = exporter.get_vault_key() 1ehjfda
120 parser_class: type[VaultNativeConfigParser] | None = { 1ehjgbfda
121 "v0.2": VaultNativeV02ConfigParser,
122 "v0.3": VaultNativeV03ConfigParser,
123 }.get(format)
124 if parser_class is None: # pragma: no cover [failsafe] 1ehjgbfda
125 msg = exporter.INVALID_VAULT_NATIVE_CONFIGURATION_FORMAT.format(
126 fmt=format
127 )
128 raise ValueError(msg)
129 try: 1ehjgbfda
130 return parser_class(contents, key)() 1ehjgbfda
131 except ValueError as exc: 1hja
132 raise exporter.NotAVaultConfigError(path, format=format) from exc 1hja
135def _h(bs: Buffer) -> str:
136 return "<{}>".format(memoryview(bs).hex(" ")) 1ehgbfdac
139class VaultNativeConfigParser(abc.ABC):
140 """A base parser for vault's native configuration format.
142 Certain details are specific to the respective vault versions, and
143 are abstracted out. This class by itself is not instantiable
144 because of this.
146 """
148 def __init__(self, contents: Buffer, password: str | Buffer) -> None:
149 """Initialize the parser.
151 Args:
152 contents:
153 The binary contents of the encrypted configuration file.
155 Note: On disk, these are usually stored in
156 base64-encoded form, not in the "raw" form as needed
157 here.
159 password:
160 The vault master key/master passphrase the file is
161 encrypted with. Must be non-empty. See
162 [`exporter.get_vault_key`][] for details.
164 If this is a text string, then the UTF-8 encoding of the
165 string is used as the binary password.
167 Raises:
168 ValueError:
169 The password must not be empty.
171 Warning:
172 Non-public class, provided for didactical and educational
173 purposes only. Subject to change without notice, including
174 removal.
176 """
177 if not password: 1ehjgbfdanc
178 msg = "Password must not be empty" 1n
179 raise ValueError(msg) 1n
180 self._consistency_lock = threading.RLock() 1ehjgbfdac
181 self._contents = bytes(contents) 1ehjgbfdac
182 self._iv_size = 0 1ehjgbfdac
183 self._mac_size = 0 1ehjgbfdac
184 self._encryption_key = b"" 1ehjgbfdac
185 self._encryption_key_size = 0 1ehjgbfdac
186 self._signing_key = b"" 1ehjgbfdac
187 self._signing_key_size = 0 1ehjgbfdac
188 self._message = b"" 1ehjgbfdac
189 self._message_tag = b"" 1ehjgbfdac
190 self._iv = b"" 1ehjgbfdac
191 self._payload = b"" 1ehjgbfdac
192 self._password = password 1ehjgbfdac
193 self._sentinel: object = object() 1ehjgbfdac
194 self._data: Any = self._sentinel 1ehjgbfdac
196 def __call__(self) -> Any: # noqa: ANN401
197 """Return the decrypted and parsed vault configuration.
199 Raises:
200 cryptography.exceptions.InvalidSignature:
201 The encrypted configuration does not contain a valid
202 signature.
203 ValueError:
204 The format is invalid, in a non-cryptographic way. (For
205 example, it contains an unsupported version marker, or
206 unexpected extra contents, or invalid padding.)
208 """
209 with self._consistency_lock: 1ehjgbfdac
210 if self._data is self._sentinel: 1ehjgbfdac
211 self._parse_contents() 1ehjgbfdac
212 self._derive_keys() 1ehgbfdac
213 self._check_signature() 1ehgbfdac
214 self._data = self._decrypt_payload() 1egbfdac
215 return self._data 1ehjgbfdac
217 @staticmethod
218 def _pbkdf2(
219 password: str | Buffer, key_size: int, iterations: int
220 ) -> bytes:
221 """Generate a key from a password.
223 Uses PBKDF2 with HMAC-SHA1, with [vault.Vault.UUID][] as a fixed
224 salt value.
226 Args:
227 password:
228 The password from which to derive the key.
229 key_size:
230 The size of the output string. The effective key size
231 (in bytes) is thus half of this output string size.
232 iterations:
233 The PBKDF2 iteration count.
235 Returns:
236 The PBKDF2-derived key, encoded as a lowercase ASCII
237 hexadecimal string.
239 Danger: Insecure use of cryptography
240 This function is insecure because it uses a fixed salt
241 value, which is not secure against rainbow tables. It is
242 further difficult to use because the effective key size is
243 only half as large as the "size" parameter (output string
244 size). Finally, though the use of SHA-1 in HMAC per se is
245 not known to be insecure, SHA-1 is known not to be
246 collision-resistant.
248 """
249 if isinstance(password, str): 1ehgbfdakc
250 password = password.encode("utf-8") 1dc
251 raw_key = pbkdf2.PBKDF2HMAC( 1ehgbfdakc
252 algorithm=hashes.SHA1(),
253 length=key_size // 2,
254 salt=vault.Vault.UUID,
255 iterations=iterations,
256 ).derive(bytes(password))
257 result_key = raw_key.hex().lower().encode("ASCII") 1ehgbfdakc
258 logger.debug( 1ehgbfdakc
259 _msg.TranslatedString(
260 _msg.DebugMsgTemplate.VAULT_NATIVE_PBKDF2_CALL,
261 password=password,
262 salt=vault.Vault.UUID,
263 iterations=iterations,
264 key_size=key_size // 2,
265 algorithm="sha1",
266 raw_result=raw_key,
267 result_key=result_key.decode("ASCII"),
268 ),
269 )
270 return result_key 1ehgbfdakc
272 def _parse_contents(self) -> None:
273 """Parse the contents into IV, payload and MAC.
275 This operates on, and sets, multiple internal attributes of the
276 parser.
278 Raises:
279 ValueError:
280 The configuration file contents are clearly truncated.
282 """
283 logger.info( 1ehjgbfdac
284 _msg.TranslatedString(
285 _msg.InfoMsgTemplate.VAULT_NATIVE_PARSING_IV_PAYLOAD_MAC,
286 ),
287 )
289 def cut(buffer: bytes, cutpoint: int) -> tuple[bytes, bytes]: 1ehjgbfdac
290 return buffer[:cutpoint], buffer[cutpoint:] 1ehgbfdac
292 with self._consistency_lock: 1ehjgbfdac
293 contents = self._contents 1ehjgbfdac
294 iv_size = self._iv_size 1ehjgbfdac
295 mac_size = self._mac_size 1ehjgbfdac
297 if len(contents) < iv_size + 16 + mac_size: 1ehjgbfdac
298 msg = "Invalid vault configuration file: file is truncated" 1j
299 raise ValueError(msg) 1j
301 cutpos1 = len(contents) - mac_size 1ehgbfdac
302 cutpos2 = iv_size 1ehgbfdac
303 message, message_tag = cut(contents, cutpos1) 1ehgbfdac
304 iv, payload = cut(message, cutpos2) 1ehgbfdac
306 self._message = message 1ehgbfdac
307 self._message_tag = message_tag 1ehgbfdac
308 self._iv = iv 1ehgbfdac
309 self._payload = payload 1ehjgbfdac
311 logger.debug( 1ehgbfdac
312 _msg.TranslatedString(
313 _msg.DebugMsgTemplate.VAULT_NATIVE_PARSE_BUFFER,
314 contents=_h(contents),
315 iv=_h(iv),
316 payload=_h(payload),
317 mac=_h(message_tag),
318 ),
319 )
321 def _derive_keys(self) -> None:
322 """Derive the signing and encryption keys.
324 This is a bookkeeping method. The actual work is done in
325 [`_generate_keys`][].
327 """
328 logger.info( 1ehgbfdac
329 _msg.TranslatedString(
330 _msg.InfoMsgTemplate.VAULT_NATIVE_DERIVING_KEYS,
331 ),
332 )
333 with self._consistency_lock: 1ehgbfdac
334 self._generate_keys() 1ehgbfdac
335 assert len(self._encryption_key) == self._encryption_key_size, ( 1ehgbfdac
336 "Derived encryption key is invalid"
337 )
338 assert len(self._signing_key) == self._signing_key_size, ( 1ehgbfdac
339 "Derived signing key is invalid"
340 )
342 @abc.abstractmethod
343 def _generate_keys(self) -> None:
344 """Derive the signing and encryption keys, and set the key sizes.
346 Subclasses must override this, as the derivation system is
347 version-specific. The default implementation raises an error.
349 Raises:
350 AssertionError:
351 There is no default implementation.
353 """
354 raise AssertionError
356 def _check_signature(self) -> None:
357 """Check for a valid MAC on the encrypted vault configuration.
359 The MAC uses HMAC-SHA1, and thus is 32 bytes long, before
360 encoding.
362 Raises:
363 ValueError:
364 The MAC is invalid.
366 """
367 logger.info( 1ehgbfdac
368 _msg.TranslatedString(
369 _msg.InfoMsgTemplate.VAULT_NATIVE_CHECKING_MAC,
370 ),
371 )
372 with self._consistency_lock: 1ehgbfdac
373 mac = hmac.HMAC(self._signing_key, hashes.SHA256()) 1ehgbfdac
374 mac_input = self._hmac_input() 1ehgbfdac
375 mac_expected = self._message_tag 1ehgbfdac
376 logger.debug( 1ehgbfdac
377 _msg.TranslatedString(
378 _msg.DebugMsgTemplate.VAULT_NATIVE_CHECKING_MAC_DETAILS,
379 mac_input=_h(mac_input),
380 mac=_h(mac_expected),
381 ),
382 )
383 mac.update(mac_input) 1ehgbfdac
384 try: 1ehgbfdac
385 mac.verify(mac_expected) 1ehgbfdac
386 except crypt_exceptions.InvalidSignature: 1ha
387 msg = "File does not contain a valid signature" 1ha
388 raise ValueError(msg) from None 1ha
390 @abc.abstractmethod
391 def _hmac_input(self) -> bytes:
392 """Return the input the MAC is supposed to verify.
394 Subclasses must override this, as the MAC-attested data is
395 version-specific. The default implementation raises an error.
397 Raises:
398 AssertionError:
399 There is no default implementation.
401 """
402 raise AssertionError
404 def _decrypt_payload(self) -> Any: # noqa: ANN401
405 """Return the decrypted vault configuration.
407 Requires [`_parse_contents`][] and [`_derive_keys`][] to have
408 run, and relies on [`_check_signature`][] for tampering
409 detection.
411 """
412 logger.info( 1egbfdac
413 _msg.TranslatedString(
414 _msg.InfoMsgTemplate.VAULT_NATIVE_DECRYPTING_CONTENTS,
415 ),
416 )
417 with self._consistency_lock: 1egbfdac
418 payload = self._payload 1egbfdac
419 iv_size = self._iv_size 1egbfdac
420 decryptor = self._make_decryptor() 1egbfdac
421 padded_plaintext = bytearray() 1egbfdac
422 padded_plaintext.extend(decryptor.update(payload)) 1egbfdac
423 padded_plaintext.extend(decryptor.finalize()) 1egbfdac
424 logger.debug( 1egbfdac
425 _msg.TranslatedString(
426 _msg.DebugMsgTemplate.VAULT_NATIVE_PADDED_PLAINTEXT,
427 contents=_h(padded_plaintext),
428 ),
429 )
430 unpadder = padding.PKCS7(iv_size * 8).unpadder() 1egbfdac
431 plaintext = bytearray() 1egbfdac
432 plaintext.extend(unpadder.update(bytes(padded_plaintext))) 1egbfdac
433 plaintext.extend(unpadder.finalize()) 1egbfdac
434 logger.debug( 1egbfdac
435 _msg.TranslatedString(
436 _msg.DebugMsgTemplate.VAULT_NATIVE_PLAINTEXT,
437 contents=_h(plaintext),
438 ),
439 )
440 return json.loads(plaintext) 1egbfdac
442 @abc.abstractmethod
443 def _make_decryptor(self) -> ciphers.CipherContext:
444 """Return the cipher context object used for decryption.
446 Subclasses must override this, as the cipher setup is
447 version-specific. The default implementation raises an error.
449 Raises:
450 AssertionError:
451 There is no default implementation.
453 """
454 raise AssertionError
457class VaultNativeV03ConfigParser(VaultNativeConfigParser):
458 """A parser for vault's native configuration format (v0.3).
460 This is the modern, pre-storeroom configuration format.
462 Warning:
463 Non-public class, provided for didactical and educational
464 purposes only. Subject to change without notice, including
465 removal.
467 """
469 KEY_SIZE = 32
470 """
471 Key size for both the encryption and the signing key, including the
472 encoding as a hexadecimal string. (The effective cryptographic
473 strength is half of this value.)
474 """
476 def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
477 super().__init__(*args, **kwargs) 1ehjgbfdanc
478 self._iv_size = 16 1ehjgbfdac
479 self._mac_size = 32 1ehjgbfdac
481 def _generate_keys(self) -> None:
482 """Derive the signing and encryption keys, and set the key sizes.
484 Version 0.3 vault configurations use a constant key size; see
485 [`KEY_SIZE`][]. The encryption and signing keys differ in how
486 many rounds of PBKDF2 they use (100 and 200, respectively).
488 Danger: Insecure use of cryptography
489 This function makes use of the insecure function
490 [`VaultNativeConfigParser._pbkdf2`][], without any attempts
491 at mitigating its insecurity. It further uses `_pbkdf2`
492 with the low iteration count of 100 and 200 rounds, which is
493 *drastically* insufficient to defend against password
494 guessing attacks using GPUs or ASICs. We provide this
495 function for the purpose of interoperability with existing
496 vault installations. Do not rely on this system to keep
497 your vault configuration secure against access by even
498 moderately determined attackers!
500 """
501 with self._consistency_lock: 1ehgbfdac
502 self._encryption_key = self._pbkdf2( 1ehgbfdac
503 self._password, self.KEY_SIZE, 100
504 )
505 self._signing_key = self._pbkdf2( 1ehgbfdac
506 self._password, self.KEY_SIZE, 200
507 )
508 self._encryption_key_size = self._signing_key_size = self.KEY_SIZE 1ehgbfdac
510 def _hmac_input(self) -> bytes:
511 """Return the input the MAC is supposed to verify.
513 This includes hexadecimal encoding of the message payload.
515 """
516 return self._message.hex().lower().encode("ASCII") 1ehgbfdac
518 def _make_decryptor(self) -> ciphers.CipherContext:
519 """Return the cipher context object used for decryption.
521 This is a standard AES256-CBC cipher context using the
522 previously derived encryption key and the IV declared in the
523 (MAC-verified) message payload.
525 """
526 with self._consistency_lock: 1egbfdac
527 encryption_key = self._encryption_key 1egbfdac
528 iv = self._iv 1egbfdac
529 return ciphers.Cipher( 1egbfdac
530 algorithms.AES256(encryption_key), modes.CBC(iv)
531 ).decryptor()
534class VaultNativeV02ConfigParser(VaultNativeConfigParser):
535 """A parser for vault's native configuration format (v0.2).
537 This is the classic configuration format. Compared to v0.3, it
538 contains an (accidental) API misuse for the generation of the master
539 keys, a low-entropy method of generating initialization vectors for
540 the AES-CBC encryption step, and extra layers of base64 encoding.
541 Because of these significantly weakened confidentiality guarantees,
542 v0.2 configurations should be upgraded to at least v0.3 as soon as
543 possible.
545 Warning:
546 Non-public class, provided for didactical and educational
547 purposes only. Subject to change without notice, including
548 removal.
550 """
552 def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
553 super().__init__(*args, **kwargs) 1jbac
554 self._iv_size = 16 1jbac
555 self._mac_size = 64 1jbac
557 def _parse_contents(self) -> None:
558 """Parse the contents into IV, payload and MAC.
560 Like the base class implementation, this operates on, and sets,
561 multiple internal attributes of the parser. In version 0.2
562 vault configurations, the payload is encoded in base64 and the
563 message tag (MAC) is encoded in hexadecimal, so unlike the base
564 class implementation, we additionally decode the payload and the
565 MAC.
567 Raises:
568 ValueError:
569 The configuration file contents are clearly truncated,
570 or the payload or the message tag cannot be decoded
571 properly.
573 """
574 with self._consistency_lock: 1jbac
575 super()._parse_contents() 1jbac
576 payload = self._payload = base64.standard_b64decode(self._payload) 1bac
577 message_tag = self._message_tag = bytes.fromhex( 1jbac
578 self._message_tag.decode("ASCII")
579 )
580 logger.debug( 1bac
581 _msg.TranslatedString(
582 _msg.DebugMsgTemplate.VAULT_NATIVE_V02_PAYLOAD_MAC_POSTPROCESSING,
583 payload=_h(payload),
584 mac=_h(message_tag),
585 ),
586 )
588 def _generate_keys(self) -> None:
589 """Derive the signing and encryption keys, and set the key sizes.
591 Version 0.2 vault configurations use 8-byte encryption keys and
592 16-byte signing keys, including the hexadecimal encoding. They
593 both use 16 rounds of PBKDF2. This is due to an oversight in
594 vault, where the author mistakenly supplied the intended
595 iteration count as the key size, and the key size as the
596 iteration count.
598 Danger: Insecure use of cryptography
599 This function makes use of the insecure function
600 [`VaultNativeConfigParser._pbkdf2`][], without any attempts
601 at mitigating its insecurity. It further uses `_pbkdf2`
602 with the low iteration count of 16 rounds, which is
603 *drastically* insufficient to defend against password
604 guessing attacks using GPUs or ASICs, and generates the
605 encryption key as a truncation of the signing key. We
606 provide this function for the purpose of interoperability
607 with existing vault installations. Do not rely on this
608 system to keep your vault configuration secure against
609 access by even moderately determined attackers!
611 """
612 with self._consistency_lock: 1bac
613 self._encryption_key = self._pbkdf2(self._password, 8, 16) 1bac
614 self._signing_key = self._pbkdf2(self._password, 16, 16) 1bac
615 self._encryption_key_size = 8 1bac
616 self._signing_key_size = 16 1bac
618 def _hmac_input(self) -> bytes:
619 """Return the input the MAC is supposed to verify.
621 This includes hexadecimal encoding of the message payload.
623 """
624 return base64.standard_b64encode(self._message) 1bac
626 @staticmethod
627 def _evp_bytestokey_md5_one_iteration_no_salt(
628 data: bytes, key_size: int, iv_size: int
629 ) -> tuple[bytes, bytes]:
630 """Reimplement OpenSSL's `EVP_BytesToKey` with fixed parameters.
632 `EVP_BytesToKey` in general is a key derivation function,
633 i.e., a function that derives key material from an input
634 byte string. `EVP_BytesToKey` conceptually splits the
635 derived key material into an encryption key and an
636 initialization vector (IV).
638 Note: Algorithm description
639 `EVP_BytesToKey` takes an input byte string, two output
640 size (encryption key size and IV size), a message digest
641 function, a salt value and an iteration count. The
642 derived key material is calculated in blocks, each of
643 which is the output of (iterated application of) the
644 message digest function. The input to the message
645 digest function is the concatenation of the previous
646 block (if any) with the input byte string and the salt
647 value (if any):
649 ~~~~ python
650 data = block_input = b"".join([previous_block, input_string, salt])
651 for i in range(iteration_count):
652 data = message_digest(data)
653 block = data
654 ~~~~
656 We use as many blocks as are necessary to cover the
657 total output byte string size. The first few bytes
658 (dictated by the encryption key size) form the
659 encryption key, the other bytes (dictated by the IV
660 size) form the IV.
662 We implement exactly the subset of `EVP_BytesToKey` that the
663 Node.js `crypto` library (v21 series and older) uses in its
664 implementation of `crypto.createCipher("aes256", password)`.
665 Specifically, the message digest function is fixed to MD5,
666 the salt is always empty, and the iteration count is fixed
667 at one.
670 Returns:
671 A 2-tuple containing the derived encryption key and the
672 derived initialization vector.
674 Danger: Insecure use of cryptography
675 This function reimplements the OpenSSL function
676 `EVP_BytesToKey`, which generates cryptographically weak
677 keys, without any attempts at mitigating its insecurity. We
678 provide this function for the purpose of interoperability
679 with existing vault installations. Do not rely on this
680 system to keep your vault configuration secure against
681 access by even moderately determined attackers!
683 """
684 total_size = key_size + iv_size 1bac
685 buffer = bytearray() 1bac
686 last_block = b"" 1bac
687 salt = b"" 1bac
688 logger.debug( 1bac
689 _msg.TranslatedString(
690 _msg.DebugMsgTemplate.VAULT_NATIVE_EVP_BYTESTOKEY_INIT,
691 data=_h(data),
692 salt=_h(salt),
693 key_size=key_size,
694 iv_size=iv_size,
695 buffer_length=len(buffer),
696 buffer=_h(buffer),
697 ),
698 )
699 while len(buffer) < total_size: 1bac
700 with warnings.catch_warnings(): 1bac
701 warnings.simplefilter( 1bac
702 "ignore", crypt_utils.CryptographyDeprecationWarning
703 )
704 block = hashes.Hash(hashes.MD5()) 1bac
705 block.update(last_block) 1bac
706 block.update(data) 1bac
707 block.update(salt) 1bac
708 last_block = block.finalize() 1bac
709 buffer.extend(last_block) 1bac
710 logger.debug( 1bac
711 _msg.TranslatedString(
712 _msg.DebugMsgTemplate.VAULT_NATIVE_EVP_BYTESTOKEY_ROUND,
713 buffer_length=len(buffer),
714 buffer=_h(buffer),
715 ),
716 )
717 logger.debug( 1bac
718 _msg.TranslatedString(
719 _msg.DebugMsgTemplate.VAULT_NATIVE_EVP_BYTESTOKEY_RESULT,
720 enc_key=_h(buffer[:key_size]),
721 iv=_h(buffer[key_size:total_size]),
722 ),
723 )
724 return bytes(buffer[:key_size]), bytes(buffer[key_size:total_size]) 1bac
726 def _make_decryptor(self) -> ciphers.CipherContext:
727 """Return the cipher context object used for decryption.
729 This is a standard AES256-CBC cipher context. The encryption key
730 and the IV are derived via the OpenSSL `EVP_BytesToKey` function
731 (using MD5, no salt, and one iteration). This is what the
732 Node.js `crypto` library (v21 series and older) used in its
733 implementation of `crypto.createCipher("aes256", password)`.
735 Danger: Insecure use of cryptography
736 This function makes use of (an implementation of) the
737 OpenSSL function `EVP_BytesToKey`, which generates
738 cryptographically weak keys, without any attempts at
739 mitigating its insecurity. We provide this function for the
740 purpose of interoperability with existing vault
741 installations. Do not rely on this system to keep your
742 vault configuration secure against access by even moderately
743 determined attackers!
745 """
746 with self._consistency_lock: 1bac
747 data = base64.standard_b64encode(self._iv + self._encryption_key) 1bac
748 encryption_key, iv = self._evp_bytestokey_md5_one_iteration_no_salt( 1bac
749 data, key_size=32, iv_size=16
750 )
751 return ciphers.Cipher( 1bac
752 algorithms.AES256(encryption_key), modes.CBC(iv)
753 ).decryptor()
756if __name__ == "__main__":
757 import os
759 logging.basicConfig(level=("DEBUG" if os.getenv("DEBUG") else "WARNING"))
760 with exporter.get_vault_path().open("rb") as infile:
761 contents = base64.standard_b64decode(infile.read())
762 password = exporter.get_vault_key()
763 try:
764 config = VaultNativeV03ConfigParser(contents, password)()
765 except ValueError:
766 config = VaultNativeV02ConfigParser(contents, password)()
767 print(json.dumps(config, indent=2, sort_keys=True)) # noqa: T201