Completed
Push — master ( 2d4f92...dbbefe )
by John
01:23
created

hs3512()   A

Complexity

Conditions 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 0
loc 17
rs 9.4285
1
#!/usr/bin/env python3
2
"""This module is used to generate file hashes/checksums and PGP signatures."""
3
4
import zlib  # crc32/adler32
5
import hashlib  # all other hashes
6
import hmac  # escreens is a hmac, news at 11
7
import configparser  # config parsing, duh
8
import os  # path work
9
import concurrent.futures  # parallelization
10
import gnupg  # interface b/w Python, GPG
11
from bbarchivist import bbconstants  # premade stuff
12
from bbarchivist import utilities  # cores
13
14
__author__ = "Thurask"
15
__license__ = "WTFPL v2"
16
__copyright__ = "Copyright 2015-2016 Thurask"
17
18
19
def hc32(filepath, blocksize=16 * 1024 * 1024):
20
    """
21
    Return CRC32 checksum of a file.
22
23
    :param filepath: File you wish to verify.
24
    :type filepath: str
25
26
    :param blocksize: How much of file to read at once.
27
    :type blocksize: int
28
    """
29
    seed = 0
30
    with open(filepath, 'rb') as file:
31
        for chunk in iter(lambda: file.read(blocksize), b''):
32
            seed = zlib.crc32(chunk, seed)
33
    final = format(seed & 0xFFFFFFFF, "08x")
34
    return final
35
36
37
def ha32(filepath, blocksize=16 * 1024 * 1024):
38
    """
39
    Return Adler32 checksum of a file.
40
41
    :param filepath: File you wish to verify.
42
    :type filepath: str
43
44
    :param blocksize: How much of file to read at once.
45
    :type blocksize: int
46
    """
47
    asum = 1
48
    with open(filepath, 'rb') as file:
49
        while True:
50
            data = file.read(blocksize)
51
            if not data:
52
                break
53
            asum = zlib.adler32(data, asum)
54
            if asum < 0:
55
                asum += 2 ** 32
56
    final = format(asum & 0xFFFFFFFF, "08x")
57
    return final
58
59
60
def hs1(filepath, blocksize=16 * 1024 * 1024):
61
    """
62
    Return SHA-1 hash of a file.
63
64
    :param filepath: File you wish to verify.
65
    :type filepath: str
66
67
    :param blocksize: How much of file to read at once.
68
    :type blocksize: int
69
    """
70
    sha1 = hashlib.sha1()
71
    with open(filepath, 'rb') as file:
0 ignored issues
show
Unused Code introduced by
The variable file seems to be unused.
Loading history...
72
        hashfunc_reader(filepath, sha1, blocksize)
73
    return sha1.hexdigest()
74
75
76
def hs224(filepath, blocksize=16 * 1024 * 1024):
77
    """
78
    Return SHA-224 hash of a file.
79
80
    :param filepath: File you wish to verify.
81
    :type filepath: str
82
83
    :param blocksize: How much of file to read at once.
84
    :type blocksize: int
85
    """
86
    sha224 = hashlib.sha224()
87
    with open(filepath, 'rb') as file:
0 ignored issues
show
Unused Code introduced by
The variable file seems to be unused.
Loading history...
88
        hashfunc_reader(filepath, sha224, blocksize)
89
    return sha224.hexdigest()
90
91
92
def hs256(filepath, blocksize=16 * 1024 * 1024):
93
    """
94
    Return SHA-256 hash of a file.
95
96
    :param filepath: File you wish to verify.
97
    :type filepath: str
98
99
    :param blocksize: How much of file to read at once.
100
    :type blocksize: int
101
    """
102
    sha256 = hashlib.sha256()
103
    with open(filepath, 'rb') as file:
0 ignored issues
show
Unused Code introduced by
The variable file seems to be unused.
Loading history...
104
        hashfunc_reader(filepath, sha256, blocksize)
105
    return sha256.hexdigest()
106
107
108
def hs384(filepath, blocksize=16 * 1024 * 1024):
109
    """
110
    Return SHA-384 hash of a file.
111
112
    :param filepath: File you wish to verify.
113
    :type filepath: str
114
115
    :param blocksize: How much of file to read at once.
116
    :type blocksize: int
117
    """
118
    sha384 = hashlib.sha384()
119
    with open(filepath, 'rb') as file:
