dploot 3.2.0__py2.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.
- dploot/__init__.py +0 -0
- dploot/action/__init__.py +0 -0
- dploot/action/backupkey.py +102 -0
- dploot/action/blob.py +167 -0
- dploot/action/browser.py +192 -0
- dploot/action/certificates.py +146 -0
- dploot/action/cng.py +129 -0
- dploot/action/credentials.py +132 -0
- dploot/action/machinecertificates.py +136 -0
- dploot/action/machinecng.py +120 -0
- dploot/action/machinecredentials.py +120 -0
- dploot/action/machinemasterkeys.py +117 -0
- dploot/action/machinetriage.py +158 -0
- dploot/action/machinevaults.py +127 -0
- dploot/action/masterkeys.py +200 -0
- dploot/action/mobaxterm.py +138 -0
- dploot/action/rdg.py +132 -0
- dploot/action/sccm.py +125 -0
- dploot/action/triage.py +187 -0
- dploot/action/vaults.py +132 -0
- dploot/action/wam.py +133 -0
- dploot/action/wifi.py +137 -0
- dploot/entry.py +98 -0
- dploot/lib/__init__.py +0 -0
- dploot/lib/consts.py +30 -0
- dploot/lib/crypto.py +415 -0
- dploot/lib/dpapi.py +302 -0
- dploot/lib/masterkey.py +75 -0
- dploot/lib/smb.py +517 -0
- dploot/lib/target.py +219 -0
- dploot/lib/utils.py +99 -0
- dploot/lib/wmi.py +61 -0
- dploot/triage/__init__.py +30 -0
- dploot/triage/backupkey.py +81 -0
- dploot/triage/browser.py +441 -0
- dploot/triage/certificates.py +444 -0
- dploot/triage/cng.py +165 -0
- dploot/triage/credentials.py +163 -0
- dploot/triage/masterkeys.py +279 -0
- dploot/triage/mobaxterm.py +653 -0
- dploot/triage/rdg.py +249 -0
- dploot/triage/sccm.py +302 -0
- dploot/triage/vaults.py +307 -0
- dploot/triage/wam.py +251 -0
- dploot/triage/wifi.py +424 -0
- dploot-3.2.0.dist-info/METADATA +865 -0
- dploot-3.2.0.dist-info/RECORD +50 -0
- dploot-3.2.0.dist-info/WHEEL +4 -0
- dploot-3.2.0.dist-info/entry_points.txt +3 -0
- dploot-3.2.0.dist-info/licenses/LICENSE +21 -0
dploot/lib/dpapi.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
from Cryptodome.Cipher import AES, PKCS1_v1_5
|
|
4
|
+
from Cryptodome.PublicKey import RSA
|
|
5
|
+
from binascii import hexlify, unhexlify
|
|
6
|
+
|
|
7
|
+
from Cryptodome.Util.Padding import unpad
|
|
8
|
+
from Cryptodome.Hash import HMAC
|
|
9
|
+
|
|
10
|
+
from impacket.uuid import bin_to_string
|
|
11
|
+
from impacket.dpapi import (
|
|
12
|
+
MasterKeyFile,
|
|
13
|
+
MasterKey,
|
|
14
|
+
DomainKey,
|
|
15
|
+
DPAPI_BLOB,
|
|
16
|
+
CREDENTIAL_BLOB,
|
|
17
|
+
VAULT_VCRD,
|
|
18
|
+
VAULT_VPOL,
|
|
19
|
+
VAULT_KNOWN_SCHEMAS,
|
|
20
|
+
VAULT_VPOL_KEYS,
|
|
21
|
+
PVK_FILE_HDR,
|
|
22
|
+
PRIVATE_KEY_BLOB,
|
|
23
|
+
ALGORITHMS_DATA,
|
|
24
|
+
privatekeyblob_to_pkcs1,
|
|
25
|
+
DPAPI_DOMAIN_RSA_MASTER_KEY,
|
|
26
|
+
CredentialFile,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from dploot.lib.crypto import (
|
|
30
|
+
PRIVATE_KEY_RSA,
|
|
31
|
+
PVKFile,
|
|
32
|
+
PVKFile_SIG,
|
|
33
|
+
PVKHeader,
|
|
34
|
+
deriveKeysFromUser,
|
|
35
|
+
deriveKeysFromUserkey,
|
|
36
|
+
pvkblob_to_pkcs1,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def decrypt_masterkey(
|
|
41
|
+
masterkey: bytes,
|
|
42
|
+
domain_backupkey: Optional[bytes] = None,
|
|
43
|
+
dpapi_systemkey: Optional[Dict] = None,
|
|
44
|
+
sid: str = "",
|
|
45
|
+
password: Optional[str] = None,
|
|
46
|
+
nthash: Optional[str] = None,
|
|
47
|
+
) -> Any:
|
|
48
|
+
if (
|
|
49
|
+
domain_backupkey is None
|
|
50
|
+
and password is None
|
|
51
|
+
and nthash is None
|
|
52
|
+
and dpapi_systemkey is None
|
|
53
|
+
):
|
|
54
|
+
return None
|
|
55
|
+
data = masterkey
|
|
56
|
+
mkf = MasterKeyFile(data)
|
|
57
|
+
dk = mk = bkmk = None
|
|
58
|
+
data = data[len(mkf) :]
|
|
59
|
+
if mkf["MasterKeyLen"] > 0:
|
|
60
|
+
mk = MasterKey(data[: mkf["MasterKeyLen"]])
|
|
61
|
+
data = data[len(mk) :]
|
|
62
|
+
|
|
63
|
+
if mkf["BackupKeyLen"] > 0:
|
|
64
|
+
bkmk = MasterKey(data[: mkf["BackupKeyLen"]])
|
|
65
|
+
data = data[len(bkmk) :]
|
|
66
|
+
|
|
67
|
+
if mkf["DomainKeyLen"] > 0:
|
|
68
|
+
dk = DomainKey(data[: mkf["DomainKeyLen"]])
|
|
69
|
+
data = data[len(dk) :]
|
|
70
|
+
|
|
71
|
+
if domain_backupkey is not None and dk is not None:
|
|
72
|
+
key = PRIVATE_KEY_BLOB(domain_backupkey[len(PVK_FILE_HDR()) :])
|
|
73
|
+
private = privatekeyblob_to_pkcs1(key)
|
|
74
|
+
cipher = PKCS1_v1_5.new(private)
|
|
75
|
+
|
|
76
|
+
decryptedKey = cipher.decrypt(dk["SecretData"][::-1], None)
|
|
77
|
+
if decryptedKey:
|
|
78
|
+
domain_master_key = DPAPI_DOMAIN_RSA_MASTER_KEY(decryptedKey)
|
|
79
|
+
return domain_master_key["buffer"][: domain_master_key["cbMasterKey"]]
|
|
80
|
+
|
|
81
|
+
if sid != "":
|
|
82
|
+
if nthash is not None:
|
|
83
|
+
nthash = unhexlify(nthash)
|
|
84
|
+
key1, key2 = deriveKeysFromUserkey(sid, nthash)
|
|
85
|
+
decryptedKey = mk.decrypt(key2) or mk.decrypt(key1)
|
|
86
|
+
if decryptedKey:
|
|
87
|
+
return decryptedKey
|
|
88
|
+
|
|
89
|
+
if password is not None:
|
|
90
|
+
key1, key2, key3 = deriveKeysFromUser(sid, password)
|
|
91
|
+
decryptedKey = mk.decrypt(key3) or mk.decrypt(key2) or mk.decrypt(key1)
|
|
92
|
+
if decryptedKey:
|
|
93
|
+
return decryptedKey
|
|
94
|
+
|
|
95
|
+
if dpapi_systemkey is not None:
|
|
96
|
+
decryptedKey = (
|
|
97
|
+
mk.decrypt(dpapi_systemkey["UserKey"])
|
|
98
|
+
or mk.decrypt(dpapi_systemkey["MachineKey"])
|
|
99
|
+
or bkmk.decrypt(dpapi_systemkey["UserKey"])
|
|
100
|
+
or bkmk.decrypt(dpapi_systemkey["MachineKey"])
|
|
101
|
+
)
|
|
102
|
+
if decryptedKey:
|
|
103
|
+
return decryptedKey
|
|
104
|
+
|
|
105
|
+
if sid != "":
|
|
106
|
+
test = dpapi_systemkey["UserKey"]
|
|
107
|
+
key1, key2 = deriveKeysFromUserkey(sid, test)
|
|
108
|
+
if key2 is not None:
|
|
109
|
+
decryptedKey = mk.decrypt(key2)
|
|
110
|
+
if decryptedKey:
|
|
111
|
+
return decryptedKey
|
|
112
|
+
|
|
113
|
+
decryptedKey = mk.decrypt(key1)
|
|
114
|
+
if decryptedKey:
|
|
115
|
+
return decryptedKey
|
|
116
|
+
|
|
117
|
+
if key2 is not None:
|
|
118
|
+
decryptedKey = bkmk.decrypt(key2)
|
|
119
|
+
if decryptedKey:
|
|
120
|
+
return decryptedKey
|
|
121
|
+
|
|
122
|
+
decryptedKey = bkmk.decrypt(key1)
|
|
123
|
+
if decryptedKey:
|
|
124
|
+
return decryptedKey
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def decrypt_credential(credential_bytes: bytes, masterkey: MasterKey) -> Any:
|
|
129
|
+
cred = CredentialFile(credential_bytes)
|
|
130
|
+
decrypted = decrypt_blob(cred["Data"], masterkey)
|
|
131
|
+
if decrypted:
|
|
132
|
+
return CREDENTIAL_BLOB(decrypted)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def find_masterkey_for_credential_blob(
|
|
137
|
+
credential_bytes: bytes, masterkeys: Any
|
|
138
|
+
) -> "Any | None":
|
|
139
|
+
cred = CredentialFile(credential_bytes)
|
|
140
|
+
return find_masterkey_for_blob(cred["Data"], masterkeys=masterkeys)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def decrypt_privatekey(
|
|
144
|
+
privatekey_bytes: bytes, masterkey: Any
|
|
145
|
+
) -> RSA.RsaKey:
|
|
146
|
+
blob = PVKHeader(privatekey_bytes)
|
|
147
|
+
blob = PVKFile_SIG(privatekey_bytes) if blob["SigHeadLen"] > 0 else PVKFile(privatekey_bytes)
|
|
148
|
+
key = unhexlify(masterkey.sha1)
|
|
149
|
+
decrypted = decrypt(blob["Blob"], key)
|
|
150
|
+
rsa_temp = PRIVATE_KEY_RSA(decrypted)
|
|
151
|
+
return pvkblob_to_pkcs1(rsa_temp)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def find_masterkey_for_privatekey_blob(
|
|
155
|
+
privatekey_bytes: bytes, masterkeys: List[Any]
|
|
156
|
+
) -> "Any | None":
|
|
157
|
+
blob = PVKHeader(privatekey_bytes)
|
|
158
|
+
if len(blob["Remaining"]) == 0:
|
|
159
|
+
return None
|
|
160
|
+
blob = PVKFile_SIG(privatekey_bytes) if blob["SigHeadLen"] > 0 else PVKFile(privatekey_bytes)
|
|
161
|
+
|
|
162
|
+
masterkey = bin_to_string(blob["Blob"]["GuidMasterKey"])
|
|
163
|
+
return find_masterkey(masterkey=masterkey, masterkeys=masterkeys)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def decrypt_vpol(vpol_bytes: bytes, masterkey: Any) -> "VAULT_VPOL_KEYS | None":
|
|
167
|
+
vpol = VAULT_VPOL(vpol_bytes)
|
|
168
|
+
blob = vpol["Blob"]
|
|
169
|
+
|
|
170
|
+
key = unhexlify(masterkey.sha1)
|
|
171
|
+
decrypted = decrypt(blob, key)
|
|
172
|
+
if decrypted:
|
|
173
|
+
return VAULT_VPOL_KEYS(decrypted)
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def decrypt_vcrd(vcrd_bytes: bytes, vpol_keys: List[bytes]) -> Any:
|
|
178
|
+
blob = VAULT_VCRD(vcrd_bytes)
|
|
179
|
+
|
|
180
|
+
for key in vpol_keys:
|
|
181
|
+
key = unhexlify(key)
|
|
182
|
+
cleartext = None
|
|
183
|
+
for i, entry in enumerate(blob.attributesLen):
|
|
184
|
+
try:
|
|
185
|
+
if entry > 28:
|
|
186
|
+
attribute = blob.attributes[i]
|
|
187
|
+
cipher = AES.new(key, AES.MODE_CBC, iv=attribute["IV"]) if "IV" in attribute.fields and len(attribute["IV"]) == 16 else AES.new(key, AES.MODE_CBC)
|
|
188
|
+
cleartext = cipher.decrypt(attribute["Data"])
|
|
189
|
+
if cleartext is not None:
|
|
190
|
+
# Lookup schema Friendly Name and print if we find one
|
|
191
|
+
if (
|
|
192
|
+
blob["FriendlyName"].decode("utf-16le")[:-1]
|
|
193
|
+
in VAULT_KNOWN_SCHEMAS
|
|
194
|
+
):
|
|
195
|
+
# Found one. Cast it and print
|
|
196
|
+
return VAULT_KNOWN_SCHEMAS[
|
|
197
|
+
blob["FriendlyName"].decode("utf-16le")[:-1]
|
|
198
|
+
](cleartext)
|
|
199
|
+
else:
|
|
200
|
+
# otherwise
|
|
201
|
+
return cleartext
|
|
202
|
+
except Exception as e:
|
|
203
|
+
if (
|
|
204
|
+
str(e)
|
|
205
|
+
!= "('unpack requires a buffer of 4 bytes', \"When unpacking field 'Id2 | <L=0 | b''[:4]'\")"
|
|
206
|
+
):
|
|
207
|
+
logging.debug(e)
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def find_masterkey_for_vpol_blob(vault_bytes: bytes, masterkeys: Any) -> "Any | None":
|
|
212
|
+
vault = VAULT_VPOL(vault_bytes)
|
|
213
|
+
blob = vault["Blob"]
|
|
214
|
+
masterkey = bin_to_string(blob["GuidMasterKey"])
|
|
215
|
+
return find_masterkey(masterkey=masterkey, masterkeys=masterkeys)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def decrypt_blob(blob_bytes: bytes, masterkey: Any, entropy=None) -> "bytes | None":
|
|
219
|
+
blob = DPAPI_BLOB(blob_bytes)
|
|
220
|
+
# Ugly fix below:
|
|
221
|
+
# if blob_bytes was too long, strip blob.rawData so its len matches len(blob)
|
|
222
|
+
if len(blob.rawData) > len(blob):
|
|
223
|
+
logging.debug(
|
|
224
|
+
f"{__name__}.decrypt_blob(): rawData too long. Stripping {len(blob.rawData) - len(blob)} bytes."
|
|
225
|
+
)
|
|
226
|
+
blob.rawData = blob.rawData[0 : len(blob)]
|
|
227
|
+
key = unhexlify(masterkey.sha1)
|
|
228
|
+
return decrypt(blob, key, entropy=entropy) if entropy is not None else decrypt(blob, key)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def decrypt(blob: DPAPI_BLOB, keyHash, entropy=None) -> "bytes | None":
|
|
232
|
+
hash_algo = ALGORITHMS_DATA[blob["HashAlgo"]][1]
|
|
233
|
+
block_size = hash_algo.block_size
|
|
234
|
+
for algo in [compute_sessionKey_1, compute_sessionKey_2]:
|
|
235
|
+
sessionKey = algo(keyHash, blob["Salt"], hash_algo, block_size, entropy)
|
|
236
|
+
sessionKey = sessionKey.digest()
|
|
237
|
+
derivedKey = blob.deriveKey(sessionKey)
|
|
238
|
+
crypto = ALGORITHMS_DATA[blob["CryptAlgo"]]
|
|
239
|
+
cipher = crypto[1].new(
|
|
240
|
+
derivedKey[: crypto[0]], mode=crypto[2], iv=b"\x00" * crypto[3]
|
|
241
|
+
)
|
|
242
|
+
cleartext = cipher.decrypt(blob["Data"])
|
|
243
|
+
try:
|
|
244
|
+
cleartext = unpad(cleartext, crypto[1].block_size)
|
|
245
|
+
except ValueError as e:
|
|
246
|
+
if "Padding is incorrect" in str(e):
|
|
247
|
+
pass
|
|
248
|
+
# Now check the signature
|
|
249
|
+
# ToDo Fix this, it's just ugly, more testing so we can remove one
|
|
250
|
+
toSign = blob.rawData[20 : len(blob) - len(blob["Sign"]) - 4]
|
|
251
|
+
if toSign[-4:] != blob["Data"][-4:]:
|
|
252
|
+
logging.debug(f"{__name__}.decrypt(): toSign is wrong!")
|
|
253
|
+
logging.debug("toSign : %s" % (hexlify(toSign)))
|
|
254
|
+
logging.debug(
|
|
255
|
+
"Sign (%2d) : %s" % (len(blob["Sign"]), hexlify(blob["Sign"]))
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
hmac_calculated = algo(keyHash, blob["HMac"], hash_algo, block_size, entropy)
|
|
259
|
+
hmac_calculated.update(toSign)
|
|
260
|
+
if blob["Sign"] == hmac_calculated.digest():
|
|
261
|
+
return cleartext
|
|
262
|
+
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def compute_sessionKey_1(
|
|
267
|
+
key_hash: bytes, salt: bytes, hash_algo: object, block_size: int, entropy: bytes
|
|
268
|
+
):
|
|
269
|
+
pad_block = key_hash.ljust(block_size, b"\x00")
|
|
270
|
+
ipad = bytearray(i ^ 0x36 for i in pad_block)
|
|
271
|
+
opad = bytearray(i ^ 0x5C for i in pad_block)
|
|
272
|
+
|
|
273
|
+
a = hash_algo.new(ipad)
|
|
274
|
+
a.update(salt)
|
|
275
|
+
|
|
276
|
+
computed_key = hash_algo.new(opad)
|
|
277
|
+
computed_key.update(a.digest())
|
|
278
|
+
|
|
279
|
+
if entropy is not None:
|
|
280
|
+
computed_key.update(entropy)
|
|
281
|
+
return computed_key
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def compute_sessionKey_2(
|
|
285
|
+
key_hash: bytes, salt: bytes, hash_algo: object, block_size: int, entropy: bytes
|
|
286
|
+
):
|
|
287
|
+
computed_key = HMAC.new(key_hash, salt, hash_algo)
|
|
288
|
+
if entropy is not None:
|
|
289
|
+
computed_key.update(entropy)
|
|
290
|
+
|
|
291
|
+
return computed_key
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def find_masterkey_for_blob(blob_bytes: bytes, masterkeys: Any) -> "Any | None":
|
|
295
|
+
blob = DPAPI_BLOB(blob_bytes)
|
|
296
|
+
masterkey = bin_to_string(blob["GuidMasterKey"])
|
|
297
|
+
return find_masterkey(masterkey=masterkey, masterkeys=masterkeys)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def find_masterkey(masterkey: str, masterkeys: Any) -> "Any | None":
|
|
301
|
+
masterkey = masterkey.lower()
|
|
302
|
+
return next((key for key in masterkeys if key.guid.lower() == masterkey), None)
|
dploot/lib/masterkey.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from binascii import hexlify
|
|
3
|
+
from Cryptodome.Hash import SHA1
|
|
4
|
+
|
|
5
|
+
from impacket.dpapi import (
|
|
6
|
+
MasterKeyFile,
|
|
7
|
+
MasterKey,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from dploot.lib.dpapi import decrypt_masterkey
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Masterkey:
|
|
14
|
+
def __init__(self, guid, blob = None, sid:str = None, key = None, sha1 = None, user: str = "None") -> None:
|
|
15
|
+
self.guid = guid
|
|
16
|
+
self.blob = blob
|
|
17
|
+
self.sid = str(sid).upper()
|
|
18
|
+
self.user = user
|
|
19
|
+
|
|
20
|
+
self.key = key
|
|
21
|
+
self._sha1 = sha1
|
|
22
|
+
|
|
23
|
+
if self.blob is not None:
|
|
24
|
+
self.generate_hash()
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
return f"{{{self.guid}}}:{self.sha1}" if self.key is not None else ""
|
|
28
|
+
|
|
29
|
+
def decrypt(self, domain_backupkey = None, password = None, nthash = None, dpapi_systemkey = None) -> bool:
|
|
30
|
+
key = decrypt_masterkey(
|
|
31
|
+
masterkey=self.blob,
|
|
32
|
+
domain_backupkey=domain_backupkey,
|
|
33
|
+
sid=self.sid,
|
|
34
|
+
password=password,
|
|
35
|
+
nthash=nthash,
|
|
36
|
+
dpapi_systemkey=dpapi_systemkey
|
|
37
|
+
)
|
|
38
|
+
if key is not None:
|
|
39
|
+
self.key = key
|
|
40
|
+
return True
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def generate_hash(self):
|
|
44
|
+
hashes = []
|
|
45
|
+
mkf = MasterKeyFile(self.blob)
|
|
46
|
+
data = self.blob[len(mkf) :]
|
|
47
|
+
if mkf["MasterKeyLen"] > 0:
|
|
48
|
+
mk = MasterKey(data[:mkf["MasterKeyLen"]])
|
|
49
|
+
try:
|
|
50
|
+
iteration_count = mk["MasterKeyIterationCount"]
|
|
51
|
+
iv = hexlify(mk["Salt"]).decode("ascii")
|
|
52
|
+
encryted_data = hexlify(mk["data"]).decode("ascii")
|
|
53
|
+
version = 1 if len(encryted_data) == 208 else 2
|
|
54
|
+
hash_algo = "sha1" if len(encryted_data) == 208 else "sha512"
|
|
55
|
+
crypt_algo = "des3" if len(encryted_data) == 208 else "aes256"
|
|
56
|
+
hashes = [f"{self.user}:$DPAPImk${version}*{context}*{self.sid}*{crypt_algo}*{hash_algo}*{iteration_count}*{iv}*{len(encryted_data)}*{encryted_data}"for context in [1,2,3]]
|
|
57
|
+
except Exception as e:
|
|
58
|
+
import traceback
|
|
59
|
+
traceback.print_exc()
|
|
60
|
+
print(e)
|
|
61
|
+
return hashes
|
|
62
|
+
|
|
63
|
+
def dump(self) -> None:
|
|
64
|
+
print(self)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def sha1(self):
|
|
68
|
+
if self._sha1 is not None:
|
|
69
|
+
return self._sha1
|
|
70
|
+
if self.key is not None:
|
|
71
|
+
try:
|
|
72
|
+
self._sha1 = hexlify(SHA1.new(self.key).digest()).decode("latin-1")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logging.debug(f"Could not generate sha1 for masterkey {self.guid}: {e}")
|
|
75
|
+
return self._sha1
|