Use neutral arguments in `Vault.create_hash` signature
Marco Ricci

Marco Ricci commited on 2024-06-08 19:06:56
Zeige 1 geänderte Dateien mit 35 Einfügungen und 11 Löschungen.


Currently, when generating a passphrase from an SSH key signature,
`derivepassphrase.Vault.create_hash` requires the user to know the role
of the `key` and `message` arguments themselves; in particular, it
requires the user to add the vault UUID to the message themselves.  The
fact that the vault UUID is added to the hashing input is an
implementation detail, as should be the fact that one of the arguments
is a key and the other is a salt value to a key-derivation function. Put
these details only into the docstring's description section, not the
argument names; rename them to `phrase` and `service`, respectively.
... ...
@@ -145,17 +145,24 @@ class Vault:
145 145
 
146 146
     @classmethod
147 147
     def create_hash(
148
-        cls, key: bytes | bytearray, message: bytes | bytearray, *,
148
+        cls, phrase: bytes | bytearray, service: bytes | bytearray, *,
149 149
         length: int = 32,
150 150
     ) -> bytes:
151
-        """Create a pseudorandom byte stream from key and message.
151
+        r"""Create a pseudorandom byte stream from phrase and service.
152
+
153
+        Create a pseudorandom byte stream from `phrase` and `service` by
154
+        feeding them into the key-derivation function PBKDF2
155
+        (8 iterations, using SHA-1).
152 156
 
153 157
         Args:
154
-            key:
155
-                A cryptographic key.  Typically a master passphrase, or
156
-                an SSH signature.
157
-            message:
158
-                A message.  Typically a vault service name.
158
+            phrase:
159
+                A master passphrase, or sometimes an SSH signature.
160
+                Used as the key for PBKDF2, the underlying cryptographic
161
+                primitive.
162
+            service:
163
+                A vault service name.  Will be suffixed with
164
+                `Vault._UUID`, and then used as the salt value for
165
+                PBKDF2.
159 166
             length:
160 167
                 The length of the byte stream to generate.
161 168
 
... ...
@@ -170,9 +177,27 @@ class Vault:
170 177
             call this method with the same input with ever-increasing
171 178
             target lengths.
172 179
 
180
+        Examples:
181
+            >>> # See also Vault.phrase_from_signature examples.
182
+            >>> phrase = bytes.fromhex('''
183
+            ... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
184
+            ... 00 00 00 40
185
+            ... f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86
186
+            ... 66 07 13 19 13 09 21 33 33 f9 e4 36 53 1d af fd
187
+            ... 0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c
188
+            ... 1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02
189
+            ... ''')
190
+            >>> Vault.create_hash(phrase, b'some_service', length=4)
191
+            b'M\xb1<S'
192
+            >>> Vault.create_hash(phrase, b'some_service', length=16)
193
+            b'M\xb1<S\x827E\xd1M\xaf\xf8~\xc8n\x10\xcc'
194
+            >>> Vault.create_hash(phrase, b'NOSUCHSERVICE', length=16)
195
+            b'\x1c\xc3\x9c\xd9\xb6\x1a\x99CS\x07\xc41\xf4\x85#s'
196
+
173 197
         """
174
-        return hashlib.pbkdf2_hmac(hash_name='sha1', password=key,
175
-                                   salt=message, iterations=8, dklen=length)
198
+        salt = bytes(service) + cls._UUID
199
+        return hashlib.pbkdf2_hmac(hash_name='sha1', password=phrase,
200
+                                   salt=salt, iterations=8, dklen=length)
176 201
 
177 202
     def generate(
178 203
         self, service_name: str | bytes | bytearray, /, *,
... ...
@@ -220,8 +245,7 @@ class Vault:
220 245
             try:
221 246
                 required = self._required[:]
222 247
                 seq = sequin.Sequin(self.create_hash(
223
-                    key=phrase, message=(service_name + self._UUID),
224
-                    length=hash_length))
248
+                    phrase=phrase, service=service_name, length=hash_length))
225 249
                 result = bytearray()
226 250
                 while len(result) < self._length:
227 251
                     pos = seq.generate(len(required))
228 252