Home

py-spectre @main - refs - log -
-
https://git.jolheiser.com/py-spectre.git
Python implementation for Spectre/Masterpassword
py-spectre / src / spectre / spectre.py
- raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import hashlib
import struct
from hmac import HMAC
from typing import Optional

from spectre.scope import Scope, Scoper, default_scoper
from spectre.template import Template, _characters, _templates


class Spectre:
    def __init__(self, name: str, secret: str, scoper: Scoper = default_scoper):
        self._name = name
        self._secret = secret
        self._scoper = scoper
        self._key = self._user_key()

    def _user_key(self) -> bytes:
        name_bytes = self._name.encode()
        secret_bytes = self._secret.encode()
        key_scope = self._scoper.scope(Scope.AUTHENTICATION).encode()

        name_bytes_len = len(name_bytes)
        key_salt = key_scope + _big_endian(name_bytes_len) + name_bytes
        n = 32768  # N
        r = 8
        p = 2
        m = 128 * r * (n + p + 2)
        return hashlib.scrypt(
            secret_bytes, maxmem=m, salt=key_salt, n=n, r=r, p=p, dklen=64
        )

    def _site_key(self, name: str, counter: int, scope: Scope) -> bytes:
        name_bytes = name.encode()
        scope_bytes = self._scoper.scope(scope).encode()

        name_bytes_len = len(name_bytes)
        key_salt = (
            scope_bytes
            + _big_endian(name_bytes_len)
            + name_bytes
            + _big_endian(counter)
        )

        return HMAC(self._key, msg=key_salt, digestmod=hashlib.sha256).digest()

    def site(
        self,
        site_name: str,
        template_type: Optional[Template] = None,
        counter: int = 1,
        scope: Scope = Scope.AUTHENTICATION,
    ) -> str:
        if not template_type:
            template_type = scope.default_template()
        site_key = self._site_key(site_name, counter, scope)

        template_set = _templates[template_type]
        template = template_set[site_key[0] % len(template_set)]

        out = ""
        for idx, b in enumerate(template):
            chars = _characters[b]
            char = chars[site_key[idx + 1] % len(chars)]
            out += char
        return out


def _big_endian(n: int) -> bytes:
    return struct.pack(">I", n)