- commit
- 88faaf1ead8d2e57d79e54de612bd494848872f6
- parent
- e73e6be734b40354774318e204c6e6c616c7b3ab
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2026-05-24 08:04
refactor: rm crypt
Diffstat
| M | xikeyring/__main__.py | 5 | +++-- |
| A | xikeyring/crypto.py | 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | xikeyring/keyring.py | 63 | +++++++------------------------------------------------------ |
3 files changed, 60 insertions, 58 deletions
diff --git a/xikeyring/__main__.py b/xikeyring/__main__.py
@@ -3,6 +3,7 @@ import os 3 3 import sys 4 4 from pathlib import Path 5 5 -1 6 from . import crypto 6 7 from .dbus import DBusService 7 8 from .dumpable import pr_set 8 9 from .keyring import KeyringProxy @@ -48,11 +49,11 @@ args = parse_args() 48 49 keyring = KeyringProxy(args.store) 49 50 if args.dump: 50 51 encrypted = keyring.path.read_bytes()51 -1 decrypted = keyring.crypt.decrypt(encrypted)-1 52 decrypted = crypto.decrypt_with_password(encrypted, keyring.password.value) 52 53 print(decrypted.decode('utf-8')) 53 54 elif args.restore: 54 55 decrypted = sys.stdin.read().encode('utf-8')55 -1 encrypted = keyring.crypt.encrypt(decrypted)-1 56 encrypted = crypto.encrypt_with_password(decrypted, keyring.password.value) 56 57 write_bytes(keyring.path, encrypted) 57 58 else: 58 59 service = DBusService(keyring)
diff --git a/xikeyring/crypto.py b/xikeyring/crypto.py
@@ -0,0 +1,50 @@
-1 1 import base64
-1 2 import os
-1 3
-1 4 import argon2
-1 5 from cryptography.fernet import Fernet
-1 6
-1 7
-1 8 def get_argon2(
-1 9 password: bytes,
-1 10 salt: bytes,
-1 11 time_cost: int,
-1 12 memory_cost: int,
-1 13 parallelism: int,
-1 14 ) -> bytes:
-1 15 # https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice
-1 16 key = argon2.low_level.hash_secret_raw(
-1 17 secret=password,
-1 18 salt=salt,
-1 19 time_cost=time_cost,
-1 20 memory_cost=memory_cost,
-1 21 parallelism=parallelism,
-1 22 hash_len=32,
-1 23 type=argon2.low_level.Type.ID,
-1 24 )
-1 25 return base64.urlsafe_b64encode(key)
-1 26
-1 27
-1 28 def encrypt_with_password(data: bytes, password: bytes) -> bytes:
-1 29 salt = os.urandom(16)
-1 30 params = [3, 1 << 16, 4]
-1 31 key = get_argon2(password, salt, *params)
-1 32 content = Fernet(key).encrypt(data)
-1 33 return b'$'.join(
-1 34 [
-1 35 b'fernet-argon2',
-1 36 base64.urlsafe_b64encode(salt),
-1 37 *[str(p).encode() for p in params],
-1 38 content,
-1 39 ]
-1 40 )
-1 41
-1 42 def decrypt_with_password(data: bytes, password: bytes) -> bytes:
-1 43 algo, salt, *params, content = data.split(b'$')
-1 44 salt = base64.urlsafe_b64decode(salt)
-1 45 params = [int(p, 10) for p in params]
-1 46 if algo == b'fernet-argon2' and len(params) == 3:
-1 47 key = get_argon2(password, salt, *params)
-1 48 else:
-1 49 raise TypeError('Unknown encryption algorithm')
-1 50 return Fernet(key).decrypt(content)
diff --git a/xikeyring/keyring.py b/xikeyring/keyring.py
@@ -4,10 +4,9 @@ import os 4 4 from dataclasses import dataclass 5 5 from pathlib import Path 6 67 -1 import argon28 -1 from cryptography.fernet import Fernet9 7 from cryptography.fernet import InvalidToken 10 8 -1 9 from . import crypto 11 10 from .kernel_keyring import KernelKey 12 11 from .prompt import PinentryPrompt as Prompt 13 12 @@ -40,54 +39,6 @@ def write_bytes(path: Path, data: bytes) -> int: 40 39 os.close(fd) 41 40 42 4143 -1 class Crypt:44 -1 def __init__(self, password: bytes):45 -1 self.password = KernelKey(password)46 -147 -1 def get_argon2(48 -1 self,49 -1 salt: bytes,50 -1 time_cost: int,51 -1 memory_cost: int,52 -1 parallelism: int,53 -1 ) -> bytes:54 -1 # https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice55 -1 key = argon2.low_level.hash_secret_raw(56 -1 secret=self.password.value,57 -1 salt=salt,58 -1 time_cost=time_cost,59 -1 memory_cost=memory_cost,60 -1 parallelism=parallelism,61 -1 hash_len=32,62 -1 type=argon2.low_level.Type.ID,63 -1 )64 -1 return base64.urlsafe_b64encode(key)65 -166 -1 def encrypt(self, data: bytes) -> bytes:67 -1 salt = os.urandom(16)68 -1 params = [3, 1 << 16, 4]69 -1 key = self.get_argon2(salt, *params)70 -1 content = Fernet(key).encrypt(data)71 -1 return b'$'.join(72 -1 [73 -1 b'fernet-argon2',74 -1 base64.urlsafe_b64encode(salt),75 -1 *[str(p).encode() for p in params],76 -1 content,77 -1 ]78 -1 )79 -180 -1 def decrypt(self, data: bytes) -> bytes:81 -1 algo, salt, *params, content = data.split(b'$')82 -1 salt = base64.urlsafe_b64decode(salt)83 -1 params = [int(p, 10) for p in params]84 -1 if algo == b'fernet-argon2' and len(params) == 3:85 -1 key = self.get_argon2(salt, *params)86 -1 else:87 -1 raise TypeError('Unknown encryption algorithm')88 -1 return Fernet(key).decrypt(content)89 -190 -191 42 class Keyring: 92 43 def __init__(self, path: Path): 93 44 self.path = path @@ -95,28 +46,28 @@ class Keyring: 95 46 96 47 if self.path.exists(): 97 48 while True:98 -1 self.crypt = self._get_crypt()-1 49 self.password = self._get_password() 99 50 try: 100 51 self._read() 101 52 break 102 53 except InvalidToken: 103 54 pass 104 55 else:105 -1 self.crypt = self._get_crypt()-1 56 self.password = self._get_password() 106 57 self._write({}) 107 58108 -1 def _get_crypt(self):-1 59 def _get_password(self): 109 60 # TODO: different messages for create|unlock|retry 110 61 password = self.prompt.get_password( 111 62 'An application wants access to your keyring, but it is locked' 112 63 ) 113 64 if not password: 114 65 raise AccessDeniedError115 -1 return Crypt(password)-1 66 return KernelKey(password) 116 67 117 68 def _read(self) -> dict[int, Item]: 118 69 encrypted = self.path.read_bytes()119 -1 decrypted = self.crypt.decrypt(encrypted)-1 70 decrypted = crypto.decrypt_with_password(encrypted, self.password.value) 120 71 raw = json.loads(decrypted) 121 72 return { 122 73 id: Item(base64.urlsafe_b64decode(secret), attributes, app_id) @@ -134,7 +85,7 @@ class Keyring: 134 85 for id, item in items.items() 135 86 ] 136 87 decrypted = json.dumps(raw).encode('utf-8')137 -1 encrypted = self.crypt.encrypt(decrypted)-1 88 encrypted = crypto.encrypt_with_password(decrypted, self.password.value) 138 89 write_bytes(self.path, encrypted) 139 90 140 91 def confirm_access(self, app_id: str) -> None: