5 # Purpose: To measure cross-packet SPA entropy on a byte by byte slice basis
6 # and produce gunplot graphs. This is useful to measure SPA packet
7 # randomness after encryption and verify that it high as one would
10 # Author: Michael Rash <mbr@cipherdyne.org>
17 use Getopt::Long 'GetOptions';
21 my $base64_decode = 1;
23 my $prefix = 'entropy';
24 my $file_to_measure = '';
25 my $run_fwknop_client = 0;
27 my $lib_dir = '../../lib/.libs';
28 my $fwknop_client_path = '../../client/.libs/fwknop';
31 my $hmac_key_file = '../../test/conf/fwknoprc_default_hmac_base64_key';
32 my $enable_fwknop_client_gpg = 0;
33 my $gpg_recipient = '361BBAD4';
34 my $gpg_signer = '6A3FAD56';
35 my $gpg_home_dir = '../../test/conf/client-gpg';
36 my $spa_key_file = '../../test/local_spa.key';
40 my $openssl_salt = '0000000000000000';
41 my $openssl_mode = 'aes-256-cbc';
43 my %min_max_entropy = (
54 my @encrypted_data = ();
55 my @plaintext_data = ();
56 my @cross_pkt_data = ();
58 Getopt::Long::Configure('no_ignore_case');
59 die "[*] See '$0 -h' for usage information" unless (GetOptions(
60 'file-to-measure=s' => \$file_to_measure,
61 'base64-decode' => \$base64_decode,
62 'count=i' => \$packets,
63 'prefix=s' => \$prefix,
64 'run-fwknop-client' => \$run_fwknop_client,
65 'enc-mode=s' => \$enc_mode,
66 'gpg-mode' => \$enable_fwknop_client_gpg,
67 'gpg-recip=s' => \$gpg_recipient,
68 'gpg-signer=s' => \$gpg_signer,
69 'gpg-home=s' => \$gpg_home_dir,
70 'hmac-mode' => \$hmac_type,
71 'lib-dir=s' => \$lib_dir,
72 'Client-path=s' => \$fwknop_client_path,
73 'use-openssl' => \$use_openssl,
74 'openssl-salt=s' => \$openssl_salt,
75 'openssl-mode=s' => \$openssl_mode,
80 die "[*] Must execute --run-fwknop-client in --use-openssl mode"
81 if $use_openssl and not $run_fwknop_client;
83 &run_fwknop_client() if $run_fwknop_client;
91 open F, "> $prefix.dat" or die $!;
93 for my $str (@cross_pkt_data) {
95 my $entropy = &get_entropy($str);
97 # print F "$pos $entropy\n";
98 print F "$pos $entropy ### " . &hex_dump($str) . "\n";
100 if ($min_max_entropy{'min'}{'val'} == -1
101 and $min_max_entropy{'max'}{'val'} == -1) {
102 $min_max_entropy{'min'}{'val'} = $entropy;
103 $min_max_entropy{'min'}{'pos'} = $pos;
104 $min_max_entropy{'max'}{'val'} = $entropy;
105 $min_max_entropy{'max'}{'pos'} = $pos;
107 if ($entropy < $min_max_entropy{'min'}{'val'}) {
108 $min_max_entropy{'min'}{'val'} = $entropy;
109 $min_max_entropy{'min'}{'pos'} = $pos;
111 if ($entropy > $min_max_entropy{'max'}{'val'}) {
112 $min_max_entropy{'max'}{'val'} = $entropy;
113 $min_max_entropy{'max'}{'pos'} = $pos;
120 my $min = sprintf "%.2f", $min_max_entropy{'min'}{'val'};
121 my $max = sprintf "%.2f", $min_max_entropy{'max'}{'val'};
123 print "[+] Min entropy: $min at byte: $min_max_entropy{'min'}{'pos'}\n";
124 print "[+] Max entropy: $max at byte: $min_max_entropy{'max'}{'pos'}\n";
134 ### we've already gotten plaintext information from the fwknop client,
135 ### so encrypt this data with openssl and use it to re-write the
137 unlink $file_to_measure if -e $file_to_measure;
139 my @openssl_encrypted_data = ();
141 ### encrypt the plaintext and use it to re-write the -f file
142 for my $line (@plaintext_data) {
144 my $ptext_file = 'ptext.tmp';
145 my $enc_file = 'ptext.enc';
147 open F, "> $ptext_file" or die $!;
151 unlink $enc_file if -e $enc_file;
153 system "openssl enc -$openssl_mode -a -S $openssl_salt " .
154 "-in ptext.tmp -out ptext.enc -k fwknoptest000000";
156 my $base64_enc_data = '';
157 open F, "< $enc_file" or die $!;
160 $base64_enc_data .= $_;
164 push @openssl_encrypted_data, $base64_enc_data;
168 open F, "> $file_to_measure" or die $!;
169 for my $line (@openssl_encrypted_data) {
176 if ($file_to_measure) {
177 open IN, "< $file_to_measure" or die "[*] Could not open $file_to_measure: $!";
183 next unless $_ =~ /\S/;
186 if ($base64_decode) {
187 if (&is_base64($_)) {
190 if ($enable_fwknop_client_gpg) {
191 unless ($base64_str =~ /^hQ/) {
192 $base64_str = 'hQ' . $base64_str;
195 ### base64-encoded "Salted__" prefix
196 unless ($base64_str =~ /^U2FsdGVkX1/) {
197 $base64_str = 'U2FsdGVkX1' . $base64_str;
201 my ($equals_rv, $equals_padding) = &base64_equals_padding($base64_str);
202 if ($equals_padding) {
203 $base64_str .= $equals_padding;
205 my $str = decode_base64($base64_str);
207 if ($enable_fwknop_client_gpg) {
208 $str =~ s/^\x85\x02//;
210 $str =~ s/^Salted__//;
212 push @encrypted_data, $str;
214 push @encrypted_data, $_;
217 push @encrypted_data, $_;
222 last if $l_ctr == $packets;
226 ### hex dump encrypted data
227 open HEX, "> hex_dump.data" or die $!;
228 for my $line (@encrypted_data) {
229 print HEX &hex_dump($line), "\n";
233 print "[+] Read in $l_ctr SPA packets...\n";
237 sub run_fwknop_client() {
238 die "[*] Must set packets file with -f <file>" unless $file_to_measure;
239 die "[*] Must set packet count with -c <count>" unless $packets;
241 if (-e $file_to_measure) {
242 unlink $file_to_measure or die $!;
245 my $cmd = "LD_LIBRARY_PATH=$lib_dir $fwknop_client_path -A tcp/22 " .
246 "-a 127.0.0.2 -D 127.0.0.1 -B $file_to_measure -b -v --test";
249 $cmd .= " --rc-file $hmac_key_file";
251 $cmd .= " --get-key $spa_key_file";
254 if ($enable_fwknop_client_gpg) {
255 $cmd .= " --gpg-recipient-key $gpg_recipient " .
256 "--gpg-signer-key $gpg_signer " .
257 "--gpg-home-dir $gpg_home_dir";
259 $cmd .= " -M $enc_mode";
261 $cmd .= " 2> /dev/null";
263 print "[+] Running fwknop client via the following command:\n\n$cmd\n\n";
265 for (my $i=0; $i < $packets; $i++) {
266 open C, "$cmd |" or die $!;
268 if (/Plaintext\:\s+(\S+)/) {
269 push @plaintext_data, $1;
281 ### calculate minimum length
282 for my $line (@encrypted_data) {
284 next unless $line =~ /\S/;
285 my $len = length($line);
289 if ($len < $min_len) {
297 sub build_data_slices() {
298 for my $line (@encrypted_data) {
299 my @chars = split //, $line;
301 for my $char (@chars) {
302 $cross_pkt_data[$c_ctr] .= $char;
303 last if $c_ctr == $min_len;
311 open F, "> $prefix.gnu" or die $!;
313 my $enc_str = $enc_mode;
314 $enc_str = 'gpg' if $enable_fwknop_client_gpg;
316 my $yrange = '[0:9]';
318 set title "SPA slice entropy (encryption mode: $enc_str)"
319 set terminal gif nocrop enhanced
320 set output "$prefix.gif"
323 plot '$prefix.dat' using 1:2 with lines title 'min: $min \\@ byte: $min_max_entropy{'min'}{'pos'}, max: $max \\@ byte: $min_max_entropy{'max'}{'pos'}'
327 print "[+] Creating $prefix.gif gnuplot graph...\n\n";
328 system "gnuplot $prefix.gnu";
338 my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'ent');
344 ### Entropy = 5.637677 bits per byte.
345 if (/Entropy\s=\s(\d\S+)/) {
354 my $child_exit_status = $? >> 8;
359 sub base64_equals_padding() {
363 return 1, $padding if $msg =~ /=$/;
365 my $remainder = 4 - length($msg) % 4;
367 if ($remainder == 3) {
368 ### not possible for valid base64 data - should only have
369 ### pad with one or two '=' chars
373 unless ($remainder == 4) {
374 $padding .= '='x$remainder;
382 my @chars = split //, $data;
388 for my $char (@chars) {
390 $hex_part .= sprintf "%.2x", ord($char);
392 if ($char =~ /[^\x20-\x7e]/) {
395 $ascii_part .= $char;
399 return "$hex_part $ascii_part";
400 # return "$ascii_part";
406 ### check to make sure the packet data only contains base64 encoded
407 ### characters per RFC 3548: 0-9, A-Z, a-z, +, /, =
408 if ($data =~ /[^\x30-\x39\x41-\x5a\x61-\x7a\x2b\x2f\x3d]/) {
411 if ($data =~ /=[^=]/) {
418 print "$0 [options]\n";