0 ignored issues
show
Unused Code introduced by
The variable file seems to be unused.
Loading history...
120
        hashfunc_reader(filepath, sha384, blocksize)
121
    return sha384.hexdigest()
122
123
124
def hs512(filepath, blocksize=16 * 1024 * 1024):
125
    """
126
    Return SHA-512 hash of a file.
127
128
    :param filepath: File you wish to verify.
129
    :type filepath: str
130
131
    :param blocksize: How much of file to read at once.
132
    :type blocksize: int
133
    """
134
    sha512 = hashlib.sha512()
135
    with open(filepath, 'rb') as file:
0 ignored issues
show
Unused Code introduced by
The variable file seems to be unused.
Loading history...
136
        hashfunc_reader(filepath, sha512, blocksize)
137
    return sha512.hexdigest()
138
139
140
def hs3224(filepath, blocksize=16 * 1024 * 1024):
141
    """
142
    Return SHA3-224 hash of a file.
143
144
    :param filepath: File you wish to verify.
145
    :type filepath: str
146
147
    :param blocksize: How much of file to read at once.
148
    :type blocksize: int
149
    """
150
    try:
151
        sha3224 = hashlib.sha3_224()
152
    except AttributeError:
153
        print("REQUIRES PYTHON 3.6+")
154
    else:
155
        hashfunc_reader(filepath, sha3224, blocksize)
156
        return sha3224.hexdigest()
157
158
159
def hs3256(filepath, blocksize=16 * 1024 * 1024):
160
    """
161
    Return SHA3-256 hash of a file.
162
163
    :param filepath: File you wish to verify.
164
    :type filepath: str
165
166
    :param blocksize: How much of file to read at once.
167
    :type blocksize: int
168
    """
169
    try:
170
        sha3256 = hashlib.sha3_256()
171
    except AttributeError:
172
        print("REQUIRES PYTHON 3.6+")
173
    else:
174
        hashfunc_reader(filepath, sha3256, blocksize)
175
        return sha3256.hexdigest()
176
177
178
def hs3384(filepath, blocksize=16 * 1024 * 1024):
179
    """
180
    Return SHA3-384 hash of a file.
181
182
    :param filepath: File you wish to verify.
183
    :type filepath: str
184
185
    :param blocksize: How much of file to read at once.
186
    :type blocksize: int
187
    """
188
    try:
189
        sha3384 = hashlib.sha3_384()
190
    except AttributeError:
191
        print("REQUIRES PYTHON 3.6+")
192
    else:
193
        hashfunc_reader(filepath, sha3384, blocksize)
194
        return sha3384.hexdigest()
195
196
197
def hs3512(filepath, blocksize=16 * 1024 * 1024):
198
    """
199
    Return SHA3-512 hash of a file.
200
201
    :param filepath: File you wish to verify.
202
    :type filepath: str
203
204
    :param blocksize: How much of file to read at once.
205
    :type blocksize: int
206
    """
207
    try:
208
        sha3512 = hashlib.sha3_512()
209
    except AttributeError:
210
        print("REQUIRES PYTHON 3.6+")
211
    else:
212
        hashfunc_reader(filepath, sha3512, blocksize)
213
        return sha3512.hexdigest()
214
215
216
def hm5(filepath, blocksize=16 * 1024 * 1024):
217
    """
218
    Return MD5 hash of a file.
219
220
    :param filepath: File you wish to verify.
221
    :type filepath: str
222
223
    :param blocksize: How much of file to read at once.
224
    :type blocksize: int
225
    """
226
    md5 = hashlib.md5()
227
    with open(filepath, 'rb') as file:
228
        while True:
229
            data = file.read(blocksize)
230
            if not data:
231
                break
232
            md5.update(data)
233
    return md5.hexdigest()
234
235
236
def hashfunc_reader(filepath, engine, blocksize=16 * 1024 * 1024):
237
    """
238
    Generate hash from file contents.
239
240
    :param filepath: File you wish to verify.
241
    :type filepath: str
242
243
    :param engine: Hash object to update with file contents.
244
    :type engine: _hashlib.HASH
245
246
    :param blocksize: How much of file to read at once.
247
    :type blocksize: int
248
    """
249
    with open(filepath, 'rb') as file:
250
        while True:
251
            data = file.read(blocksize)
252
            if not data:
253
                break
254
            engine.update(data)
