logging.debug(f" > Method: PKCS12-3DES-Derivation")
logging.debug(f" > Salt: {censor(entry_salt)} (Local) + {censor(global_salt)} (Global)")
- import hmac
- hp = sha1(global_salt + master_password.encode()).digest()
- pes = entry_salt + b"\x00" * (20 - len(entry_salt))
- chp = sha1(hp + entry_salt).digest()
- k1 = hmac.new(chp, pes + entry_salt, sha1).digest()
- tk = hmac.new(chp, pes, sha1).digest()
- k2 = hmac.new(chp, tk + entry_salt, sha1).digest()
- k = k1 + k2
- iv = k[-8:]
- key = k[:24]
-
- logging.debug(f" > Cipher: 3DES-CBC | IV: {censor(iv)}")
- return PKCS7unpad(DES3.new(key, DES3.MODE_CBC, iv).decrypt(ciphertext))
+ return PKCS7unpad(decrypt3DES(global_salt, master_password, entry_salt, ciphertext))
except Exception as e:
logging.debug(f" > Failed: {e}")
conn = sqlite3.connect(str(db))
c = conn.cursor()
- # 1. Get Global Salt & Password Check Entry
+ # 1. Get Global Salt
c.execute("SELECT item1, item2 FROM metadata WHERE id = 'password'")
try:
global_salt, item2 = next(c)
logging.info(f"[*] Global Salt: {censor(global_salt)}")
# 2. VERIFY PASSWORD EXPLICITLY
- # This ensures we throw WrongPassword BEFORE trying to decrypt keys
- # and fail with a different error if keys are missing but password is correct.
try:
decodedItem2, _ = der_decode(item2)
try:
cipherT = decodedItem2[1].asOctets()
clearText = decrypt3DES(global_salt, pwd, entrySalt, cipherT)
elif algorithm_oid == OID_PBES2:
- # Re-implement AES decrypt inline for password check
algo = decodedItem2[0][1][0]
pbkdf2_params = algo[1]
entry_salt = pbkdf2_params[0].asOctets()
raise WrongPassword()
except Exception as e:
- # If any crypto fails during verification, it's a wrong password
- # (or corrupted salt/metadata, but we assume password first)
logging.debug(f"Password check failed: {e}")
raise WrongPassword()
rows = c.fetchall()
logging.info(f"[*] Found {len(rows)} entries in nssPrivate")
+ # FIX: Check if rows exist BEFORE assuming corruption.
+ # If the table is empty, it's just an empty DB, not corruption.
+ if not rows:
+ raise NoDatabase()
+
found_keys = []
for idx, (a11, a102) in enumerate(rows):
logging.debug(f"[*] Attempting to decrypt Key #{idx} (ID: {censor(a102)})...")
logging.debug(f"[*] Key #{idx}: Failed to decrypt (Corrupt?)")
if not found_keys:
- # We verified the password is correct above, but still found no keys.
- # This is a database corruption issue, NOT a wrong password.
- raise Exception("Database corrupted: Password verified, but no valid master keys found.")
+ # Rows existed, but none decrypted successfully.
+ # Since password was verified in step 2, this IS corruption.
+ raise Exception("Database corrupted: Password verified, but no valid master keys could be decrypted.")
return found_keys, global_salt
def main_export(args):
- try:
- key = askpass(args.directory)
- except NoDatabase:
- return
+ # Removed try/except NoDatabase here to let it bubble to main() for proper logging
+ key = askpass(args.directory)
if not key:
logging.error("Failed to derive master key.")