mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 15:58:48 +00:00
Include fingerprint salt in password key derivation
This commit is contained in:
@@ -11,6 +11,7 @@ from utils.key_derivation import (
|
||||
derive_index_key,
|
||||
derive_key_from_password,
|
||||
)
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
TEST_SEED = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
TEST_PASSWORD = "pw"
|
||||
@@ -22,7 +23,8 @@ def create_vault(
|
||||
password: str = TEST_PASSWORD,
|
||||
) -> tuple[Vault, EncryptionManager]:
|
||||
"""Create a Vault initialized for tests."""
|
||||
seed_key = derive_key_from_password(password)
|
||||
fp = generate_fingerprint(seed)
|
||||
seed_key = derive_key_from_password(password, fp)
|
||||
seed_mgr = EncryptionManager(seed_key, dir_path)
|
||||
seed_mgr.encrypt_parent_seed(seed)
|
||||
|
||||
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
||||
from multiprocessing import Process, Queue
|
||||
import pytest
|
||||
from helpers import TEST_SEED, TEST_PASSWORD
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
@@ -51,7 +52,8 @@ def _backup(index_key: bytes, dir_path: Path, loops: int, out: Queue) -> None:
|
||||
@pytest.mark.parametrize("_", range(3))
|
||||
def test_concurrency_stress(tmp_path: Path, loops: int, _):
|
||||
index_key = derive_index_key(TEST_SEED)
|
||||
seed_key = derive_key_from_password(TEST_PASSWORD)
|
||||
fp = generate_fingerprint(TEST_SEED)
|
||||
seed_key = derive_key_from_password(TEST_PASSWORD, fp)
|
||||
EncryptionManager(seed_key, tmp_path).encrypt_parent_seed(TEST_SEED)
|
||||
enc = EncryptionManager(index_key, tmp_path)
|
||||
Vault(enc, tmp_path).save_index({"counter": 0})
|
||||
|
@@ -9,6 +9,7 @@ from utils.key_derivation import (
|
||||
derive_key_from_password_argon2,
|
||||
derive_index_key,
|
||||
)
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
from seedpass.core.encryption import EncryptionManager
|
||||
|
||||
|
||||
@@ -33,12 +34,13 @@ cfg_values = st.one_of(
|
||||
def test_fuzz_key_round_trip(password, seed_bytes, config, mode, tmp_path: Path):
|
||||
"""Ensure EncryptionManager round-trips arbitrary data."""
|
||||
seed_phrase = Mnemonic("english").to_mnemonic(seed_bytes)
|
||||
fp = generate_fingerprint(seed_phrase)
|
||||
if mode == "argon2":
|
||||
key = derive_key_from_password_argon2(
|
||||
password, time_cost=1, memory_cost=8, parallelism=1
|
||||
password, fp, time_cost=1, memory_cost=8, parallelism=1
|
||||
)
|
||||
else:
|
||||
key = derive_key_from_password(password, iterations=1)
|
||||
key = derive_key_from_password(password, fp, iterations=1)
|
||||
|
||||
enc_mgr = EncryptionManager(key, tmp_path)
|
||||
|
||||
|
@@ -4,6 +4,7 @@ from tempfile import TemporaryDirectory
|
||||
import pytest
|
||||
import sys
|
||||
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
@@ -16,7 +17,8 @@ PASSWORD = "passw0rd"
|
||||
|
||||
|
||||
def setup_vault(tmp: Path) -> Vault:
|
||||
seed_key = derive_key_from_password(PASSWORD)
|
||||
fp = generate_fingerprint(SEED)
|
||||
seed_key = derive_key_from_password(PASSWORD, fp)
|
||||
seed_mgr = EncryptionManager(seed_key, tmp)
|
||||
seed_mgr.encrypt_parent_seed(SEED)
|
||||
|
||||
|
@@ -19,10 +19,11 @@ TEST_PASSWORD = "pw"
|
||||
|
||||
def _setup_profile(tmp: Path, mode: str):
|
||||
argon_kwargs = dict(time_cost=1, memory_cost=8, parallelism=1)
|
||||
fp = tmp.name
|
||||
if mode == "argon2":
|
||||
seed_key = derive_key_from_password_argon2(TEST_PASSWORD, **argon_kwargs)
|
||||
seed_key = derive_key_from_password_argon2(TEST_PASSWORD, fp, **argon_kwargs)
|
||||
else:
|
||||
seed_key = derive_key_from_password(TEST_PASSWORD, iterations=1)
|
||||
seed_key = derive_key_from_password(TEST_PASSWORD, fp, iterations=1)
|
||||
EncryptionManager(seed_key, tmp).encrypt_parent_seed(TEST_SEED)
|
||||
|
||||
index_key = derive_index_key(TEST_SEED)
|
||||
@@ -44,7 +45,7 @@ def _make_pm(tmp: Path, cfg: ConfigManager):
|
||||
pm.encryption_mode = EncryptionMode.SEED_ONLY
|
||||
pm.config_manager = cfg
|
||||
pm.fingerprint_dir = tmp
|
||||
pm.current_fingerprint = "fp"
|
||||
pm.current_fingerprint = tmp.name
|
||||
pm.verify_password = lambda pw: True
|
||||
return pm
|
||||
|
||||
@@ -65,7 +66,9 @@ def test_setup_encryption_manager_kdf_modes(monkeypatch):
|
||||
if mode == "argon2":
|
||||
monkeypatch.setattr(
|
||||
"seedpass.core.manager.derive_key_from_password_argon2",
|
||||
lambda pw: derive_key_from_password_argon2(pw, **argon_kwargs),
|
||||
lambda pw, fp: derive_key_from_password_argon2(
|
||||
pw, fp, **argon_kwargs
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(PasswordManager, "initialize_bip85", lambda self: None)
|
||||
monkeypatch.setattr(
|
||||
|
@@ -10,8 +10,9 @@ from utils.key_derivation import (
|
||||
|
||||
def test_derive_key_deterministic():
|
||||
password = "correct horse battery staple"
|
||||
key1 = derive_key_from_password(password, iterations=1)
|
||||
key2 = derive_key_from_password(password, iterations=1)
|
||||
fp = "fp"
|
||||
key1 = derive_key_from_password(password, fp, iterations=1)
|
||||
key2 = derive_key_from_password(password, fp, iterations=1)
|
||||
assert key1 == key2
|
||||
assert len(key1) == 44
|
||||
logging.info("Deterministic key derivation succeeded")
|
||||
@@ -19,7 +20,7 @@ def test_derive_key_deterministic():
|
||||
|
||||
def test_derive_key_empty_password_error():
|
||||
with pytest.raises(ValueError):
|
||||
derive_key_from_password("")
|
||||
derive_key_from_password("", "fp")
|
||||
logging.info("Empty password correctly raised ValueError")
|
||||
|
||||
|
||||
@@ -38,7 +39,12 @@ def test_derive_index_key_seed_only():
|
||||
|
||||
def test_argon2_key_deterministic():
|
||||
pw = "correct horse battery staple"
|
||||
k1 = derive_key_from_password_argon2(pw, time_cost=1, memory_cost=8, parallelism=1)
|
||||
k2 = derive_key_from_password_argon2(pw, time_cost=1, memory_cost=8, parallelism=1)
|
||||
fp = "fp"
|
||||
k1 = derive_key_from_password_argon2(
|
||||
pw, fp, time_cost=1, memory_cost=8, parallelism=1
|
||||
)
|
||||
k2 = derive_key_from_password_argon2(
|
||||
pw, fp, time_cost=1, memory_cost=8, parallelism=1
|
||||
)
|
||||
assert k1 == k2
|
||||
assert len(k1) == 44
|
||||
|
@@ -14,19 +14,22 @@ from seedpass.core.backup import BackupManager
|
||||
from seedpass.core.config_manager import ConfigManager
|
||||
from seedpass.core.manager import PasswordManager, EncryptionMode
|
||||
from utils.key_derivation import derive_index_key, derive_key_from_password
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
SEED = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
|
||||
|
||||
def test_password_change_and_unlock(monkeypatch):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
fp = Path(tmpdir)
|
||||
fp_name = generate_fingerprint(SEED)
|
||||
fp = Path(tmpdir) / fp_name
|
||||
fp.mkdir()
|
||||
old_pw = "oldpw"
|
||||
new_pw = "newpw"
|
||||
|
||||
# initial encryption setup
|
||||
index_key = derive_index_key(SEED)
|
||||
seed_key = derive_key_from_password(old_pw)
|
||||
seed_key = derive_key_from_password(old_pw, fp_name)
|
||||
enc_mgr = EncryptionManager(index_key, fp)
|
||||
seed_mgr = EncryptionManager(seed_key, fp)
|
||||
vault = Vault(enc_mgr, fp)
|
||||
@@ -54,7 +57,7 @@ def test_password_change_and_unlock(monkeypatch):
|
||||
pm.vault = vault
|
||||
pm.password_generator = SimpleNamespace(encryption_manager=enc_mgr)
|
||||
pm.fingerprint_dir = fp
|
||||
pm.current_fingerprint = "fp"
|
||||
pm.current_fingerprint = fp_name
|
||||
pm.parent_seed = SEED
|
||||
pm.nostr_client = SimpleNamespace(
|
||||
publish_snapshot=lambda *a, **k: (None, "abcd")
|
||||
|
@@ -15,6 +15,7 @@ from seedpass.core.backup import BackupManager
|
||||
from seedpass.core.config_manager import ConfigManager
|
||||
from seedpass.core.portable_backup import export_backup, import_backup
|
||||
from utils.key_derivation import derive_index_key, derive_key_from_password
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
|
||||
SEED = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
@@ -22,7 +23,8 @@ PASSWORD = "passw0rd"
|
||||
|
||||
|
||||
def setup_vault(tmp: Path):
|
||||
seed_key = derive_key_from_password(PASSWORD)
|
||||
fp = generate_fingerprint(SEED)
|
||||
seed_key = derive_key_from_password(PASSWORD, fp)
|
||||
seed_mgr = EncryptionManager(seed_key, tmp)
|
||||
seed_mgr.encrypt_parent_seed(SEED)
|
||||
|
||||
@@ -126,7 +128,8 @@ def test_export_creates_additional_backup_and_import(monkeypatch):
|
||||
with TemporaryDirectory() as td, TemporaryDirectory() as extra:
|
||||
tmp = Path(td)
|
||||
|
||||
seed_key = derive_key_from_password(PASSWORD)
|
||||
fp = generate_fingerprint(SEED)
|
||||
seed_key = derive_key_from_password(PASSWORD, fp)
|
||||
seed_mgr = EncryptionManager(seed_key, tmp)
|
||||
seed_mgr.encrypt_parent_seed(SEED)
|
||||
|
||||
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from helpers import TEST_PASSWORD
|
||||
from utils.key_derivation import derive_key_from_password
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
from mnemonic import Mnemonic
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
@@ -13,10 +14,10 @@ from seedpass.core.manager import PasswordManager, EncryptionMode
|
||||
|
||||
def test_seed_encryption_round_trip():
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
key = derive_key_from_password(TEST_PASSWORD)
|
||||
enc_mgr = EncryptionManager(key, Path(tmpdir))
|
||||
|
||||
seed = Mnemonic("english").generate(strength=128)
|
||||
fp = generate_fingerprint(seed)
|
||||
key = derive_key_from_password(TEST_PASSWORD, fp)
|
||||
enc_mgr = EncryptionManager(key, Path(tmpdir))
|
||||
enc_mgr.encrypt_parent_seed(seed)
|
||||
decrypted = enc_mgr.decrypt_parent_seed()
|
||||
|
||||
|
@@ -4,6 +4,7 @@ from cryptography.fernet import Fernet
|
||||
|
||||
from helpers import TEST_PASSWORD, TEST_SEED
|
||||
from utils.key_derivation import derive_key_from_password
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
@@ -11,7 +12,8 @@ from seedpass.core.encryption import EncryptionManager
|
||||
|
||||
|
||||
def test_parent_seed_migrates_from_fernet(tmp_path: Path) -> None:
|
||||
key = derive_key_from_password(TEST_PASSWORD)
|
||||
fp = generate_fingerprint(TEST_SEED)
|
||||
key = derive_key_from_password(TEST_PASSWORD, fp)
|
||||
fernet = Fernet(key)
|
||||
encrypted = fernet.encrypt(TEST_SEED.encode())
|
||||
legacy_file = tmp_path / "parent_seed.enc"
|
||||
|
Reference in New Issue
Block a user