Some debugging and better seed for random()
[signduterre.git] / signduterre.py
blobc3cc3c32ed4be10a40d2e588bb936fabcccc8301
1 #!/usr/bin/python
2 manual = """
3 Signature-du-Terroir
4 Construct a signature of the installed software state or check the integrity of the installation
5 using a previously made signature.
7 Usage: signduterre.py [options] FILE1 FILE2 ...
9 Options:
10 -h, --help show this help message and exit
11 -s HEX, --salt=HEX Enter salt in hexadecimal. If not given, a salt will
12 be suggested
13 -p TEXT, --passphrase=TEXT
14 Enter passphrase in cleartext
15 -c FILE, --check-file=FILE
16 Check contents of file (output of previous run)
17 -i FILE, --input-file=FILE
18 Use names from input-file (one filename per line)
19 -u USER, --user=USER Execute $(cmd) as USER, default 'nobody' (root/sudo
20 only)
21 -g N, --generate-passphrases=N
22 Generate and print N passphrases, one of which will be
23 used (default N=1)
24 -S, --Status For each file, add a line with unvarying file status
25 information: st_mode, st_ino, st_dev, st_uid, st_gid,
26 and st_size (like the '?' prefix, default False)
27 -t, --total-only Only print the total hash, must be checked BEFORE
28 running --detail (default True)
29 -d, --detailed-view Print hashes of individual files, must be checked
30 AFTER running --total-only check (default False)
31 -e, --execute Interpret $(cmd;...) (default False)
32 -n, --no-execute Explicitely do NOT Interpret ${ENV} and $(cmd)
33 -m, --manual Print the manual and exit
34 -r, --release-notes Print the release notes and exit
35 -l, --license Print license text and exit
36 -v, --verbose Print more information
38 FILE1 FILE2 ...
39 Names and paths of one or more files to be checked. Any name starting with a '$', eg, $PATH, will be
40 interpreted as an environmental variable or command according to the bash conventions:
41 '$ENV' and '${ENV}' as variables, '$(cmd;cmd...)' as system commands (bash --restricted -c 'cmd;cmd...' PID).
42 Where PID the current Process ID is (available as positional parameter $0). Do not forget to enclose the
43 arguments in single ''-quotes! The commands are scanned for unwanted characters (eg, ') and these are removed.
44 The use of '$(cmd;cmd...)' requires explicit use of the -e or --execute option.
46 If executed as root or sudo, $(cmd;cmd...) will be executed as 'sudo -H -u <user>' which defaults to
47 --user nobody ('--user root' is at your own risk). This will obviously not work when invoked as non-root/sudo.
48 --user root is necessary when you need to check privileged information, eg,
49 you want to check the MBR with '$(dd if=/dev/hda bs=512 count=1 | od -X)'
50 However, as you might use --check-file with files you did not create yourself, it is important to
51 be warned if commands are to be executed.
53 Interpretation of $() ONLY works if the -e or --execute options are entered. signduterre.py can easily
54 be adapted to automatically use the setting in the check-file. However, this is deemed insecure and
55 commented out in the distribution version.
57 The -n or --no-execute option explicitely supress the interpretation of $(cmd) arguments.
59 Meta information from lstat on files is signed when the filename is preceded by a '?'. '?./signduterre.py' will
60 extract (st_mode, st_ino, st_dev, st_nlinks, st_uid, st_gid, st_size) and hash a line of these data (visible with --verbose).
61 The --Status option will automatically add such a line in front of every file. Note that '?' is implied for directories.
62 both '/' and '?/' produce a hash of, eg,:
63 lstat(/) = [st_mode=041775, st_ino=2, st_dev=234881026, st_uid=0, st_gid=80, st_size=1360]
64 Note that nlinks of a directory include every file in the directory, so this will check whether files have been added
65 to a directory.
67 Signature-du-Terroir
69 A very simple security application to test for the integrity of files and "states" in a computer installation.
70 signduterre.py constructs a signature of the current system state and checks installation state with a previously made signature.
71 The files are hashed with a passphrase to allow detection of compromised systems while running on the same system.
72 The signature checking can be subverted, but the flexibillity of signduterre.py and the fact that the output of any command
73 can be tested makes automated root-kit attacks extremely difficult.
75 signduterre.py writes a total SHA-256 hash to STDOUT of all the files and commands entered as arguments. It can also write a
76 hash for each individual file (insecure). The output of a signature can be send to a file and later used to
77 check with --check-file. Hashes are calculated with a hashed salt + passphrase sequence pre-pended to create
78 unpredictable hashes. This procedure ensures that an attacker does not know whether or not the correct passphrase
79 has been entered. An attacker can only know when to supply the requested hash values if she knows
80 the passphrase or has copies available of all the tested files and output of commands to calculate the hashes
81 on the fly.
83 SECURITY WARNINGS:
85 When run on a compromised system, signduterre.py can be subverted if the attacker keeps a copy of all the files and
86 reroutes the open() and lstat() functions, or simply delegating signduterre.py to a chroot jail with the original system.
87 Except for running from clean boot media (USB?), I know of no solution to this problem ;-)
88 But signduterre.py simply intended to raise the bar.
90 Signature-du-Terroir works on the assumption that any attacker in control of a compromised system cannot
91 predict whether the passphrase entered is correct or not. So, a safe use of signduterre.py is to start with a random
92 number of incorrect passphrases and see whether they fail.
93 Repeat:
94 THE CORRECT USE OF signduterre.py IS TO ENTER A RANDOM NUMBER OF INCORRECT PASSPHRASES FOR EACH TEST
95 AND SEE WHETHER IT FAILS EVERY TIME!
97 On a compromised system, signduterre.py's detailed file testing is easily subverted. With a matched file hash,
98 the attacker can know the correct passphrase has been entered and can print out the stored hashes or 'ok's
99 for the rest of the checks. So if the attacker keeps any entry in the signature file uncompromised,
100 she can intercept the output, test the password on the unchanged entry and substitute the requested hashes
101 for the output if the hash of that entry matches.
103 When checking for root-kits and other malware, it is safest to compare the signature files from a different,
104 clean, system. But then you would not need signduterre.py anyway.
105 If you have to work on the system itself, only use the -t or --total-only options to create
106 signatures with a total hash and without individual file hashes. Such a signature can be used to check
107 whether the system is unchanged. Another signature file WITH A DIFFERENT PASSPHRASE can then be used to
108 identify the individual files that have changed. If a detailed signature file has the same passphrase,
109 an attacker could use that other file to read the individual file hashes to check whether the correct
110 passphrase was entered.
112 As generating and entering wrong passphrases is tedious, it is to be expected that users will take shortcuts.
113 To assist users, the '--generate-passphrases N' option will, together with the '--passphrase SUGGEST' option generate
114 and print N passphrases. One of these is chosen at random for the signature. When checking a file, the stored passphrases
115 can be read in again, either from the '--passphrase <passphrase file>' option, or directly from the --check-file.
116 signduterre.py will print out the result each of the passphrases. Note, that storing passphrases in a
117 file and
118 feeding it to signduterre.py is MUCH less secure than just typing them in.
120 For those who want to know more about what an "ideal attacker" can do, see:
121 Ken Thompson "Reflections on Trusting Trust"
122 http://www.acm.org/classics/sep95/
124 David A Wheeler "Countering Trusting Trust through Diverse Double-Compiling"
125 http://www.acsa-admin.org/2005/abstracts/47.html
127 and the discussion of these at Bruce Schneier's 'Countering "Trusting Trust"'
128 http://www.schneier.com/blog/archives/2006/01/countering_trus.html
130 Manual
132 The intent of signduterre.py is to ensure that the signature cannot be subverted even if the system has been compromised
133 by an attacker that has obtained root control over the computer and any existing signature files.
135 signduterre.py asks for a passphrase which is PRE-pended to every file before the hash is constructed (unless the
136 passphrase is entered with an option). As long as the passphrase is not compromised, the hashes cannot
137 be reconstructed. A randomly generated, unpadded base-64 encoded 16 Byte password (ie, ~22 characters) is suggested in
138 interactive use. If '--passphrase SUGGESTED' is entered on the command line as the salt, the suggested
139 value will be used. This value is printed to STDERR (the screen or 2) for safe keeping. Please, make sure
140 you store the printed passphrase. For instance:
141 python signduterre.py -p SUGGESTED -s SUGGESTED /boot/* /sbin/* /bin/* \\
142 2> Signature_`date "+%Y%m%d_%H-%M-%S"`.pwd > Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
143 will store the passphrase (and all error messages) in a file like 'Signature_20090630_11-14-03.pwd'
144 and the checik-file in 'Signature_20090630_11-14-03.txt'.
146 It is not secure to store files with the passphrase on the system you want to check. However, you could
147 pipe STDERR to some safe site.
149 Good passphrases are difficult to remember, so their plaintext form should be protected. To protect the
150 passphrase against rainbow and brute force attacks, the passphrase is concatenated to a salt phrase and
151 hashed before use (SHA-256).
153 The salt phrase is requested when constructing a signature. In interactive use, an 8 byte hexadecimal
154 (= 16 character) salt from /dev/random is suggested. If '--salt SUGGESTED' is entered on the command line
155 as the salt, the suggested value will be used. The salt is printed in plaintext to the output.
156 The salt will make it more difficult to determine whether the same passphrase has been used to create
157 different signatures.
159 At the bottom, a 'TOTAL HASH' line will be printed that hashes all the lines printed for the files. This includes
160 the file names as printed on the hash lines. It is not inconceivable that existing signature files could be
161 adapted to point to different copies of the files in ways that might be missed when checking the signature.
162 The total hash will point out such changes.
164 Examples:
165 > python signduterre.py --execute --detail --salt 436a73e3 --passphrase liauwefa3251EWC signduterre.py /sbin/* /bin/* \\
166 /usr/bin/find /usr/bin/file /usr/bin/python* '${PATH}' > Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
168 Prints a signature to the file Signature_20090625_14-31-54.txt (with your date). The signature contains the
169 SHA-256 hashes of the files, signduterre.py, /sbin/*, /bin/*, /usr/bin/find, /usr/bin/file, /usr/bin/python*,
170 and a hash of the PATH environment variable.
172 > python signduterre.py --execute --detail --salt SUGGESTED --passphrase SUGGESTED --Status --detailed-view \\
173 signduterre.py /sbin/* /bin/* /usr/bin/find /usr/bin/file /usr/bin/python* '${PATH}' \\
174 2> Signature_`date "+%Y%m%d_%H-%M-%S"`.pwd > Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
176 Prints a signature to the system Signature_20090625_14-31-54.txt (with your date) and the automatically generated
177 password to Signature_20090625_14-31-54.pwd (with your date). The salt will be automatically determined.
178 The signature contains the SHA-256 hashes of the file status and file contents of signduterre.py, /sbin/*,
179 /bin/*, /usr/bin/find, /usr/bin/file, /usr/bin/python* on separate lines, and a hash of the PATH environment variable.
181 > python signduterre.py --execute --passphrase liauwefa3251EWC -c Signature_20090625_14-31-54.txt
183 Will check the files and PATH variable from the signature file Signature_20090625_14-31-54.txt.
185 > python signduterre.py --passphrase liauwefa3251EWC -c Signature_20090625_14-31-54.txt
186 > python signduterre.py --passphrase liauwefa3251EWC -c Signature_20090625_14-31-54.txt --no-execute
188 Will both fail if Signature_20090625_14-31-54.txt contains a $(cmd) entry. The --no-execute
189 option is default and prevents the execute option (if reading the execute optionfrom the signature file
190 has been activated).
192 > python signduterre.py signduterre.py --salt SUGGESTED -passphrase SUGGESTED signduterre.py -g 20 \\
193 &> Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
195 Will generate and print 20 passphrases and print a signature using one randomly chosen passphrase
196 from these 20. Everything is written to a single file 'Signature_20090630_16-44-34.txt'.
198 > python signduterre.py signduterre.py -c Signature_20090630_16-44-34.txt
200 Will check all 20 passphrases generated before from the Signature file and print the results.
202 > sudo python signduterre.py -u root -s SUGGESTED -p SUGGESTED -v -e -t \\
203 '$(ls -LCid /proc/$0/root /proc/$0/exe|sed "s|/$0/|/PID/|g")' '$(dd if=/dev/hda bs=512 count=1 | od -X)' \\
204 >Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
206 Will hash the inode numbers of the effective root directory (eg, chroot) and the executable (python)
207 together with the contents of the MBR (Master Boot Record) in Hex. It uses suggested salt and passphrase.
208 Accessing /dev/hda is only possible when root, so the command is entered with sudo and --user root.
212 license = """
213 Signature-du-Terroir
214 Construct a signature of the installed software state or check a previously made signature.
216 copyright 2009, R.J.J.H. van Son
218 This program is free software: you can redistribute it and/or modify
219 it under the terms of the GNU General Public License as published by
220 the Free Software Foundation, either version 3 of the License, or
221 (at your option) any later version.
223 This program is distributed in the hope that it will be useful,
224 but WITHOUT ANY WARRANTY; without even the implied warranty of
225 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
226 GNU General Public License for more details.
228 You should have received a copy of the GNU General Public License
229 along with this program. If not, see <http://www.gnu.org/licenses/>.
230 """;
232 # Note that only release notes are put here
233 # See git repository for detailed change comments:
234 # git clone git://repo.or.cz/signduterre.git
235 releasenotes = """
236 20090630 - Generating and testing random passphrases
237 20090630 - --execute works on $(cmd) only, nlinks in ?path and ? implied for directories
238 20090630 - Ported to Python 3.0
240 20090628 - Release v0.1b
241 20090628 - Added release-notes
243 20090626 - Release v0.1a
244 20090626 - Initial commit to Git
245 """;
247 import sys;
248 import os;
249 import stat;
250 import subprocess;
251 # if sys.stdout.isatty(): import readline;
252 import binascii;
253 import hashlib;
254 import re;
255 from optparse import OptionParser;
256 import base64;
257 import random;
258 import struct;
260 # Limit the characters that can be used in $(cmd) commands
261 not_allowed_chars = re.compile('[^\w\ \.\/\"\|\;\,\-\$\[\]\{\}\(\)\@\`\!\*]');
263 programname = "Signature-du-Terroir";
264 version = "0.2";
266 print("# Program: "+programname + " version " + version + "\n");
268 parser = OptionParser()
269 parser.add_option("-s", "--salt", metavar="HEX",
270 dest="salt", default=False,
271 help="Enter salt in hexadecimal. If not given, a salt will be suggested")
272 parser.add_option("-p", "--passphrase", metavar="TEXT",
273 dest="passphrase", default=False,
274 help="Enter passphrase in cleartext")
275 parser.add_option("-c", "--check-file",
276 dest="check", default=False, metavar="FILE",
277 help="Check contents of file (output of previous run)")
278 parser.add_option("-i", "--input-file",
279 dest="input", default=False, metavar="FILE",
280 help="Use names from input-file (one filename per line)")
281 parser.add_option("-u", "--user",
282 dest="user", default="nobody", metavar="USER",
283 help="Execute $(cmd) as USER, default 'nobody' (root/sudo only)")
284 parser.add_option("-g", "--generate-passphrases",
285 dest="generate", default=1, type='int', metavar="N",
286 help="Generate and print N passphrases, one of which will be used (default N=1)")
287 parser.add_option("-S", "--Status",
288 dest="status", default=False, action="store_true",
289 help="For each file, add a line with unvarying file status information: st_mode, st_ino, st_dev, st_uid, st_gid, and st_size (like the '?' prefix, default False)")
290 parser.add_option("-t", "--total-only",
291 dest="total", default=False, action="store_true",
292 help="Only print the total hash, must be checked BEFORE running --detail (default True)")
293 parser.add_option("-d", "--detailed-view",
294 dest="detail", default=False, action="store_true",
295 help="Print hashes of individual files, must be checked AFTER running --total-only check (default False)")
296 parser.add_option("-e", "--execute",
297 dest="execute", default=False, action="store_true",
298 help="Interpret $(cmd) (default False)")
299 parser.add_option("-n", "--no-execute",
300 dest="noexecute", default=False, action="store_true",
301 help="Explicitely do NOT Interpret ${ENV} and $(cmd)")
302 parser.add_option("-m", "--manual",
303 dest="manual", default=False, action="store_true",
304 help="Print the manual and exit")
305 parser.add_option("-r", "--release-notes",
306 dest="releasenotes", default=False, action="store_true",
307 help="Print the release notes and exit")
308 parser.add_option("-l", "--license",
309 dest="license", default=False, action="store_true",
310 help="Print license text and exit")
311 parser.add_option("-v", "--verbose",
312 dest="verbose", default=False, action="store_true",
313 help="Print more information")
315 (options, check_filenames) = parser.parse_args();
316 # Print license
317 if options.license:
318 print (license, file=sys.stderr);
319 exit(0);
320 # Print manual
321 if options.manual:
322 print (manual, file=sys.stderr);
323 exit(0);
324 # Print manual
325 if options.releasenotes:
326 print ("Version: "+version, file=sys.stderr);
327 print (releasenotes, file=sys.stderr);
328 exit(0);
330 my_salt = options.salt;
331 my_passphrase = options.passphrase;
332 my_check = options.check;
333 my_status = options.status;
334 my_verbose = options.verbose;
335 execute = options.execute;
336 noexecute = options.noexecute;
337 input_file = options.input;
338 my_generate = options.generate;
339 if my_generate < 1: my_generate = 1;
341 # Set total-only with the correct default
342 total_only = True;
343 total_only = not options.detail;
344 if options.total: total_only = options.total;
345 if my_check: total_only = False;
347 my_user = options.user;
348 # Things might be executed as another user
349 user_change = '';
350 if os.getuid() == 0:
351 user_change = 'sudo -H -u '+my_user+' ';
352 print("User: "+my_user);
354 # Execute option
355 if execute:
356 text_execute = "True";
357 else:
358 text_execute = "False";
360 if execute: print("Execute system commands: "+text_execute+"\n");
362 # Read input-file
363 if input_file:
364 with open(input_file, 'r') as i:
365 for line in i:
366 # Clean up filename
367 current_filename = re.sub('[^\w\-\.\/\$\{\(\)\}]', '', line);
368 check_filenames.append(current_filename);
370 stat_list = [];
371 for x in check_filenames:
372 if os.path.isdir(x):
373 x = '?'+x;
374 if my_status and not x.startswith(('?', '$')):
375 stat_list.append('?'+x);
376 stat_list.append(x);
377 check_filenames = stat_list;
379 # Read the check file
380 passphrase_list = [];
381 check_hashes = {};
382 total_hash = "";
383 if my_check:
384 print("# Checking: "+my_check+"\n");
385 check_filenames = [];
386 with open(my_check, 'r') as c:
387 for line in c:
388 match = re.search("Execute system commands:\s+(True|False)", line);
389 if match != None:
390 # Uncomment the next line if you want automatic --execute from the check-file (DANGEROUS)
391 # execute = match.group(1).upper() == 'TRUE';
392 continue;
394 match = re.search("Salt\:\s+\'([\w]*)\'", line);
395 if match != None:
396 my_salt = match.group(1);
397 continue;
399 match = re.search("User\:\s+\'([\w]*)\'", line);
400 if match != None:
401 # Uncomment the next line if you want automatic --user from the check-file (DANGEROUS)
402 # my_user = match.group(1);
403 continue;
405 match = re.search("Passphrase\:\s+\'([^\']*)\'", line);
406 if match != None:
407 passphrase_list.append(match.group(1));
408 continue;
410 match = re.search("^\s*([a-f0-9]+)\s+\*(TOTAL HASH)\s*$", line)
411 if match != None:
412 total_hash = match.group(1);
413 continue;
415 match = re.search("^\s*([a-f0-9\-]+)\s+\*(.*)\s*$", line)
416 if match != None:
417 check_filenames.append(match.group(2));
418 check_hashes[match.group(2)] = match.group(1);
419 continue;
421 # Read a suggested salt from /dev/random if needed
422 if not my_salt:
423 dev_random = open("/dev/random", 'rb');
424 salt = dev_random.read(8);
425 dev_random.close;
426 sys.stderr.write("Enter salt (suggest \'"+str(binascii.hexlify(salt), 'ascii')+"\'): ");
427 my_salt = input();
428 elif my_salt == 'SUGGESTED':
429 dev_random = open("/dev/random", 'rb');
430 salt = dev_random.read(8);
431 dev_random.close;
432 my_salt = str(binascii.hexlify(salt), 'ascii');
434 print("Salt: \'"+my_salt+"\'");
436 # Get passphrase
437 if my_passphrase and os.path.isfile(my_passphrase):
438 with open(my_passphrase, 'r') as file:
439 for line in file:
440 match = re.search("Passphrase\:\s+\'([^\']*)\'", line);
441 if match != None:
442 passphrase_list.append(match.group(1));
443 elif not my_passphrase and len(passphrase_list) == 0:
444 dev_random = open("/dev/random", 'rb');
445 suggest_passphrase = dev_random.read(16);
446 dev_random.close;
447 sys.stderr.write("Enter passphrase (suggest \'"+str(base64.b64encode(suggest_passphrase), 'ascii').rstrip('=')+"\'): ");
448 # How kan we make this unreadable on input?
449 my_passphrase = input();
450 elif my_passphrase == 'SUGGESTED':
451 dev_random = open("/dev/random", 'rb');
452 (seed) = struct.unpack('q', dev_random.read(8));
453 print(seed)
454 dev_random.close;
455 random.seed(seed);
456 j = int(random.random()*my_generate);
457 print(j);
458 dev_random = open("/dev/urandom", 'rb');
459 for i in range(0, my_generate):
460 suggest_passphrase = dev_random.read(16);
461 current_passphrase = str(base64.b64encode(suggest_passphrase), 'ascii').rstrip('=');
462 print("Passphrase: \'"+current_passphrase+"\'", file=sys.stderr);
463 if j == i: my_passphrase = current_passphrase;
464 dev_random.close;
466 if my_passphrase:
467 passphrase_list.append(my_passphrase);
469 i = 1;
470 j = 0;
471 for my_passphrase in passphrase_list:
472 # Construct the passphrase hash
473 passphrase = hashlib.sha256();
475 passphrase.update(bytes(my_salt, encoding='ascii'));
476 passphrase.update(bytes(my_passphrase, encoding='ascii'));
477 my_passphrase = 0; # Do not let the cleartext passphrase lying around
479 # Create prefix which is a hash of the salt+passphrase
480 prefix = passphrase.hexdigest();
482 # Destroy passphrase against RAM attacks (is this really necessary?)
483 passphrase = 0;
485 # Create signature and write output
486 if noexecute: execute = False; # Doubly make sure that NOTHING is executed if required
487 totalhash = hashlib.sha256();
488 totalhash.update(bytes(prefix, encoding='ascii'));
489 for filename in check_filenames:
490 # Create file hash object
491 filehash = hashlib.sha256();
492 filehash.update(bytes(prefix, encoding='ascii'));
493 # Use system variables and commands
494 if filename.startswith('$'):
495 # Commands $(command)
496 match = re.search('^\$([\(\{]?)([^\)\}]+)[\)\}]?$', filename);
497 if match != None:
498 if match.group(1) == '(':
499 if not execute :
500 error_message = "Executable argument \'"+filename+"\' only allowed with the --execute flag";
501 print (error_message, file=sys.stderr);
502 if not sys.stdout.isatty(): print(error_message);
503 exit(1);
504 current_command = not_allowed_chars.sub(" ", match.group(2));
505 current_command_line = user_change+"bash --restricted -c \'"+current_command+"\' "+str(os.getpid());
506 if my_verbose:
507 print ("#", current_command_line);
508 (status, b) = subprocess.getstatusoutput(current_command_line);
509 if status != 0:
510 print ('$('+current_command+')'+"\n"+b, file=sys.stderr);
511 exit(status);
512 else:
513 current_var = not_allowed_chars.sub(" ", match.group(2));
514 if my_verbose:
515 print ("# echo $"+ current_var);
516 b = os.environ[current_var];
517 filehash.update(bytes(bytes(b, encoding='utf8')));
518 # lstat() meta information
519 elif filename.startswith('?'):
520 filestat = os.lstat(filename.lstrip('?'));
521 b = 'lstat('+filename.lstrip('?')+') = [st_mode='+str(oct(filestat.st_mode))+', st_ino='+str(filestat.st_ino)+', st_dev='+str(filestat.st_dev)+', st_nlink='+str(filestat.st_nlink)+', st_uid='+str(filestat.st_uid)+', st_gid='+str(filestat.st_gid)+', st_size='+str(filestat.st_size)+']';
522 filehash.update(bytes(b, encoding='utf8'));
523 if my_verbose:
524 print ("# "+ b);
525 # Use file
526 else:
527 # open and read the file
528 with open(filename, 'rb') as file:
529 for b in file:
530 filehash.update(b);
532 current_digest = filehash.hexdigest();
533 current_hash_line = current_digest+" *"+filename
534 totalhash.update(bytes(current_hash_line, encoding='ascii'));
536 # Be careful to use this ONLY after totalhash has been updated!
537 if total_only: current_hash_line = (len(current_digest)*'-')+" *"+filename;
539 # Write output
540 if not my_check:
541 print(current_hash_line);
542 elif check_hashes[filename] == (len(current_digest)*'-'):
543 print(check_hashes[filename]+" *"+filename);
544 elif current_digest != check_hashes[filename]:
545 print("DIFFERENT: "+current_hash_line);
546 else:
547 print("ok"+" *"+filename);
549 # Handle total hash
550 current_total_digest = totalhash.hexdigest();
551 current_total_digest_line = current_total_digest+" *"+"TOTAL HASH\n";
552 print("# \n# Total hash");
553 if not my_check:
554 print(current_total_digest_line);
555 elif current_total_digest != total_hash:
556 print("DIFFERENT: "+current_total_digest_line+"\n");
557 else:
558 number = "";
559 if len(passphrase_list) > 1: number = " # "+str(i);
560 print("OK"+" *"+"TOTAL HASH"+number+"\n");
561 j = i;
562 i += 1;
564 if len(passphrase_list) > 1:
565 if j > 0:
566 print("Entry:",j,"matched");
567 else:
568 print("No entry matched!");