auth.py 6.03 KB
Newer Older
1 2
from silkaj.crypto_tools import get_publickey_from_seed, b58_decode, xor_bytes
from silkaj.tools import message_exit
3
from nacl import encoding
Tortue95's avatar
Tortue95 committed
4
import nacl.hash
5
from scrypt import hash
Tortue95's avatar
Tortue95 committed
6
import pyaes
7 8 9
from getpass import getpass
from os import path
from re import compile, search
10
import click
Tortue95's avatar
Tortue95 committed
11

Moul's avatar
Moul committed
12

13 14 15
@click.pass_context
def auth_method(ctx):
    if ctx.obj["AUTH_SEED"]:
Tortue95's avatar
Tortue95 committed
16
        return auth_by_seed()
17 18 19
    if ctx.obj["AUTH_FILE"]:
        return auth_by_auth_file()
    if ctx.obj["AUTH_WIF"]:
20
        return auth_by_wif()
21
    else:
22
        return auth_by_scrypt()
Tortue95's avatar
Tortue95 committed
23

Moul's avatar
Moul committed
24

25 26
def generate_auth_file(file):
    if not file:
Tortue95's avatar
Tortue95 committed
27
        file = "authfile"
28
    seed = auth_method()
Moul's avatar
Moul committed
29
    with open(file, "w") as f:
Tortue95's avatar
Tortue95 committed
30
        f.write(seed)
Moul's avatar
Moul committed
31 32 33 34 35
    print(
        "Authentication file 'authfile' generated and stored in current\
 folder for following public key:",
        get_publickey_from_seed(seed),
    )
Tortue95's avatar
Tortue95 committed
36

Moul's avatar
Moul committed
37

38 39 40 41
@click.pass_context
def auth_by_auth_file(ctx):
    if ctx.obj["AUTH_FILE_PATH"]:
        file = ctx.obj["AUTH_FILE_PATH"]
Tortue95's avatar
Tortue95 committed
42 43
    else:
        file = "authfile"
44
    if not path.isfile(file):
Moul's avatar
Moul committed
45
        message_exit('Error: the file "' + file + '" does not exist')
Moul's avatar
Moul committed
46
    with open(file) as f:
Tortue95's avatar
Tortue95 committed
47 48
        filetxt = f.read()

Moul's avatar
Moul committed
49 50 51 52
    regex_seed = compile("^[0-9a-fA-F]{64}$")
    regex_gannonce = compile(
        "^pub: [1-9A-HJ-NP-Za-km-z]{43,44}\nsec: [1-9A-HJ-NP-Za-km-z]{88,90}.*$"
    )
Tortue95's avatar
Tortue95 committed
53
    # Seed Format
54
    if search(regex_seed, filetxt):
Tortue95's avatar
Tortue95 committed
55
        seed = filetxt[0:64]
Tortue95's avatar
Tortue95 committed
56
    # gannonce.duniter.org Format
57
    elif search(regex_gannonce, filetxt):
Tortue95's avatar
Tortue95 committed
58
        private_key = filetxt.split("sec: ")[1].split("\n")[0]
59
        seed = encoding.HexEncoder.encode(b58_decode(private_key))[0:64].decode("utf-8")
Tortue95's avatar
Tortue95 committed
60
    else:
61
        message_exit("Error: the format of the file is invalid")
Tortue95's avatar
Tortue95 committed
62 63
    return seed

Tortue95's avatar
Tortue95 committed
64

Tortue95's avatar
Tortue95 committed
65
def auth_by_seed():
Moul's avatar
Moul committed
66
    seed = input("Please enter your seed on hex format: ")
Moul's avatar
Moul committed
67
    regex = compile("^[0-9a-fA-F]{64}$")
68
    if not search(regex, seed):
69
        message_exit("Error: the format of the seed is invalid")
Tortue95's avatar
Tortue95 committed
70 71 72
    return seed


