key.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. import binascii
  2. import hmac
  3. import os
  4. import textwrap
  5. from binascii import a2b_base64, b2a_base64, hexlify
  6. from hashlib import sha256, pbkdf2_hmac
  7. from typing import Literal, Callable, ClassVar
  8. from ..logger import create_logger
  9. logger = create_logger()
  10. import argon2.low_level
  11. from ..constants import * # NOQA
  12. from ..helpers import StableDict
  13. from ..helpers import Error, IntegrityError
  14. from ..helpers import get_keys_dir
  15. from ..helpers import get_limited_unpacker
  16. from ..helpers import bin_to_hex
  17. from ..helpers.passphrase import Passphrase, PasswordRetriesExceeded, PassphraseWrong
  18. from ..helpers import msgpack
  19. from ..helpers import workarounds
  20. from ..item import Key, EncryptedKey, want_bytes
  21. from ..manifest import Manifest
  22. from ..platform import SaveFile
  23. from ..repoobj import RepoObj
  24. from .low_level import AES, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256, hkdf_hmac_sha512
  25. from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b, AES256_OCB, CHACHA20_POLY1305
  26. from . import low_level
  27. # workaround for lost passphrase or key in "authenticated" or "authenticated-blake2" mode
  28. AUTHENTICATED_NO_KEY = "authenticated_no_key" in workarounds
  29. class UnsupportedPayloadError(Error):
  30. """Unsupported payload type {}. A newer version is required to access this repository."""
  31. class UnsupportedManifestError(Error):
  32. """Unsupported manifest envelope. A newer version is required to access this repository."""
  33. class KeyfileNotFoundError(Error):
  34. """No key file for repository {} found in {}."""
  35. class KeyfileInvalidError(Error):
  36. """Invalid key file for repository {} found in {}."""
  37. class KeyfileMismatchError(Error):
  38. """Mismatch between repository {} and key file {}."""
  39. class RepoKeyNotFoundError(Error):
  40. """No key entry found in the config of repository {}."""
  41. class UnsupportedKeyFormatError(Error):
  42. """Your borg key is stored in an unsupported format. Try using a newer version of borg."""
  43. class TAMRequiredError(IntegrityError):
  44. __doc__ = textwrap.dedent(
  45. """
  46. Manifest is unauthenticated, but it is required for this repository. Is somebody attacking you?
  47. """
  48. ).strip()
  49. traceback = False
  50. class ArchiveTAMRequiredError(TAMRequiredError):
  51. __doc__ = textwrap.dedent(
  52. """
  53. Archive '{}' is unauthenticated, but it is required for this repository.
  54. """
  55. ).strip()
  56. traceback = False
  57. class TAMInvalid(IntegrityError):
  58. __doc__ = IntegrityError.__doc__
  59. traceback = False
  60. def __init__(self):
  61. # Error message becomes: "Data integrity error: Manifest authentication did not verify"
  62. super().__init__("Manifest authentication did not verify")
  63. class ArchiveTAMInvalid(IntegrityError):
  64. __doc__ = IntegrityError.__doc__
  65. traceback = False
  66. def __init__(self):
  67. # Error message becomes: "Data integrity error: Archive authentication did not verify"
  68. super().__init__("Archive authentication did not verify")
  69. class TAMUnsupportedSuiteError(IntegrityError):
  70. """Could not verify manifest: Unsupported suite {!r}; a newer version is needed."""
  71. traceback = False
  72. def key_creator(repository, args, *, other_key=None):
  73. for key in AVAILABLE_KEY_TYPES:
  74. if key.ARG_NAME == args.encryption:
  75. assert key.ARG_NAME is not None
  76. return key.create(repository, args, other_key=other_key)
  77. else:
  78. raise ValueError('Invalid encryption mode "%s"' % args.encryption)
  79. def key_argument_names():
  80. return [key.ARG_NAME for key in AVAILABLE_KEY_TYPES if key.ARG_NAME]
  81. def identify_key(manifest_data):
  82. key_type = manifest_data[0]
  83. if key_type == KeyType.PASSPHRASE: # legacy, see comment in KeyType class.
  84. return RepoKey
  85. for key in LEGACY_KEY_TYPES + AVAILABLE_KEY_TYPES:
  86. if key.TYPE == key_type:
  87. return key
  88. else:
  89. raise UnsupportedPayloadError(key_type)
  90. def key_factory(repository, manifest_chunk, *, ro_cls=RepoObj):
  91. manifest_data = ro_cls.extract_crypted_data(manifest_chunk)
  92. assert manifest_data, "manifest data must not be zero bytes long"
  93. return identify_key(manifest_data).detect(repository, manifest_data)
  94. def uses_same_chunker_secret(other_key, key):
  95. """is the chunker secret the same?"""
  96. # avoid breaking the deduplication by a different chunker secret
  97. same_chunker_secret = other_key.chunk_seed == key.chunk_seed
  98. return same_chunker_secret
  99. def uses_same_id_hash(other_key, key):
  100. """other_key -> key upgrade: is the id hash the same?"""
  101. # avoid breaking the deduplication by changing the id hash
  102. old_sha256_ids = (PlaintextKey,)
  103. new_sha256_ids = (PlaintextKey,)
  104. old_hmac_sha256_ids = (RepoKey, KeyfileKey, AuthenticatedKey)
  105. new_hmac_sha256_ids = (AESOCBRepoKey, AESOCBKeyfileKey, CHPORepoKey, CHPOKeyfileKey, AuthenticatedKey)
  106. old_blake2_ids = (Blake2RepoKey, Blake2KeyfileKey, Blake2AuthenticatedKey)
  107. new_blake2_ids = (
  108. Blake2AESOCBRepoKey,
  109. Blake2AESOCBKeyfileKey,
  110. Blake2CHPORepoKey,
  111. Blake2CHPOKeyfileKey,
  112. Blake2AuthenticatedKey,
  113. )
  114. same_ids = (
  115. isinstance(other_key, old_hmac_sha256_ids + new_hmac_sha256_ids)
  116. and isinstance(key, new_hmac_sha256_ids)
  117. or isinstance(other_key, old_blake2_ids + new_blake2_ids)
  118. and isinstance(key, new_blake2_ids)
  119. or isinstance(other_key, old_sha256_ids + new_sha256_ids)
  120. and isinstance(key, new_sha256_ids)
  121. )
  122. return same_ids
  123. class KeyBase:
  124. # Numeric key type ID, must fit in one byte.
  125. TYPE: int = None # override in subclasses
  126. # set of key type IDs the class can handle as input
  127. TYPES_ACCEPTABLE: set[int] = None # override in subclasses
  128. # Human-readable name
  129. NAME = "UNDEFINED"
  130. # Name used in command line / API (e.g. borg init --encryption=...)
  131. ARG_NAME = "UNDEFINED"
  132. # Storage type (no key blob storage / keyfile / repo)
  133. STORAGE: ClassVar[str] = KeyBlobStorage.NO_STORAGE
  134. # Seed for the buzhash chunker (borg.algorithms.chunker.Chunker)
  135. # type is int
  136. chunk_seed: int = None
  137. # Whether this *particular instance* is encrypted from a practical point of view,
  138. # i.e. when it's using encryption with a empty passphrase, then
  139. # that may be *technically* called encryption, but for all intents and purposes
  140. # that's as good as not encrypting in the first place, and this member should be False.
  141. #
  142. # The empty passphrase is also special because Borg tries it first when no passphrase
  143. # was supplied, and if an empty passphrase works, then Borg won't ask for one.
  144. logically_encrypted = False
  145. def __init__(self, repository):
  146. self.TYPE_STR = bytes([self.TYPE])
  147. self.repository = repository
  148. self.target = None # key location file path / repo obj
  149. self.copy_crypt_key = False
  150. def id_hash(self, data):
  151. """Return HMAC hash using the "id" HMAC key"""
  152. raise NotImplementedError
  153. def encrypt(self, id, data):
  154. pass
  155. def decrypt(self, id, data):
  156. pass
  157. def assert_id(self, id, data):
  158. if id and id != Manifest.MANIFEST_ID:
  159. id_computed = self.id_hash(data)
  160. if not hmac.compare_digest(id_computed, id):
  161. raise IntegrityError("Chunk %s: id verification failed" % bin_to_hex(id))
  162. def assert_type(self, type_byte, id=None):
  163. if type_byte not in self.TYPES_ACCEPTABLE:
  164. id_str = bin_to_hex(id) if id is not None else "(unknown)"
  165. raise IntegrityError(f"Chunk {id_str}: Invalid encryption envelope")
  166. def _tam_key(self, salt, context):
  167. return hkdf_hmac_sha512(
  168. ikm=self.id_key + self.crypt_key,
  169. salt=salt,
  170. info=b"borg-metadata-authentication-" + context,
  171. output_length=64,
  172. )
  173. def pack_and_authenticate_metadata(self, metadata_dict, context=b"manifest", salt=None):
  174. if salt is None:
  175. salt = os.urandom(64)
  176. metadata_dict = StableDict(metadata_dict)
  177. tam = metadata_dict["tam"] = StableDict({"type": "HKDF_HMAC_SHA512", "hmac": bytes(64), "salt": salt})
  178. packed = msgpack.packb(metadata_dict)
  179. tam_key = self._tam_key(salt, context)
  180. tam["hmac"] = hmac.digest(tam_key, packed, "sha512")
  181. return msgpack.packb(metadata_dict)
  182. def unpack_and_verify_manifest(self, data):
  183. """Unpack msgpacked *data* and return manifest."""
  184. if data.startswith(b"\xc1" * 4):
  185. # This is a manifest from the future, we can't read it.
  186. raise UnsupportedManifestError()
  187. data = bytearray(data)
  188. unpacker = get_limited_unpacker("manifest")
  189. unpacker.feed(data)
  190. unpacked = unpacker.unpack()
  191. if AUTHENTICATED_NO_KEY:
  192. return unpacked
  193. if "tam" not in unpacked:
  194. raise TAMRequiredError(self.repository._location.canonical_path())
  195. tam = unpacked.pop("tam", None)
  196. if not isinstance(tam, dict):
  197. raise TAMInvalid()
  198. tam_type = tam.get("type", "<none>")
  199. if tam_type != "HKDF_HMAC_SHA512":
  200. raise TAMUnsupportedSuiteError(repr(tam_type))
  201. tam_hmac = tam.get("hmac")
  202. tam_salt = tam.get("salt")
  203. if not isinstance(tam_salt, (bytes, str)) or not isinstance(tam_hmac, (bytes, str)):
  204. raise TAMInvalid()
  205. tam_hmac = want_bytes(tam_hmac) # legacy
  206. tam_salt = want_bytes(tam_salt) # legacy
  207. offset = data.index(tam_hmac)
  208. data[offset : offset + 64] = bytes(64)
  209. tam_key = self._tam_key(tam_salt, context=b"manifest")
  210. calculated_hmac = hmac.digest(tam_key, data, "sha512")
  211. if not hmac.compare_digest(calculated_hmac, tam_hmac):
  212. raise TAMInvalid()
  213. logger.debug("TAM-verified manifest")
  214. return unpacked
  215. def unpack_and_verify_archive(self, data):
  216. """Unpack msgpacked *data* and return (object, salt)."""
  217. data = bytearray(data)
  218. unpacker = get_limited_unpacker("archive")
  219. unpacker.feed(data)
  220. unpacked = unpacker.unpack()
  221. if "tam" not in unpacked:
  222. archive_name = unpacked.get("name", "<unknown>")
  223. raise ArchiveTAMRequiredError(archive_name)
  224. tam = unpacked.pop("tam", None)
  225. if not isinstance(tam, dict):
  226. raise ArchiveTAMInvalid()
  227. tam_type = tam.get("type", "<none>")
  228. if tam_type != "HKDF_HMAC_SHA512":
  229. raise TAMUnsupportedSuiteError(repr(tam_type))
  230. tam_hmac = tam.get("hmac")
  231. tam_salt = tam.get("salt")
  232. if not isinstance(tam_salt, (bytes, str)) or not isinstance(tam_hmac, (bytes, str)):
  233. raise ArchiveTAMInvalid()
  234. tam_hmac = want_bytes(tam_hmac) # legacy
  235. tam_salt = want_bytes(tam_salt) # legacy
  236. offset = data.index(tam_hmac)
  237. data[offset : offset + 64] = bytes(64)
  238. tam_key = self._tam_key(tam_salt, context=b"archive")
  239. calculated_hmac = hmac.digest(tam_key, data, "sha512")
  240. if not hmac.compare_digest(calculated_hmac, tam_hmac):
  241. raise ArchiveTAMInvalid()
  242. logger.debug("TAM-verified archive")
  243. return unpacked, tam_salt
  244. class PlaintextKey(KeyBase):
  245. TYPE = KeyType.PLAINTEXT
  246. TYPES_ACCEPTABLE = {TYPE}
  247. NAME = "plaintext"
  248. ARG_NAME = "none"
  249. chunk_seed = 0
  250. logically_encrypted = False
  251. @classmethod
  252. def create(cls, repository, args, **kw):
  253. logger.info('Encryption NOT enabled.\nUse the "--encryption=repokey|keyfile" to enable encryption.')
  254. return cls(repository)
  255. @classmethod
  256. def detect(cls, repository, manifest_data):
  257. return cls(repository)
  258. def id_hash(self, data):
  259. return sha256(data).digest()
  260. def encrypt(self, id, data):
  261. return b"".join([self.TYPE_STR, data])
  262. def decrypt(self, id, data):
  263. self.assert_type(data[0], id)
  264. return memoryview(data)[1:]
  265. def _tam_key(self, salt, context):
  266. return salt + context
  267. def random_blake2b_256_key():
  268. # This might look a bit curious, but is the same construction used in the keyed mode of BLAKE2b.
  269. # Why limit the key to 64 bytes and pad it with 64 nulls nonetheless? The answer is that BLAKE2b
  270. # has a 128 byte block size, but only 64 bytes of internal state (this is also referred to as a
  271. # "local wide pipe" design, because the compression function transforms (block, state) => state,
  272. # and len(block) >= len(state), hence wide.)
  273. # In other words, a key longer than 64 bytes would have simply no advantage, since the function
  274. # has no way of propagating more than 64 bytes of entropy internally.
  275. # It's padded to a full block so that the key is never buffered internally by blake2b_update, ie.
  276. # it remains in a single memory location that can be tracked and could be erased securely, if we
  277. # wanted to.
  278. return os.urandom(64) + bytes(64)
  279. class ID_BLAKE2b_256:
  280. """
  281. Key mix-in class for using BLAKE2b-256 for the id key.
  282. The id_key length must be 32 bytes.
  283. """
  284. def id_hash(self, data):
  285. return blake2b_256(self.id_key, data)
  286. def init_from_random_data(self):
  287. super().init_from_random_data()
  288. enc_key = os.urandom(32)
  289. enc_hmac_key = random_blake2b_256_key()
  290. self.crypt_key = enc_key + enc_hmac_key
  291. self.id_key = random_blake2b_256_key()
  292. class ID_HMAC_SHA_256:
  293. """
  294. Key mix-in class for using HMAC-SHA-256 for the id key.
  295. The id_key length must be 32 bytes.
  296. """
  297. def id_hash(self, data):
  298. return hmac_sha256(self.id_key, data)
  299. class AESKeyBase(KeyBase):
  300. """
  301. Chunks are encrypted using 256bit AES in Counter Mode (CTR)
  302. Payload layout: TYPE(1) + HMAC(32) + NONCE(8) + CIPHERTEXT
  303. To reduce payload size only 8 bytes of the 16 bytes nonce is saved
  304. in the payload, the first 8 bytes are always zeros. This does not
  305. affect security but limits the maximum repository capacity to
  306. only 295 exabytes!
  307. """
  308. PAYLOAD_OVERHEAD = 1 + 32 + 8 # TYPE + HMAC + NONCE
  309. CIPHERSUITE: Callable = None # override in derived class
  310. logically_encrypted = True
  311. def encrypt(self, id, data):
  312. # legacy, this is only used by the tests.
  313. next_iv = self.cipher.next_iv()
  314. return self.cipher.encrypt(data, header=self.TYPE_STR, iv=next_iv)
  315. def decrypt(self, id, data):
  316. self.assert_type(data[0], id)
  317. try:
  318. return self.cipher.decrypt(data)
  319. except IntegrityError as e:
  320. raise IntegrityError(f"Chunk {bin_to_hex(id)}: Could not decrypt [{str(e)}]")
  321. def init_from_given_data(self, *, crypt_key, id_key, chunk_seed):
  322. assert len(crypt_key) in (32 + 32, 32 + 128)
  323. assert len(id_key) in (32, 128)
  324. assert isinstance(chunk_seed, int)
  325. self.crypt_key = crypt_key
  326. self.id_key = id_key
  327. self.chunk_seed = chunk_seed
  328. def init_from_random_data(self):
  329. data = os.urandom(100)
  330. chunk_seed = bytes_to_int(data[96:100])
  331. # Convert to signed int32
  332. if chunk_seed & 0x80000000:
  333. chunk_seed = chunk_seed - 0xFFFFFFFF - 1
  334. self.init_from_given_data(crypt_key=data[0:64], id_key=data[64:96], chunk_seed=chunk_seed)
  335. def init_ciphers(self, manifest_data=None):
  336. enc_key, enc_hmac_key = self.crypt_key[0:32], self.crypt_key[32:]
  337. self.cipher = self.CIPHERSUITE(mac_key=enc_hmac_key, enc_key=enc_key, header_len=1, aad_offset=1)
  338. if manifest_data is None:
  339. nonce = 0
  340. else:
  341. self.assert_type(manifest_data[0])
  342. # manifest_blocks is a safe upper bound on the amount of cipher blocks needed
  343. # to encrypt the manifest. depending on the ciphersuite and overhead, it might
  344. # be a bit too high, but that does not matter.
  345. manifest_blocks = num_cipher_blocks(len(manifest_data))
  346. nonce = self.cipher.extract_iv(manifest_data) + manifest_blocks
  347. self.cipher.set_iv(nonce)
  348. class FlexiKey:
  349. FILE_ID = "BORG_KEY"
  350. STORAGE: ClassVar[str] = KeyBlobStorage.NO_STORAGE # override in subclass
  351. @classmethod
  352. def detect(cls, repository, manifest_data):
  353. key = cls(repository)
  354. target = key.find_key()
  355. prompt = "Enter passphrase for key %s: " % target
  356. passphrase = Passphrase.env_passphrase()
  357. if passphrase is None:
  358. passphrase = Passphrase()
  359. if not key.load(target, passphrase):
  360. for retry in range(0, 3):
  361. passphrase = Passphrase.getpass(prompt)
  362. if key.load(target, passphrase):
  363. break
  364. else:
  365. raise PasswordRetriesExceeded
  366. else:
  367. if not key.load(target, passphrase):
  368. raise PassphraseWrong
  369. key.init_ciphers(manifest_data)
  370. key._passphrase = passphrase
  371. return key
  372. def _load(self, key_data, passphrase):
  373. cdata = a2b_base64(key_data)
  374. data = self.decrypt_key_file(cdata, passphrase)
  375. if data:
  376. data = msgpack.unpackb(data)
  377. key = Key(internal_dict=data)
  378. if key.version not in (1, 2): # legacy: item.Key can still process v1 keys
  379. raise UnsupportedKeyFormatError()
  380. self.repository_id = key.repository_id
  381. self.crypt_key = key.crypt_key
  382. self.id_key = key.id_key
  383. self.chunk_seed = key.chunk_seed
  384. return True
  385. return False
  386. def decrypt_key_file(self, data, passphrase):
  387. unpacker = get_limited_unpacker("key")
  388. unpacker.feed(data)
  389. data = unpacker.unpack()
  390. encrypted_key = EncryptedKey(internal_dict=data)
  391. if encrypted_key.version != 1:
  392. raise UnsupportedKeyFormatError()
  393. else:
  394. self._encrypted_key_algorithm = encrypted_key.algorithm
  395. if encrypted_key.algorithm == "sha256":
  396. return self.decrypt_key_file_pbkdf2(encrypted_key, passphrase)
  397. elif encrypted_key.algorithm == "argon2 chacha20-poly1305":
  398. return self.decrypt_key_file_argon2(encrypted_key, passphrase)
  399. else:
  400. raise UnsupportedKeyFormatError()
  401. @staticmethod
  402. def pbkdf2(passphrase, salt, iterations, output_len_in_bytes):
  403. if os.environ.get("BORG_TESTONLY_WEAKEN_KDF") == "1":
  404. iterations = 1
  405. return pbkdf2_hmac("sha256", passphrase.encode("utf-8"), salt, iterations, output_len_in_bytes)
  406. @staticmethod
  407. def argon2(
  408. passphrase: str,
  409. output_len_in_bytes: int,
  410. salt: bytes,
  411. time_cost: int,
  412. memory_cost: int,
  413. parallelism: int,
  414. type: Literal["i", "d", "id"],
  415. ) -> bytes:
  416. if os.environ.get("BORG_TESTONLY_WEAKEN_KDF") == "1":
  417. time_cost = 1
  418. parallelism = 1
  419. # 8 is the smallest value that avoids the "Memory cost is too small" exception
  420. memory_cost = 8
  421. type_map = {"i": argon2.low_level.Type.I, "d": argon2.low_level.Type.D, "id": argon2.low_level.Type.ID}
  422. key = argon2.low_level.hash_secret_raw(
  423. secret=passphrase.encode("utf-8"),
  424. hash_len=output_len_in_bytes,
  425. salt=salt,
  426. time_cost=time_cost,
  427. memory_cost=memory_cost,
  428. parallelism=parallelism,
  429. type=type_map[type],
  430. )
  431. return key
  432. def decrypt_key_file_pbkdf2(self, encrypted_key, passphrase):
  433. key = self.pbkdf2(passphrase, encrypted_key.salt, encrypted_key.iterations, 32)
  434. data = AES(key, b"\0" * 16).decrypt(encrypted_key.data)
  435. if hmac.compare_digest(hmac_sha256(key, data), encrypted_key.hash):
  436. return data
  437. return None
  438. def decrypt_key_file_argon2(self, encrypted_key, passphrase):
  439. key = self.argon2(
  440. passphrase,
  441. output_len_in_bytes=32,
  442. salt=encrypted_key.salt,
  443. time_cost=encrypted_key.argon2_time_cost,
  444. memory_cost=encrypted_key.argon2_memory_cost,
  445. parallelism=encrypted_key.argon2_parallelism,
  446. type=encrypted_key.argon2_type,
  447. )
  448. ae_cipher = CHACHA20_POLY1305(key=key, iv=0, header_len=0, aad_offset=0)
  449. try:
  450. return ae_cipher.decrypt(encrypted_key.data)
  451. except low_level.IntegrityError:
  452. return None
  453. def encrypt_key_file(self, data, passphrase, algorithm):
  454. if algorithm == "sha256":
  455. return self.encrypt_key_file_pbkdf2(data, passphrase)
  456. elif algorithm == "argon2 chacha20-poly1305":
  457. return self.encrypt_key_file_argon2(data, passphrase)
  458. else:
  459. raise ValueError(f"Unexpected algorithm: {algorithm}")
  460. def encrypt_key_file_pbkdf2(self, data, passphrase):
  461. salt = os.urandom(32)
  462. iterations = PBKDF2_ITERATIONS
  463. key = self.pbkdf2(passphrase, salt, iterations, 32)
  464. hash = hmac_sha256(key, data)
  465. cdata = AES(key, b"\0" * 16).encrypt(data)
  466. enc_key = EncryptedKey(version=1, salt=salt, iterations=iterations, algorithm="sha256", hash=hash, data=cdata)
  467. return msgpack.packb(enc_key.as_dict())
  468. def encrypt_key_file_argon2(self, data, passphrase):
  469. salt = os.urandom(ARGON2_SALT_BYTES)
  470. key = self.argon2(passphrase, output_len_in_bytes=32, salt=salt, **ARGON2_ARGS)
  471. ae_cipher = CHACHA20_POLY1305(key=key, iv=0, header_len=0, aad_offset=0)
  472. encrypted_key = EncryptedKey(
  473. version=1,
  474. algorithm="argon2 chacha20-poly1305",
  475. salt=salt,
  476. data=ae_cipher.encrypt(data),
  477. **{"argon2_" + k: v for k, v in ARGON2_ARGS.items()},
  478. )
  479. return msgpack.packb(encrypted_key.as_dict())
  480. def _save(self, passphrase, algorithm):
  481. key = Key(
  482. version=2,
  483. repository_id=self.repository_id,
  484. crypt_key=self.crypt_key,
  485. id_key=self.id_key,
  486. chunk_seed=self.chunk_seed,
  487. )
  488. data = self.encrypt_key_file(msgpack.packb(key.as_dict()), passphrase, algorithm)
  489. key_data = "\n".join(textwrap.wrap(b2a_base64(data).decode("ascii")))
  490. return key_data
  491. def change_passphrase(self, passphrase=None):
  492. if passphrase is None:
  493. passphrase = Passphrase.new(allow_empty=True)
  494. self.save(self.target, passphrase, algorithm=self._encrypted_key_algorithm)
  495. @classmethod
  496. def create(cls, repository, args, *, other_key=None):
  497. key = cls(repository)
  498. key.repository_id = repository.id
  499. if other_key is not None:
  500. if isinstance(other_key, PlaintextKey):
  501. raise Error("Copying key material from an unencrypted repository is not possible.")
  502. if isinstance(key, AESKeyBase):
  503. # user must use an AEADKeyBase subclass (AEAD modes with session keys)
  504. raise Error("Copying key material to an AES-CTR based mode is insecure and unsupported.")
  505. if not uses_same_id_hash(other_key, key):
  506. raise Error("You must keep the same ID hash (HMAC-SHA256 or BLAKE2b) or deduplication will break.")
  507. if other_key.copy_crypt_key:
  508. # give the user the option to use the same authenticated encryption (AE) key
  509. crypt_key = other_key.crypt_key
  510. else:
  511. # borg transfer re-encrypts all data anyway, thus we can default to a new, random AE key
  512. crypt_key = os.urandom(64)
  513. key.init_from_given_data(crypt_key=crypt_key, id_key=other_key.id_key, chunk_seed=other_key.chunk_seed)
  514. passphrase = other_key._passphrase
  515. else:
  516. key.init_from_random_data()
  517. passphrase = Passphrase.new(allow_empty=True)
  518. key.init_ciphers()
  519. target = key.get_new_target(args)
  520. key.save(target, passphrase, create=True, algorithm=KEY_ALGORITHMS["argon2"])
  521. logger.info('Key in "%s" created.' % target)
  522. logger.info("Keep this key safe. Your data will be inaccessible without it.")
  523. return key
  524. def sanity_check(self, filename, id):
  525. file_id = self.FILE_ID.encode() + b" "
  526. repo_id = hexlify(id)
  527. with open(filename, "rb") as fd:
  528. # we do the magic / id check in binary mode to avoid stumbling over
  529. # decoding errors if somebody has binary files in the keys dir for some reason.
  530. if fd.read(len(file_id)) != file_id:
  531. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  532. if fd.read(len(repo_id)) != repo_id:
  533. raise KeyfileMismatchError(self.repository._location.canonical_path(), filename)
  534. # we get here if it really looks like a borg key for this repo,
  535. # do some more checks that are close to how borg reads/parses the key.
  536. with open(filename, "r") as fd:
  537. lines = fd.readlines()
  538. if len(lines) < 2:
  539. logger.warning(f"borg key sanity check: expected 2+ lines total. [{filename}]")
  540. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  541. if len(lines[0].rstrip()) > len(file_id) + len(repo_id):
  542. logger.warning(f"borg key sanity check: key line 1 seems too long. [{filename}]")
  543. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  544. key_b64 = "".join(lines[1:])
  545. try:
  546. key = a2b_base64(key_b64)
  547. except binascii.Error:
  548. logger.warning(f"borg key sanity check: key line 2+ does not look like base64. [{filename}]")
  549. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  550. if len(key) < 20:
  551. # this is in no way a precise check, usually we have about 400b key data.
  552. logger.warning(
  553. f"borg key sanity check: binary encrypted key data from key line 2+ suspiciously short."
  554. f" [{filename}]"
  555. )
  556. raise KeyfileInvalidError(self.repository._location.canonical_path(), filename)
  557. # looks good!
  558. return filename
  559. def find_key(self):
  560. if self.STORAGE == KeyBlobStorage.KEYFILE:
  561. keyfile = self._find_key_file_from_environment()
  562. if keyfile is not None:
  563. return self.sanity_check(keyfile, self.repository.id)
  564. keyfile = self._find_key_in_keys_dir()
  565. if keyfile is not None:
  566. return keyfile
  567. raise KeyfileNotFoundError(self.repository._location.canonical_path(), get_keys_dir())
  568. elif self.STORAGE == KeyBlobStorage.REPO:
  569. loc = self.repository._location.canonical_path()
  570. key = self.repository.load_key()
  571. if not key:
  572. # if we got an empty key, it means there is no key.
  573. raise RepoKeyNotFoundError(loc) from None
  574. return loc
  575. else:
  576. raise TypeError("Unsupported borg key storage type")
  577. def get_existing_or_new_target(self, args):
  578. keyfile = self._find_key_file_from_environment()
  579. if keyfile is not None:
  580. return keyfile
  581. keyfile = self._find_key_in_keys_dir()
  582. if keyfile is not None:
  583. return keyfile
  584. return self._get_new_target_in_keys_dir(args)
  585. def _find_key_in_keys_dir(self):
  586. id = self.repository.id
  587. keys_dir = get_keys_dir()
  588. for name in os.listdir(keys_dir):
  589. filename = os.path.join(keys_dir, name)
  590. try:
  591. return self.sanity_check(filename, id)
  592. except (KeyfileInvalidError, KeyfileMismatchError):
  593. pass
  594. def get_new_target(self, args):
  595. if self.STORAGE == KeyBlobStorage.KEYFILE:
  596. keyfile = self._find_key_file_from_environment()
  597. if keyfile is not None:
  598. return keyfile
  599. return self._get_new_target_in_keys_dir(args)
  600. elif self.STORAGE == KeyBlobStorage.REPO:
  601. return self.repository
  602. else:
  603. raise TypeError("Unsupported borg key storage type")
  604. def _find_key_file_from_environment(self):
  605. keyfile = os.environ.get("BORG_KEY_FILE")
  606. if keyfile:
  607. return os.path.abspath(keyfile)
  608. def _get_new_target_in_keys_dir(self, args):
  609. filename = args.location.to_key_filename()
  610. path = filename
  611. i = 1
  612. while os.path.exists(path):
  613. i += 1
  614. path = filename + ".%d" % i
  615. return path
  616. def load(self, target, passphrase):
  617. if self.STORAGE == KeyBlobStorage.KEYFILE:
  618. with open(target) as fd:
  619. key_data = "".join(fd.readlines()[1:])
  620. elif self.STORAGE == KeyBlobStorage.REPO:
  621. # While the repository is encrypted, we consider a repokey repository with a blank
  622. # passphrase an unencrypted repository.
  623. self.logically_encrypted = passphrase != ""
  624. # what we get in target is just a repo location, but we already have the repo obj:
  625. target = self.repository
  626. key_data = target.load_key()
  627. if not key_data:
  628. # if we got an empty key, it means there is no key.
  629. loc = target._location.canonical_path()
  630. raise RepoKeyNotFoundError(loc) from None
  631. key_data = key_data.decode("utf-8") # remote repo: msgpack issue #99, getting bytes
  632. else:
  633. raise TypeError("Unsupported borg key storage type")
  634. success = self._load(key_data, passphrase)
  635. if success:
  636. self.target = target
  637. return success
  638. def save(self, target, passphrase, algorithm, create=False):
  639. key_data = self._save(passphrase, algorithm)
  640. if self.STORAGE == KeyBlobStorage.KEYFILE:
  641. if create and os.path.isfile(target):
  642. # if a new keyfile key repository is created, ensure that an existing keyfile of another
  643. # keyfile key repo is not accidentally overwritten by careless use of the BORG_KEY_FILE env var.
  644. # see issue #6036
  645. raise Error('Aborting because key in "%s" already exists.' % target)
  646. with SaveFile(target) as fd:
  647. fd.write(f"{self.FILE_ID} {bin_to_hex(self.repository_id)}\n")
  648. fd.write(key_data)
  649. fd.write("\n")
  650. elif self.STORAGE == KeyBlobStorage.REPO:
  651. self.logically_encrypted = passphrase != ""
  652. key_data = key_data.encode("utf-8") # remote repo: msgpack issue #99, giving bytes
  653. target.save_key(key_data)
  654. else:
  655. raise TypeError("Unsupported borg key storage type")
  656. self.target = target
  657. def remove(self, target):
  658. if self.STORAGE == KeyBlobStorage.KEYFILE:
  659. os.remove(target)
  660. elif self.STORAGE == KeyBlobStorage.REPO:
  661. target.save_key(b"") # save empty key (no new api at remote repo necessary)
  662. else:
  663. raise TypeError("Unsupported borg key storage type")
  664. class KeyfileKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey):
  665. TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE}
  666. TYPE = KeyType.KEYFILE
  667. NAME = "key file"
  668. ARG_NAME = "keyfile"
  669. STORAGE = KeyBlobStorage.KEYFILE
  670. CIPHERSUITE = AES256_CTR_HMAC_SHA256
  671. class RepoKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey):
  672. TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE}
  673. TYPE = KeyType.REPO
  674. NAME = "repokey"
  675. ARG_NAME = "repokey"
  676. STORAGE = KeyBlobStorage.REPO
  677. CIPHERSUITE = AES256_CTR_HMAC_SHA256
  678. class Blake2KeyfileKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey):
  679. TYPES_ACCEPTABLE = {KeyType.BLAKE2KEYFILE, KeyType.BLAKE2REPO}
  680. TYPE = KeyType.BLAKE2KEYFILE
  681. NAME = "key file BLAKE2b"
  682. ARG_NAME = "keyfile-blake2"
  683. STORAGE = KeyBlobStorage.KEYFILE
  684. CIPHERSUITE = AES256_CTR_BLAKE2b
  685. class Blake2RepoKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey):
  686. TYPES_ACCEPTABLE = {KeyType.BLAKE2KEYFILE, KeyType.BLAKE2REPO}
  687. TYPE = KeyType.BLAKE2REPO
  688. NAME = "repokey BLAKE2b"
  689. ARG_NAME = "repokey-blake2"
  690. STORAGE = KeyBlobStorage.REPO
  691. CIPHERSUITE = AES256_CTR_BLAKE2b
  692. class AuthenticatedKeyBase(AESKeyBase, FlexiKey):
  693. STORAGE = KeyBlobStorage.REPO
  694. # It's only authenticated, not encrypted.
  695. logically_encrypted = False
  696. def _load(self, key_data, passphrase):
  697. if AUTHENTICATED_NO_KEY:
  698. # fake _load if we have no key or passphrase
  699. NOPE = bytes(32) # 256 bit all-zero
  700. self.repository_id = NOPE
  701. self.enc_key = NOPE
  702. self.enc_hmac_key = NOPE
  703. self.id_key = NOPE
  704. self.chunk_seed = 0
  705. self.tam_required = False
  706. return True
  707. return super()._load(key_data, passphrase)
  708. def load(self, target, passphrase):
  709. success = super().load(target, passphrase)
  710. self.logically_encrypted = False
  711. return success
  712. def save(self, target, passphrase, algorithm, create=False):
  713. super().save(target, passphrase, algorithm, create=create)
  714. self.logically_encrypted = False
  715. def init_ciphers(self, manifest_data=None):
  716. if manifest_data is not None:
  717. self.assert_type(manifest_data[0])
  718. def encrypt(self, id, data):
  719. return b"".join([self.TYPE_STR, data])
  720. def decrypt(self, id, data):
  721. self.assert_type(data[0], id)
  722. return memoryview(data)[1:]
  723. class AuthenticatedKey(ID_HMAC_SHA_256, AuthenticatedKeyBase):
  724. TYPE = KeyType.AUTHENTICATED
  725. TYPES_ACCEPTABLE = {TYPE}
  726. NAME = "authenticated"
  727. ARG_NAME = "authenticated"
  728. class Blake2AuthenticatedKey(ID_BLAKE2b_256, AuthenticatedKeyBase):
  729. TYPE = KeyType.BLAKE2AUTHENTICATED
  730. TYPES_ACCEPTABLE = {TYPE}
  731. NAME = "authenticated BLAKE2b"
  732. ARG_NAME = "authenticated-blake2"
  733. # ------------ new crypto ------------
  734. class AEADKeyBase(KeyBase):
  735. """
  736. Chunks are encrypted and authenticated using some AEAD ciphersuite
  737. Layout: suite:4 keytype:4 reserved:8 messageIV:48 sessionID:192 auth_tag:128 payload:... [bits]
  738. ^-------------------- AAD ----------------------------^
  739. Offsets:0 1 2 8 32 48 [bytes]
  740. suite: 1010b for new AEAD crypto, 0000b is old crypto
  741. keytype: see constants.KeyType (suite+keytype)
  742. reserved: all-zero, for future use
  743. messageIV: a counter starting from 0 for all new encrypted messages of one session
  744. sessionID: 192bit random, computed once per session (the session key is derived from this)
  745. auth_tag: authentication tag output of the AEAD cipher (computed over payload and AAD)
  746. payload: encrypted chunk data
  747. """
  748. PAYLOAD_OVERHEAD = 1 + 1 + 6 + 24 + 16 # [bytes], see Layout
  749. CIPHERSUITE: Callable = None # override in subclass
  750. logically_encrypted = True
  751. MAX_IV = 2**48 - 1
  752. def assert_id(self, id, data):
  753. # Comparing the id hash here would not be needed any more for the new AEAD crypto **IF** we
  754. # could be sure that chunks were created by normal (not tampered, not evil) borg code:
  755. # We put the id into AAD when storing the chunk, so it gets into the authentication tag computation.
  756. # when decrypting, we provide the id we **want** as AAD for the auth tag verification, so
  757. # decrypting only succeeds if we got the ciphertext we wrote **for that chunk id**.
  758. # So, basically the **repository** can not cheat on us by giving us a different chunk.
  759. #
  760. # **BUT**, if chunks are created by tampered, evil borg code, the borg client code could put
  761. # a wrong chunkid into AAD and then AEAD-encrypt-and-auth this and store it into the
  762. # repository using this bad chunkid as key (violating the usual chunkid == id_hash(data)).
  763. # Later, when reading such a bad chunk, AEAD-auth-and-decrypt would not notice any
  764. # issue and decrypt successfully.
  765. # Thus, to notice such evil borg activity, we must check for such violations here:
  766. if id and id != Manifest.MANIFEST_ID:
  767. id_computed = self.id_hash(data)
  768. if not hmac.compare_digest(id_computed, id):
  769. raise IntegrityError("Chunk %s: id verification failed" % bin_to_hex(id))
  770. def encrypt(self, id, data):
  771. # to encrypt new data in this session we use always self.cipher and self.sessionid
  772. reserved = b"\0"
  773. iv = self.cipher.next_iv()
  774. if iv > self.MAX_IV: # see the data-structures docs about why the IV range is enough
  775. raise IntegrityError("IV overflow, should never happen.")
  776. iv_48bit = iv.to_bytes(6, "big")
  777. header = self.TYPE_STR + reserved + iv_48bit + self.sessionid
  778. return self.cipher.encrypt(data, header=header, iv=iv, aad=id)
  779. def decrypt(self, id, data):
  780. # to decrypt existing data, we need to get a cipher configured for the sessionid and iv from header
  781. self.assert_type(data[0], id)
  782. iv_48bit = data[2:8]
  783. sessionid = data[8:32]
  784. iv = int.from_bytes(iv_48bit, "big")
  785. cipher = self._get_cipher(sessionid, iv)
  786. try:
  787. return cipher.decrypt(data, aad=id)
  788. except IntegrityError as e:
  789. raise IntegrityError(f"Chunk {bin_to_hex(id)}: Could not decrypt [{str(e)}]")
  790. def init_from_given_data(self, *, crypt_key, id_key, chunk_seed):
  791. assert len(crypt_key) in (32 + 32, 32 + 128)
  792. assert len(id_key) in (32, 128)
  793. assert isinstance(chunk_seed, int)
  794. self.crypt_key = crypt_key
  795. self.id_key = id_key
  796. self.chunk_seed = chunk_seed
  797. def init_from_random_data(self):
  798. data = os.urandom(100)
  799. chunk_seed = bytes_to_int(data[96:100])
  800. # Convert to signed int32
  801. if chunk_seed & 0x80000000:
  802. chunk_seed = chunk_seed - 0xFFFFFFFF - 1
  803. self.init_from_given_data(crypt_key=data[0:64], id_key=data[64:96], chunk_seed=chunk_seed)
  804. def _get_session_key(self, sessionid):
  805. assert len(sessionid) == 24 # 192bit
  806. key = hkdf_hmac_sha512(
  807. ikm=self.crypt_key,
  808. salt=sessionid,
  809. info=b"borg-session-key-" + self.CIPHERSUITE.__name__.encode(),
  810. output_length=32,
  811. )
  812. return key
  813. def _get_cipher(self, sessionid, iv):
  814. assert isinstance(iv, int)
  815. key = self._get_session_key(sessionid)
  816. cipher = self.CIPHERSUITE(key=key, iv=iv, header_len=1 + 1 + 6 + 24, aad_offset=0)
  817. return cipher
  818. def init_ciphers(self, manifest_data=None, iv=0):
  819. # in every new session we start with a fresh sessionid and at iv == 0, manifest_data and iv params are ignored
  820. self.sessionid = os.urandom(24)
  821. self.cipher = self._get_cipher(self.sessionid, iv=0)
  822. class AESOCBKeyfileKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
  823. TYPES_ACCEPTABLE = {KeyType.AESOCBKEYFILE, KeyType.AESOCBREPO}
  824. TYPE = KeyType.AESOCBKEYFILE
  825. NAME = "key file AES-OCB"
  826. ARG_NAME = "keyfile-aes-ocb"
  827. STORAGE = KeyBlobStorage.KEYFILE
  828. CIPHERSUITE = AES256_OCB
  829. class AESOCBRepoKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
  830. TYPES_ACCEPTABLE = {KeyType.AESOCBKEYFILE, KeyType.AESOCBREPO}
  831. TYPE = KeyType.AESOCBREPO
  832. NAME = "repokey AES-OCB"
  833. ARG_NAME = "repokey-aes-ocb"
  834. STORAGE = KeyBlobStorage.REPO
  835. CIPHERSUITE = AES256_OCB
  836. class CHPOKeyfileKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
  837. TYPES_ACCEPTABLE = {KeyType.CHPOKEYFILE, KeyType.CHPOREPO}
  838. TYPE = KeyType.CHPOKEYFILE
  839. NAME = "key file ChaCha20-Poly1305"
  840. ARG_NAME = "keyfile-chacha20-poly1305"
  841. STORAGE = KeyBlobStorage.KEYFILE
  842. CIPHERSUITE = CHACHA20_POLY1305
  843. class CHPORepoKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
  844. TYPES_ACCEPTABLE = {KeyType.CHPOKEYFILE, KeyType.CHPOREPO}
  845. TYPE = KeyType.CHPOREPO
  846. NAME = "repokey ChaCha20-Poly1305"
  847. ARG_NAME = "repokey-chacha20-poly1305"
  848. STORAGE = KeyBlobStorage.REPO
  849. CIPHERSUITE = CHACHA20_POLY1305
  850. class Blake2AESOCBKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
  851. TYPES_ACCEPTABLE = {KeyType.BLAKE2AESOCBKEYFILE, KeyType.BLAKE2AESOCBREPO}
  852. TYPE = KeyType.BLAKE2AESOCBKEYFILE
  853. NAME = "key file BLAKE2b AES-OCB"
  854. ARG_NAME = "keyfile-blake2-aes-ocb"
  855. STORAGE = KeyBlobStorage.KEYFILE
  856. CIPHERSUITE = AES256_OCB
  857. class Blake2AESOCBRepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
  858. TYPES_ACCEPTABLE = {KeyType.BLAKE2AESOCBKEYFILE, KeyType.BLAKE2AESOCBREPO}
  859. TYPE = KeyType.BLAKE2AESOCBREPO
  860. NAME = "repokey BLAKE2b AES-OCB"
  861. ARG_NAME = "repokey-blake2-aes-ocb"
  862. STORAGE = KeyBlobStorage.REPO
  863. CIPHERSUITE = AES256_OCB
  864. class Blake2CHPOKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
  865. TYPES_ACCEPTABLE = {KeyType.BLAKE2CHPOKEYFILE, KeyType.BLAKE2CHPOREPO}
  866. TYPE = KeyType.BLAKE2CHPOKEYFILE
  867. NAME = "key file BLAKE2b ChaCha20-Poly1305"
  868. ARG_NAME = "keyfile-blake2-chacha20-poly1305"
  869. STORAGE = KeyBlobStorage.KEYFILE
  870. CIPHERSUITE = CHACHA20_POLY1305
  871. class Blake2CHPORepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
  872. TYPES_ACCEPTABLE = {KeyType.BLAKE2CHPOKEYFILE, KeyType.BLAKE2CHPOREPO}
  873. TYPE = KeyType.BLAKE2CHPOREPO
  874. NAME = "repokey BLAKE2b ChaCha20-Poly1305"
  875. ARG_NAME = "repokey-blake2-chacha20-poly1305"
  876. STORAGE = KeyBlobStorage.REPO
  877. CIPHERSUITE = CHACHA20_POLY1305
  878. LEGACY_KEY_TYPES = (
  879. # legacy (AES-CTR based) crypto
  880. KeyfileKey,
  881. RepoKey,
  882. Blake2KeyfileKey,
  883. Blake2RepoKey,
  884. )
  885. AVAILABLE_KEY_TYPES = (
  886. # these are available encryption modes for new repositories
  887. # not encrypted modes
  888. PlaintextKey,
  889. AuthenticatedKey,
  890. Blake2AuthenticatedKey,
  891. # new crypto
  892. AESOCBKeyfileKey,
  893. AESOCBRepoKey,
  894. CHPOKeyfileKey,
  895. CHPORepoKey,
  896. Blake2AESOCBKeyfileKey,
  897. Blake2AESOCBRepoKey,
  898. Blake2CHPOKeyfileKey,
  899. Blake2CHPORepoKey,
  900. )