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