73 74
@click.pass_context
def auth_by_scrypt(ctx):
75 76
    salt = getpass("Please enter your Scrypt Salt (Secret identifier): ")
    password = getpass("Please enter your Scrypt password (masked): ")
Tortue95's avatar
Tortue95 committed
77

78 79
    if ctx.obj["AUTH_SCRYPT_PARAMS"]:
        n, r, p = ctx.obj["AUTH_SCRYPT_PARAMS"].split(",")
80 81
        if n.isnumeric() and r.isnumeric() and p.isnumeric():
            n, r, p = int(n), int(r), int(p)
Jean-Yves's avatar
Jean-Yves committed
82
            if n <= 0 or n > 65536 or r <= 0 or r > 512 or p <= 0 or p > 32:
83
                message_exit("Error: the values of Scrypt parameters are not good")
84
        else:
85
            message_exit("one of n, r or p is not a number")
86 87 88 89
    else:
        print("Using default values. Scrypt parameters not specified or wrong format")
        n, r, p = 4096, 16, 1
    print("Scrypt parameters used: N: {0}, r: {1}, p: {2}".format(n, r, p))
Tortue95's avatar
Tortue95 committed
90

Moul's avatar
Moul committed
91
    return get_seed_from_scrypt(salt, password, n, r, p)
92 93 94


def auth_by_wif():
Tortue95's avatar
Tortue95 committed
95 96
    wif = input("Please enter your WIF or Encrypted WIF address: ")

Moul's avatar
Moul committed
97
    regex = compile("^[1-9A-HJ-NP-Za-km-z]*$")
98
    if not search(regex, wif):
99
        message_exit("Error: the format of WIF is invalid")
Tortue95's avatar
Tortue95 committed
100 101 102 103

    wif_bytes = b58_decode(wif)
    fi = wif_bytes[0:1]

Moul's avatar
Moul committed
104
    if fi == b"\x01":
Tortue95's avatar
Tortue95 committed
105
        return get_seed_from_wifv1(wif)
Moul's avatar
Moul committed
106 107
    elif fi == b"\x02":
        password = getpass("Please enter the " + "password of WIF (masked): ")
Tortue95's avatar
Tortue95 committed
108
        return get_seed_from_ewifv1(wif, password)
Tortue95's avatar
Tortue95 committed
109

110
    message_exit("Error: the format of WIF is invalid or unknown")
Tortue95's avatar
Tortue95 committed
111 112 113


def get_seed_from_scrypt(salt, password, N=4096, r=16, p=1):
114 115
    seed = hash(password, salt, N, r, p, 32)
    seedhex = encoding.HexEncoder.encode(seed).decode("utf-8")
Tortue95's avatar
Tortue95 committed
116
    return seedhex
Tortue95's avatar
Tortue95 committed
117 118 119


def get_seed_from_wifv1(wif):
Moul's avatar
Moul committed
120
    regex = compile("^[1-9A-HJ-NP-Za-km-z]*$")
121
    if not search(regex, wif):
122
        message_exit("Error: the format of WIF is invalid")
Tortue95's avatar
Tortue95 committed
123 124 125

    wif_bytes = b58_decode(wif)
    if len(wif_bytes) != 35:
126
        message_exit("Error: the size of WIF is invalid")
Tortue95's avatar
Tortue95 committed
127 128 129 130 131 132

    checksum_from_wif = wif_bytes[-2:]
    fi = wif_bytes[0:1]
    seed = wif_bytes[1:-2]
    seed_fi = wif_bytes[0:-2]

Moul's avatar
Moul committed
133
    if fi != b"\x01":
134
        message_exit("Error: It's not a WIF format")
Tortue95's avatar
Tortue95 committed
135

Tortue95's avatar
Tortue95 committed
136 137
    # checksum control
    checksum = nacl.hash.sha256(
Moul's avatar
Moul committed
138 139
        nacl.hash.sha256(seed_fi, encoding.RawEncoder), encoding.RawEncoder
    )[0:2]