255
256
257
def ssl_hash(filepath, method, blocksize=16 * 1024 * 1024):
258
    """
259
    Return SSL-library dependent hash of a file.
260
261
    :param filepath: File you wish to verify.
262
    :type filepath: str
263
264
    :param method: Method to use: algorithms in hashlib that are not guaranteed.
265
    :type method: str
266
267
    :param blocksize: How much of file to read at once.
268
    :type blocksize: int
269
    """
270
    try:
271
        engine = hashlib.new(method)
272
        hashfunc_reader(filepath, engine, blocksize)
273
        return engine.hexdigest()
274
    except ValueError as exc:
275
        print(str(exc))
276
        print("{0} HASH FAILED".format(method.upper()))
277
278
279
def hm4(filepath, blocksize=16 * 1024 * 1024):
280
    """
281
    Return MD4 hash of a file; depends on system SSL library.
282
283
    :param filepath: File you wish to verify.
284
    :type filepath: str
285
286
    :param blocksize: How much of file to read at once.
287
    :type blocksize: int
288
    """
289
    return ssl_hash(filepath, "md4", blocksize)
290
291
292
def hr160(filepath, blocksize=16 * 1024 * 1024):
293
    """
294
    Return RIPEMD160 hash of a file; depends on system SSL library.
295
296
    :param filepath: File you wish to verify.
297
    :type filepath: str
298
299
    :param blocksize: How much of file to read at once.
300
    :type blocksize: int
301
    """
302
    return ssl_hash(filepath, "ripemd160", blocksize)
303
304
305
def hwp(filepath, blocksize=16 * 1024 * 1024):
306
    """
307
    Return Whirlpool hash of a file; depends on system SSL library.
308
309
    :param filepath: File you wish to verify.
310
    :type filepath: str
311
312
    :param blocksize: How much of file to read at once.
313
    :type blocksize: int
314
    """
315
    return ssl_hash(filepath, "whirlpool", blocksize)
316
317
318
def hs0(filepath, blocksize=16 * 1024 * 1024):
319
    """
320
    Return SHA-0 hash of a file; depends on system SSL library.
321
322
    :param filepath: File you wish to verify.
323
    :type filepath: str
324
325
    :param blocksize: How much of file to read at once.
326
    :type blocksize: int
327
    """
328
    return ssl_hash(filepath, "sha", blocksize)
329
330
331
def gpgfile(filepath, gpginst, key=None, pword=None):
332
    """
333
    Make ASCII-armored signature files with a given private key.
334
    Takes an instance of gnupg.GPG().
335
336
    :param filepath: File you wish to verify.
337
    :type filepath: str
338
339
    :param gpginst: Instance of Python GnuPG executable.
340
    :type gpginst: gnupg.GPG()
341
342
    :param key: Key ID. 0xABCDEF01
343
    :type key: str
344
345
    :param pword: Passphrase for key.
346
    :type pword: str
347
    """
348
    with open(filepath, "rb") as file:
349
        fname = file.name + ".asc"
350
        gpginst.sign_file(file, detach=True, keyid=key, passphrase=pword, output=fname)
351
352
353
def calculate_escreens(pin, app, uptime, duration=30):
354
    """
355
    Calculate key for the Engineering Screens based on input.
356
357
    :param pin: PIN to check. 8 character hexadecimal, lowercase.
358
    :type pin: str
359
360
    :param app: App version. 10.x.y.zzzz.
361
    :type app: str
362
363
    :param uptime: Uptime in ms.
364
    :type uptime: str
365
366
    :param duration: 1, 3, 6, 15, 30 (days).
367
    :type duration: str
368
    """
369
    #: Somehow, values for lifetimes for escreens.
370
    lifetimes = {
371
        1: "",
372
        3: "Hello my baby, hello my honey, hello my rag time gal",
373
        7: "He was a boy, and she was a girl, can I make it any more obvious?",
374
        15: "So am I, still waiting, for this world to stop hating?",
375
        30: "I love myself today, not like yesterday. "
376
    }
377
    lifetimes[30] += "I'm cool, I'm calm, I'm gonna be okay"
378
    #: Escreens magic HMAC secret.
379
    secret = 'Up the time stream without a TARDIS'
380
    duration = int(duration)
381
    if duration not in [1, 3, 6, 15, 30]:
382
        duration = 1
383
    data = pin.lower() + app + str(uptime) + lifetimes[duration]
384
    newhmac = hmac.new(secret.encode(), data.encode(), digestmod=hashlib.sha1)
