mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +00:00
Add BIP85 TOTP derivation
This commit is contained in:
@@ -335,12 +335,6 @@ class PasswordGenerator:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def derive_totp_secret(bip85: BIP85, idx: int) -> str:
|
|
||||||
"""Derive a TOTP secret for the given index using BIP85."""
|
|
||||||
entropy = bip85.derive_entropy(index=idx, bytes_len=10, app_no=2)
|
|
||||||
return base64.b32encode(entropy).decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def derive_ssh_key(bip85: BIP85, idx: int) -> bytes:
|
def derive_ssh_key(bip85: BIP85, idx: int) -> bytes:
|
||||||
"""Derive 32 bytes of entropy suitable for an SSH key."""
|
"""Derive 32 bytes of entropy suitable for an SSH key."""
|
||||||
return bip85.derive_entropy(index=idx, bytes_len=32, app_no=32)
|
return bip85.derive_entropy(index=idx, bytes_len=32, app_no=32)
|
||||||
|
@@ -6,10 +6,10 @@ sys.path.append(str(Path(__file__).resolve().parents[1]))
|
|||||||
|
|
||||||
from local_bip85.bip85 import BIP85, Bip85Error
|
from local_bip85.bip85 import BIP85, Bip85Error
|
||||||
from password_manager.password_generation import (
|
from password_manager.password_generation import (
|
||||||
derive_totp_secret,
|
|
||||||
derive_ssh_key,
|
derive_ssh_key,
|
||||||
derive_seed_phrase,
|
derive_seed_phrase,
|
||||||
)
|
)
|
||||||
|
from utils.key_derivation import derive_totp_secret
|
||||||
|
|
||||||
MASTER_XPRV = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb"
|
MASTER_XPRV = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb"
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ EXPECTED_12 = "girl mad pet galaxy egg matter matrix prison refuse sense ordinar
|
|||||||
EXPECTED_24 = "puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano"
|
EXPECTED_24 = "puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano"
|
||||||
|
|
||||||
EXPECTED_SYMM_KEY = "7040bb53104f27367f317558e78a994ada7296c6fde36a364e5baf206e502bb1"
|
EXPECTED_SYMM_KEY = "7040bb53104f27367f317558e78a994ada7296c6fde36a364e5baf206e502bb1"
|
||||||
EXPECTED_TOTP_SECRET = "OBALWUYQJ4TTM7ZR"
|
EXPECTED_TOTP_SECRET = "VQYTWDNEWYBY2G3LOGGCEKR4LZ3LNEYY"
|
||||||
EXPECTED_SSH_KEY = "52405cd0dd21c5be78314a7c1a3c65ffd8d896536cc7dee3157db5824f0c92e2"
|
EXPECTED_SSH_KEY = "52405cd0dd21c5be78314a7c1a3c65ffd8d896536cc7dee3157db5824f0c92e2"
|
||||||
|
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ def test_bip85_symmetric_key(bip85):
|
|||||||
assert bip85.derive_symmetric_key(index=0).hex() == EXPECTED_SYMM_KEY
|
assert bip85.derive_symmetric_key(index=0).hex() == EXPECTED_SYMM_KEY
|
||||||
|
|
||||||
|
|
||||||
def test_derive_totp_secret(bip85):
|
def test_derive_totp_secret():
|
||||||
assert derive_totp_secret(bip85, 0) == EXPECTED_TOTP_SECRET
|
assert derive_totp_secret(EXPECTED_24, 0) == EXPECTED_TOTP_SECRET
|
||||||
|
|
||||||
|
|
||||||
def test_derive_ssh_key(bip85):
|
def test_derive_ssh_key(bip85):
|
||||||
|
@@ -11,8 +11,10 @@ try:
|
|||||||
derive_key_from_password,
|
derive_key_from_password,
|
||||||
derive_key_from_parent_seed,
|
derive_key_from_parent_seed,
|
||||||
derive_index_key,
|
derive_index_key,
|
||||||
|
derive_totp_secret,
|
||||||
EncryptionMode,
|
EncryptionMode,
|
||||||
DEFAULT_ENCRYPTION_MODE,
|
DEFAULT_ENCRYPTION_MODE,
|
||||||
|
TOTP_PURPOSE,
|
||||||
)
|
)
|
||||||
from .checksum import (
|
from .checksum import (
|
||||||
calculate_checksum,
|
calculate_checksum,
|
||||||
@@ -33,8 +35,10 @@ __all__ = [
|
|||||||
"derive_key_from_password",
|
"derive_key_from_password",
|
||||||
"derive_key_from_parent_seed",
|
"derive_key_from_parent_seed",
|
||||||
"derive_index_key",
|
"derive_index_key",
|
||||||
|
"derive_totp_secret",
|
||||||
"EncryptionMode",
|
"EncryptionMode",
|
||||||
"DEFAULT_ENCRYPTION_MODE",
|
"DEFAULT_ENCRYPTION_MODE",
|
||||||
|
"TOTP_PURPOSE",
|
||||||
"calculate_checksum",
|
"calculate_checksum",
|
||||||
"verify_checksum",
|
"verify_checksum",
|
||||||
"json_checksum",
|
"json_checksum",
|
||||||
|
@@ -20,6 +20,7 @@ import base64
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
import hmac
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
from bip_utils import Bip39SeedGenerator
|
from bip_utils import Bip39SeedGenerator
|
||||||
@@ -40,6 +41,9 @@ class EncryptionMode(Enum):
|
|||||||
|
|
||||||
DEFAULT_ENCRYPTION_MODE = EncryptionMode.SEED_ONLY
|
DEFAULT_ENCRYPTION_MODE = EncryptionMode.SEED_ONLY
|
||||||
|
|
||||||
|
# Purpose constant for TOTP secret derivation using BIP85
|
||||||
|
TOTP_PURPOSE = 39
|
||||||
|
|
||||||
|
|
||||||
def derive_key_from_password(password: str, iterations: int = 100_000) -> bytes:
|
def derive_key_from_password(password: str, iterations: int = 100_000) -> bytes:
|
||||||
"""
|
"""
|
||||||
@@ -159,10 +163,22 @@ def derive_totp_secret(seed: str, index: int) -> str:
|
|||||||
try:
|
try:
|
||||||
from local_bip85 import BIP85
|
from local_bip85 import BIP85
|
||||||
|
|
||||||
|
# Initialize BIP85 from the BIP39 seed bytes
|
||||||
seed_bytes = Bip39SeedGenerator(seed).Generate()
|
seed_bytes = Bip39SeedGenerator(seed).Generate()
|
||||||
bip85 = BIP85(seed_bytes)
|
bip85 = BIP85(seed_bytes)
|
||||||
entropy = bip85.derive_entropy(index=index, bytes_len=10, app_no=2)
|
|
||||||
secret = base64.b32encode(entropy).decode("utf-8")
|
# Build the BIP32 path m/83696968'/39'/TOTP'/{index}'
|
||||||
|
totp_int = int.from_bytes(b"TOTP", "big")
|
||||||
|
path = f"m/83696968'/{TOTP_PURPOSE}'/{totp_int}'/{index}'"
|
||||||
|
|
||||||
|
# Derive entropy using the same scheme as BIP85
|
||||||
|
child_key = bip85.bip32_ctx.DerivePath(path)
|
||||||
|
key_bytes = child_key.PrivateKey().Raw().ToBytes()
|
||||||
|
entropy = hmac.new(b"bip-entropy-from-k", key_bytes, hashlib.sha512).digest()
|
||||||
|
|
||||||
|
# Hash the first 32 bytes of entropy and encode the first 20 bytes
|
||||||
|
hashed = hashlib.sha256(entropy[:32]).digest()
|
||||||
|
secret = base64.b32encode(hashed[:20]).decode("utf-8")
|
||||||
logger.debug(f"Derived TOTP secret for index {index}: {secret}")
|
logger.debug(f"Derived TOTP secret for index {index}: {secret}")
|
||||||
return secret
|
return secret
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
Reference in New Issue
Block a user