Tortue95's avatar
Tortue95 committed
140
    if checksum_from_wif != checksum:
141
        message_exit("Error: bad checksum of the WIF")
Tortue95's avatar
Tortue95 committed
142

143
    seedhex = encoding.HexEncoder.encode(seed).decode("utf-8")
Tortue95's avatar
Tortue95 committed
144 145 146
    return seedhex


Tortue95's avatar
Tortue95 committed
147
def get_seed_from_ewifv1(ewif, password):
Moul's avatar
Moul committed
148
    regex = compile("^[1-9A-HJ-NP-Za-km-z]*$")
149
    if not search(regex, ewif):
150
        message_exit("Error: the format of EWIF is invalid")
Tortue95's avatar
Tortue95 committed
151 152 153

    wif_bytes = b58_decode(ewif)
    if len(wif_bytes) != 39:
154
        message_exit("Error: the size of EWIF is invalid")
Tortue95's avatar
Tortue95 committed
155 156 157 158 159 160 161 162

    wif_no_checksum = wif_bytes[0:-2]
    checksum_from_ewif = wif_bytes[-2:]
    fi = wif_bytes[0:1]
    salt = wif_bytes[1:5]
    encryptedhalf1 = wif_bytes[5:21]
    encryptedhalf2 = wif_bytes[21:37]

Moul's avatar
Moul committed
163
    if fi != b"\x02":
164
        message_exit("Error: It's not a EWIF format")
Tortue95's avatar
Tortue95 committed
165

Tortue95's avatar
Tortue95 committed
166 167
    # Checksum Control
    checksum = nacl.hash.sha256(
Moul's avatar
Moul committed
168 169
        nacl.hash.sha256(wif_no_checksum, encoding.RawEncoder), encoding.RawEncoder
    )[0:2]
Tortue95's avatar
Tortue95 committed
170
    if checksum_from_ewif != checksum:
171
        message_exit("Error: bad checksum of EWIF address")
Tortue95's avatar
Tortue95 committed
172

Tortue95's avatar
Tortue95 committed
173
    # SCRYPT
Tortue95's avatar
Tortue95 committed
174
    password = password.encode("utf-8")
175
    scrypt_seed = hash(password, salt, 16384, 8, 8, 64)
Tortue95's avatar
Tortue95 committed
176 177 178
    derivedhalf1 = scrypt_seed[0:32]
    derivedhalf2 = scrypt_seed[32:64]

Tortue95's avatar
Tortue95 committed
179
    # AES
Tortue95's avatar
Tortue95 committed
180 181 182 183
    aes = pyaes.AESModeOfOperationECB(derivedhalf2)
    decryptedhalf1 = aes.decrypt(encryptedhalf1)
    decryptedhalf2 = aes.decrypt(encryptedhalf2)

Tortue95's avatar
Tortue95 committed
184 185 186
    # XOR
    seed1 = xor_bytes(decryptedhalf1, derivedhalf1[0:16])
    seed2 = xor_bytes(decryptedhalf2, derivedhalf1[16:32])
Moul's avatar
Moul committed
187
    seed = seed1 + seed2
188
    seedhex = encoding.HexEncoder.encode(seed).decode("utf-8")
Tortue95's avatar
Tortue95 committed
189

Tortue95's avatar
Tortue95 committed
190 191
    # Password Control
    salt_from_seed = nacl.hash.sha256(
Moul's avatar
Moul committed
192 193 194 195 196
        nacl.hash.sha256(
            b58_decode(get_publickey_from_seed(seedhex)), encoding.RawEncoder
        ),
        encoding.RawEncoder,
    )[0:4]
Tortue95's avatar
Tortue95 committed
197
    if salt_from_seed != salt:
198
        message_exit("Error: bad Password of EWIF address")
Tortue95's avatar
Tortue95 committed
199 200

    return seedhex