385
    key = newhmac.hexdigest()[:8]
386
    return key.upper()
387
388
389
def hash_get(filename, hashfunc, workingdir, blocksize=16777216):
390
    """
391
    Generate and pretty format the hash result for a file.
392
393
    :param filename: File to hash.
394
    :type filename: str
395
396
    :param hashfunc: Hash function to use.
397
    :type hashfunc: function
398
399
    :param workingdir: Working directory.
400
    :type workingdir: str
401
402
    :param blocksize: Block size. Default is 16MB.
403
    :type blocksize: int
404
    """
405
    result = hashfunc(os.path.join(workingdir, filename), blocksize)
406
    return "{0} {1}\n".format(result.upper(), os.path.basename(filename))
407
408
409
def base_hash(hashtype, source, workingdir, block, hashfunc, target, kwargs=None):
410
    """
411
    Generic hash function; get hash, write to file.
412
413
    :param hashtype: Hash type.
414
    :type hashtype: str
415
416
    :param source: File to be hashed; foobar.ext
417
    :type source: str
418
419
    :param workingdir: Path containing files you wish to verify.
420
    :type workingdir: str
421
422
    :param block: Blocksize, in bytes.
423
    :type block: int
424
425
    :param target: File to write to.
426
    :type target: file
427
428
    :param kwargs: Values. Refer to `:func:verifier_config_loader`.
429
    :type kwargs: dict
430
    """
431
    if kwargs[hashtype]:
432
        hash_generic = [hashtype.upper()]
433
        hash_generic.append(hash_get(source, hashfunc, workingdir, block))
434
        target.write("\n".join(hash_generic))
435
436
437
def hash_writer(source, dest, workingdir, kwargs=None):
438
    """
439
    Write per-file hashes.
440
441
    :param source: File to be hashed; foobar.ext
442
    :type source: str
443
444
    :param dest: Destination file; foobar.ext.cksum
445
    :type dest: str
446
447
    :param workingdir: Path containing files you wish to verify.
448
    :type workingdir: str
449
450
    :param kwargs: Values. Refer to `:func:verifier_config_loader`.
451
    :type kwargs: dict
452
    """
453
    block = int(kwargs['blocksize'])
454
    with open(dest, 'w') as target:
455
        base_hash("adler32", source, workingdir, block, ha32, target, kwargs)
456
        base_hash("crc32", source, workingdir, block, hc32, target, kwargs)
457
        base_hash("md4", source, workingdir, block, hm4, target, kwargs)
458
        base_hash("md5", source, workingdir, block, hm5, target, kwargs)
459
        base_hash("sha0", source, workingdir, block, hs0, target, kwargs)
460
        base_hash("sha1", source, workingdir, block, hs1, target, kwargs)
461
        base_hash("sha224", source, workingdir, block, hs224, target, kwargs)
462
        base_hash("sha256", source, workingdir, block, hs256, target, kwargs)
463
        base_hash("sha384", source, workingdir, block, hs384, target, kwargs)
464
        base_hash("sha512", source, workingdir, block, hs512, target, kwargs)
465
        base_hash("ripemd160", source, workingdir, block, hr160, target, kwargs)
466
        base_hash("whirlpool", source, workingdir, block, hwp, target, kwargs)
467
        base_hash("sha3224", source, workingdir, block, hs3224, target, kwargs)
468
        base_hash("sha3256", source, workingdir, block, hs3256, target, kwargs)
469
        base_hash("sha3384", source, workingdir, block, hs3384, target, kwargs)
470
        base_hash("sha3512", source, workingdir, block, hs3512, target, kwargs)
471
472
473
def filefilter(file, workingdir, extras=()):
474
    """
475
    Check if file in folder is a folder, or if it's got a forbidden extension.
476
477
    :param file: File to be hashed.
478
    :type file: str
479
480
    :param workingdir: Path containing files you wish to verify.
481
    :type workingdir: str
482
483
    :param extras: Tuple of extra extensions.
484
    :type extras: tuple
485
    """
486
    return not (os.path.isdir(os.path.join(workingdir, file)) or file.endswith(bbconstants.SUPPS + extras))
