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 hexadecimal. If not given, a salt will
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
21 -g N, --generate-passphrases=N
22 Generate and print N passphrases, one of which will be
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
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
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
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.
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
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
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.
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
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.
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/>.
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
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
251 # if sys.stdout.isatty(): import readline;
255 from optparse
import OptionParser
;
260 # Limit the characters that can be used in $(cmd) commands
261 not_allowed_chars
= re
.compile('[^\w\ \.\/\"\|\;\,\-\$\[\]\{\}\(\)\@\`\!\*]');
263 programname
= "Signature-du-Terroir";
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();
318 print (license
, file=sys
.stderr
);
322 print (manual
, file=sys
.stderr
);
325 if options
.releasenotes
:
326 print ("Version: "+version
, file=sys
.stderr
);
327 print (releasenotes
, file=sys
.stderr
);
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
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
351 user_change
= 'sudo -H -u '+my_user
+' ';
352 print("User: "+my_user
);
356 text_execute
= "True";
358 text_execute
= "False";
360 if execute
: print("Execute system commands: "+text_execute
+"\n");
364 with
open(input_file
, 'r') as i
:
367 current_filename
= re
.sub('[^\w\-\.\/\$\{\(\)\}]', '', line
);
368 check_filenames
.append(current_filename
);
371 for x
in check_filenames
:
374 if my_status
and not x
.startswith(('?', '$')):
375 stat_list
.append('?'+x
);
377 check_filenames
= stat_list
;
379 # Read the check file
380 passphrase_list
= [];
384 print("# Checking: "+my_check
+"\n");
385 check_filenames
= [];
386 with
open(my_check
, 'r') as c
:
388 match
= re
.search("Execute system commands:\s+(True|False)", line
);
390 # Uncomment the next line if you want automatic --execute from the check-file (DANGEROUS)
391 # execute = match.group(1).upper() == 'TRUE';
394 match
= re
.search("Salt\:\s+\'([\w]*)\'", line
);
396 my_salt
= match
.group(1);
399 match
= re
.search("User\:\s+\'([\w]*)\'", line
);
401 # Uncomment the next line if you want automatic --user from the check-file (DANGEROUS)
402 # my_user = match.group(1);
405 match
= re
.search("Passphrase\:\s+\'([^\']*)\'", line
);
407 passphrase_list
.append(match
.group(1));
410 match
= re
.search("^\s*([a-f0-9]+)\s+\*(TOTAL HASH)\s*$", line
)
412 total_hash
= match
.group(1);
415 match
= re
.search("^\s*([a-f0-9\-]+)\s+\*(.*)\s*$", line
)
417 check_filenames
.append(match
.group(2));
418 check_hashes
[match
.group(2)] = match
.group(1);
421 # Read a suggested salt from /dev/random if needed
423 dev_random
= open("/dev/random", 'rb');
424 salt
= dev_random
.read(8);
426 sys
.stderr
.write("Enter salt (suggest \'"+str(binascii
.hexlify(salt
), 'ascii')+"\'): ");
428 elif my_salt
== 'SUGGESTED':
429 dev_random
= open("/dev/random", 'rb');
430 salt
= dev_random
.read(8);
432 my_salt
= str(binascii
.hexlify(salt
), 'ascii');
434 print("Salt: \'"+my_salt
+"\'");
437 if my_passphrase
and os
.path
.isfile(my_passphrase
):
438 with
open(my_passphrase
, 'r') as file:
440 match
= re
.search("Passphrase\:\s+\'([^\']*)\'", line
);
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);
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));
456 j
= int(random
.random()*my_generate
);
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
;
467 passphrase_list
.append(my_passphrase
);
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?)
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
);
498 if match
.group(1) == '(':
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
);
504 current_command
= not_allowed_chars
.sub(" ", match
.group(2));
505 current_command_line
= user_change
+"bash --restricted -c \'"+current_command
+"\' "+str(os
.getpid());
507 print ("#", current_command_line
);
508 (status
, b
) = subprocess
.getstatusoutput(current_command_line
);
510 print ('$('+current_command
+')'+"\n"+b
, file=sys
.stderr
);
513 current_var
= not_allowed_chars
.sub(" ", match
.group(2));
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'));
527 # open and read the file
528 with
open(filename
, 'rb') as file:
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
;
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
);
547 print("ok"+" *"+filename
);
550 current_total_digest
= totalhash
.hexdigest();
551 current_total_digest_line
= current_total_digest
+" *"+"TOTAL HASH\n";
552 print("# \n# Total hash");
554 print(current_total_digest_line
);
555 elif current_total_digest
!= total_hash
:
556 print("DIFFERENT: "+current_total_digest_line
+"\n");
559 if len(passphrase_list
) > 1: number
= " # "+str(i
);
560 print("OK"+" *"+"TOTAL HASH"+number
+"\n");
564 if len(passphrase_list
) > 1:
566 print("Entry:",j
,"matched");
568 print("No entry matched!");