487
488 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
489
def verifier(ldir, kwargs=None, selective=False):
490
    """
491
    For all files in a directory, perform various hash/checksum functions.
492
    Take dict to define hashes, write output to a/individual .cksum file(s).
493
494
    :param ldir: Path containing files you wish to verify.
495
    :type ldir: str
496
497
    :param kwargs: Values. Refer to `:func:verifier_config_loader`.
498
    :type kwargs: dict
499
    """
500
    if kwargs is None:
501
        kwargs = verifier_config_loader()
502
    exts = (".txt",) if selective else ()
503
    files = [os.path.join(ldir, file) for file in os.listdir(ldir) if filefilter(file, ldir, exts)]
504
    with concurrent.futures.ThreadPoolExecutor(max_workers=utilities.workers(files)) as xec:
505
        for file in files:
506
            print("HASHING:", os.path.basename(file))
507
            basename = file + ".cksum"
508
            targetname = os.path.join(ldir, basename)
509
            try:
510
                xec.submit(hash_writer, file, targetname, ldir, kwargs)
511
            except Exception as exc:
512
                print("SOMETHING WENT WRONG")
513
                print(str(exc))
514
                raise SystemExit
515
516
517
def gpgrunner(workingdir, keyid=None, pword=None, selective=False):
518
    """
519
    Create ASCII-armored PGP signatures for all files in a given directory, in parallel.
520
521
    :param workingdir: Path containing files you wish to verify.
522
    :type workingdir: str
523
524
    :param keyid: Key to use. 8-character hexadecimal, with or without 0x.
525
    :type keyid: str
526
527
    :param pword: Passphrase for given key.
528
    :type pword: str
529
530
    :param selective: Filtering filenames/extensions. Default is false.
531
    :type selective: bool
532
    """
533
    try:
534
        gpg = gnupg.GPG()
535
    except ValueError:
536
        print("COULD NOT FIND GnuPG!")
537
        raise SystemExit
538
    else:
539
        keyid = "0x" + keyid.upper() if not keyid.startswith("0x") else keyid.upper().replace("X", "x")
540
        dirlist = os.listdir(workingdir)
541
        files = [file for file in dirlist if filefilter(file, workingdir)]
542
        with concurrent.futures.ThreadPoolExecutor(max_workers=utilities.workers(files)) as xec:
543
            for file in files:
544
                gpgwriter(gpg, xec, file, workingdir, selective, keyid, pword)
545
546
547
def gpgwriter(gpg, xec, file, workingdir, selective=False, keyid=None, pword=None):
548
    """
549
    :param gpg: Instance of Python GnuPG executable.
550
    :type gpg: gnupg.GPG()
551
552
    :param xec: ThreadPoolExecutor instance.
553
    :type xec: concurrent.futures.ThreadPoolExecutor
554
555
    :param file: File inside workingdir that is being verified.
556
    :type file: str
557
558
    :param workingdir: Path containing files you wish to verify.
559
    :type workingdir: str
560
561
    :param selective: Filtering filenames/extensions. Default is false.
562
    :type selective: bool
563
564
    :param keyid: Key to use. 8-character hexadecimal, with or without 0x.
565
    :type keyid: str
566
567
    :param pword: Passphrase for given key.
568
    :type pword: str
569
    """
570
    sup = bbconstants.SUPPS + (".txt",) if selective else bbconstants.SUPPS
571
    if not file.endswith(sup):
572
        aps = bbconstants.ARCSPLUS
573
        pfx = bbconstants.PREFIXES
574
        if (utilities.prepends(file, pfx, aps)) if selective else True:
575
            print("VERIFYING:", os.path.basename(file))
576
            thepath = os.path.join(workingdir, file)
577
            try:
578
                xec.submit(gpgfile, thepath, gpg, keyid, pword)
579
            except Exception as exc:
580
                print("SOMETHING WENT WRONG")
581
                print(str(exc))
582
                raise SystemExit
583
584
585
def gpg_config_loader(homepath=None):
586
    """
587
    Read a ConfigParser file to get PGP key, password (optional)
588
589
    :param homepath: Folder containing ini file. Default is user directory.
590
    :type homepath: str
591
    """
592
    config = configparser.ConfigParser()
593
    if homepath is None:
594
        homepath = os.path.expanduser("~")
595
    conffile = os.path.join(homepath, "bbarchivist.ini")
596
    if not os.path.exists(conffile):
597
        open(conffile, 'w').close()
598
    config.read(conffile)
599
    if not config.has_section('gpgrunner'):
600
        config['gpgrunner'] = {}
601
    gpgkey = config.get('gpgrunner', 'key', fallback=None)
602
    gpgpass = config.get('gpgrunner', 'pass', fallback=None)
603
    return gpgkey, gpgpass
604
605
606
def gpg_config_writer(key=None, password=None, homepath=None):
607
    """
608
    Write a ConfigParser file to store PGP key, password (optional)
609
610
    :param key: Key ID, leave as None to not write.
611
    :type key: str
612
613
    :param password: Key password, leave as None to not write.
614
    :type password: str
615
616
    :param homepath: Folder containing ini file. Default is user directory.
617
    :type homepath: str
618
    """
619
    config = configparser.ConfigParser()
620
    if homepath is None:
621
        homepath = os.path.expanduser("~")
622
    conffile = os.path.join(homepath, "bbarchivist.ini")
623
    if not os.path.exists(conffile):
624
        open(conffile, 'w').close()
625
    config.read(conffile)
626
    if not config.has_section('gpgrunner'):
627
        config['gpgrunner'] = {}
628
    if key is not None:
629
        config['gpgrunner']['key'] = key
630
    if password is not None:
631
        config['gpgrunner']['pass'] = password
632
    with open(conffile, "w") as configfile:
633
        config.write(configfile)
634
635
636
def verifier_config_loader(homepath=None):
637
    """
638
    Read a ConfigParser file to get hash preferences.
639
640
    :param homepath: Folder containing ini file. Default is user directory.
641
    :type homepath: str
642
    """
643
    results = {}
644
    config = configparser.ConfigParser()
645
    if homepath is None:
646
        homepath = os.path.expanduser("~")
647
    conffile = os.path.join(homepath, "bbarchivist.ini")
648
    if not os.path.exists(conffile):
649
        open(conffile, 'w').close()
650
    config.read(conffile)
651
    if not config.has_section('hashmodes'):
652
        config['hashmodes'] = {}
653
    ini = config['hashmodes']
654
    results['crc32'] = bool(ini.getboolean('crc32', fallback=False))
655
    results['adler32'] = bool(ini.getboolean('adler32', fallback=False))
656
    results['sha0'] = bool(ini.getboolean('sha0', fallback=False))
657
    results['sha1'] = bool(ini.getboolean('sha1', fallback=True))
658
    results['sha224'] = bool(ini.getboolean('sha224', fallback=False))
659
    results['sha256'] = bool(ini.getboolean('sha256', fallback=True))
660
    results['sha384'] = bool(ini.getboolean('sha384', fallback=False))
661
    results['sha512'] = bool(ini.getboolean('sha512', fallback=False))
662
    results['md5'] = bool(ini.getboolean('md5', fallback=True))
663
    results['md4'] = bool(ini.getboolean('md4', fallback=False))
664
    results['ripemd160'] = bool(ini.getboolean('ripemd160', fallback=False))
665
    results['whirlpool'] = bool(ini.getboolean('whirlpool', fallback=False))
666
    results['blocksize'] = int(ini.getint('blocksize', fallback=16777216))
667
    results['sha3224'] = bool(ini.getboolean('sha3224', fallback=False))
668
    results['sha3256'] = bool(ini.getboolean('sha3256', fallback=False))
669
    results['sha3384'] = bool(ini.getboolean('sha3384', fallback=False))
670
    results['sha3512'] = bool(ini.getboolean('sha3512', fallback=False))
671
    return results
672
673
674
def verifier_config_writer(resultdict=None, homepath=None):
675
    """
676
    Write a ConfigParser file to store hash preferences.
677
678
    :param resultdict: Dictionary of results: {method, bool}
679
    :type resultdict: dict({str, bool})
680
681
    :param homepath: Folder containing ini file. Default is user directory.
682
    :type homepath: str
683
    """
684
    if resultdict is None:
685
        resultdict = verifier_config_loader()
686
    config = configparser.ConfigParser()
687
    if homepath is None:
688
        homepath = os.path.expanduser("~")
689
    conffile = os.path.join(homepath, "bbarchivist.ini")
690
    if not os.path.exists(conffile):
691
        open(conffile, 'w').close()
692
    config.read(conffile)
693
    if not config.has_section('hashmodes'):
694
        config['hashmodes'] = {}
695
    for method, flag in resultdict.items():
696
        config.set('hashmodes', method, str(flag).lower())
697
    with open(conffile, "w") as configfile:
698
        config.write(configfile)
699