Merge branch 'master' into SolarWinds2

This commit is contained in:
Jens Steube
2021-05-05 10:12:16 +02:00
committed by GitHub
508 changed files with 31129 additions and 3699 deletions
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Digest::MD5 qw (md5_hex);
sub module_constraints { [[0, 256], [2, 2], [0, 55], [2, 2], [2, 55]] }
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $digest = md5_hex ($salt . $word);
my $hash = sprintf ("%s:%s", $digest, $salt);
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $salt, $word) = split (':', $line);
return unless defined $hash;
return unless defined $salt;
return unless defined $word;
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $salt);
return ($new_hash, $word);
}
1;
+4 -2
View File
@@ -9,7 +9,7 @@ use strict;
use warnings;
use Digest::MD4 qw (md4_hex);
use Encode;
use Text::Iconv;
sub module_constraints { [[0, 256], [-1, -1], [0, 27], [-1, -1], [-1, -1]] }
@@ -17,7 +17,9 @@ sub module_generate_hash
{
my $word = shift;
my $digest = md4_hex (encode ('UTF-16LE', $word));
my $converter = Text::Iconv->new('utf8', 'UTF-16LE');
my $digest = md4_hex ($converter->convert ($word));
return $digest;
}
+1 -1
View File
@@ -10,7 +10,7 @@ use warnings;
use Authen::Passphrase::LANManager;
sub module_constraints { [[0, 7], [-1, -1], [-1, -1], [-1, -1], [-1, -1]] }
sub module_constraints { [[1, 7], [-1, -1], [-1, -1], [-1, -1], [-1, -1]] }
sub module_generate_hash
{
+2 -2
View File
@@ -17,8 +17,8 @@ sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $N = shift // 1024;
my $r = shift // 1;
my $N = shift // 16384;
my $r = shift // 8;
my $p = shift // 1;
my $hash_buf = scrypt_hash ($word, $salt, $N, $r, $p, 32);
+1 -1
View File
@@ -10,7 +10,7 @@ use warnings;
use Digest::SHA qw (sha256_hex);
sub module_constraints { [[0, 256], [0, 256], [0, 55], [16, 16], [-1, -1]] }
sub module_constraints { [[0, 256], [0, 256], [0, 55], [0, 51], [-1, -1]] }
sub module_generate_hash
{
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use MIME::Base64 qw (decode_base64 encode_base64);
use Digest::MD5 qw (md5_hex);
use Digest::SHA1 qw (sha1);
use Digest::HMAC qw (hmac);
use Crypt::PBKDF2;
sub module_constraints { [[0, 256], [16, 16], [-1, -1], [-1, -1], [-1, -1]] }
my $ITERATIONS = 10000;
my $MD5_SALT = ":mongo:";
my $HMAC_SALT = "Server Key";
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $iter = shift // $ITERATIONS;
my $user = shift // random_string (random_number (0, 57));
my $pbkdf = Crypt::PBKDF2->new
(
hash_class => 'HMACSHA1',
iterations => $iter,
output_len => 20
);
my $md5_dgst = md5_hex ($user . $MD5_SALT . $word);
my $pbkdf2_dgst = $pbkdf->PBKDF2 ($salt, $md5_dgst);
my $hash_buf = hmac ($HMAC_SALT, $pbkdf2_dgst, \&sha1);
my $hash = sprintf ('$mongodb-scram$*0*%s*%i*%s*%s', encode_base64 ($user, ""), $iter, encode_base64 ($salt, ""), encode_base64 ($hash_buf, ""));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my $idx = index ($line, ':');
return unless $idx >= 0;
my $hash = substr ($line, 0, $idx);
my $word = substr ($line, $idx + 1);
return unless substr ($hash, 0, 17) eq '$mongodb-scram$*0';
my (undef, undef, $user, $iter, $salt) = split ('\*', $hash);
return unless defined ($user);
return unless defined ($iter);
return unless defined ($salt);
return unless ($user =~ m/^[A-Za-z0-9+\/=]{0,76}$/);
$user = decode_base64 ($user);
return unless (length ($user) <= 57);
return unless ($iter =~ m/^[1-9][0-9]{0,7}$/);
$iter = int ($iter);
return unless ($salt =~ m/^[A-Za-z0-9+\/=]{24}$/);
$salt = decode_base64 ($salt);
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $salt, $iter, $user);
return ($new_hash, $word);
}
1;
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use MIME::Base64 qw (decode_base64 encode_base64);
use Digest::SHA qw (sha256);
use Digest::HMAC qw (hmac);
use Crypt::PBKDF2;
sub module_constraints { [[0, 256], [28, 28], [-1, -1], [-1, -1], [-1, -1]] }
my $ITERATIONS = 15000;
my $HMAC_SALT = "Server Key";
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $iter = shift // $ITERATIONS;
my $user = shift // random_string (random_number (0, 64));
my $pbkdf = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
iterations => $iter,
output_len => 32
);
my $pbkdf2_dgst = $pbkdf->PBKDF2 ($salt, $word);
my $hash_buf = hmac ($HMAC_SALT, $pbkdf2_dgst, \&sha256);
my $hash = sprintf ('$mongodb-scram$*1*%s*%i*%s*%s', encode_base64 ($user, ""), $iter, encode_base64 ($salt, ""), encode_base64 ($hash_buf, ""));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my $idx = index ($line, ':');
return unless $idx >= 0;
my $hash = substr ($line, 0, $idx);
my $word = substr ($line, $idx + 1);
return unless substr ($hash, 0, 17) eq '$mongodb-scram$*1';
my (undef, undef, $user, $iter, $salt) = split ('\*', $hash);
return unless defined ($user);
return unless defined ($iter);
return unless defined ($salt);
return unless ($user =~ m/^[A-Za-z0-9+\/=]{0,88}$/);
$user = decode_base64 ($user);
return unless (length ($user) <= 64);
return unless ($iter =~ m/^[1-9][0-9]{0,7}$/);
$iter = int ($iter);
return unless ($salt =~ m/^[A-Za-z0-9+\/=]{40}$/);
$salt = decode_base64 ($salt);
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $salt, $iter, $user);
return ($new_hash, $word);
}
1;
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Digest::SHA qw (sha1_hex);
sub module_constraints { [[0, 256], [0, 256], [0, 55], [0, 31], [0, 55]] }
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $digest = sha1_hex ($salt . sha1_hex ($word . $salt));
my $hash = sprintf ("%s:%s", $digest, $salt);
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $salt, $word) = split (':', $line);
return unless defined $hash;
return unless defined $salt;
return unless defined $word;
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $salt);
return ($new_hash, $word);
}
1;
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Crypt::PBKDF2;
use Digest::SHA qw (sha1 sha512);
use Crypt::Mode::ECB;
sub module_constraints { [[0, 256], [32, 32], [-1, -1], [-1, -1], [-1, -1]] }
my $AES256_IGE_BLOCK_SIZE = 16;
#
# Helper functions:
#
sub exclusive_or
{
my $in1 = shift;
my $in2 = shift;
# MIN () function (should always be 16 for us):
# my $len = (length ($in1) <= length ($in2)) ? length ($in2) : length ($in1);
# padding if input not multiple of block size:
# $in1 .= "\x00" x ($AES256_IGE_BLOCK_SIZE - $len);
# $in2 .= "\x00" x ($AES256_IGE_BLOCK_SIZE - $len);
my $out = "";
for (my $i = 0; $i < $AES256_IGE_BLOCK_SIZE; $i++) # $i < $len
{
$out .= chr (ord (substr ($in1, $i, 1)) ^ ord (substr ($in2, $i, 1)));
}
return $out;
}
sub aes256_encrypt_ige
{
my $key = shift;
my $iv = shift;
my $in = shift;
my $x_prev = substr ($iv, $AES256_IGE_BLOCK_SIZE, $AES256_IGE_BLOCK_SIZE);
my $y_prev = substr ($iv, 0, $AES256_IGE_BLOCK_SIZE);
my $m = Crypt::Mode::ECB->new ('AES', 0);
my $out = "";
for (my $i = 0; $i < length ($in); $i += $AES256_IGE_BLOCK_SIZE)
{
my $x = substr ($in, $i, $AES256_IGE_BLOCK_SIZE);
my $y_xor = exclusive_or ($x, $y_prev);
my $y_final = $m->encrypt ($y_xor, $key);
# $y_final .= "\x00" x ($AES256_IGE_BLOCK_SIZE - length ($y_final));
my $y = exclusive_or ($y_final, $x_prev);
$x_prev = $x;
$y_prev = $y;
$out .= $y;
}
return $out;
}
sub aes256_decrypt_ige
{
my $key = shift;
my $iv = shift;
my $in = shift;
my $x_prev = substr ($iv, 0, $AES256_IGE_BLOCK_SIZE);
my $y_prev = substr ($iv, $AES256_IGE_BLOCK_SIZE, $AES256_IGE_BLOCK_SIZE);
my $m = Crypt::Mode::ECB->new ('AES', 0);
my $out = "";
for (my $i = 0; $i < length ($in); $i += $AES256_IGE_BLOCK_SIZE)
{
my $x = substr ($in, $i, $AES256_IGE_BLOCK_SIZE);
my $y_xor = exclusive_or ($x, $y_prev);
my $y_final = $m->decrypt ($y_xor, $key);
# $y_final .= "\x00" x ($AES256_IGE_BLOCK_SIZE - length ($y_final));
my $y = exclusive_or ($y_final, $x_prev);
$x_prev = $x;
$y_prev = $y;
$out .= $y;
}
return $out;
}
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $iter = shift // 100000;
my $data = shift;
my $sha512_hash = sha512 ($salt . $word . $salt);
my $pbkdf = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512),
iterations => $iter,
output_len => 136
);
my $authkey = $pbkdf->PBKDF2 ($salt, $sha512_hash);
my $message = "";
my $message_key = "";
if (defined ($data))
{
$message = substr ($data, 16);
$message_key = substr ($data, 0, 16);
}
else
{
$message = random_bytes (272);
$message_key = substr (sha1 ($message), 0, 16);
}
my $data_a = "\x00" x 48;
my $data_b = "\x00" x 48;
my $data_c = "\x00" x 48;
my $data_d = "\x00" x 48;
substr ($data_a, 0, 16) = $message_key; # memcpy ()
substr ($data_b, 16, 16) = $message_key;
substr ($data_c, 32, 16) = $message_key;
substr ($data_d, 0, 16) = $message_key;
substr ($data_a, 16, 32) = substr ($authkey, 8, 32);
substr ($data_b, 0, 16) = substr ($authkey, 40, 16);
substr ($data_b, 32, 16) = substr ($authkey, 56, 16);
substr ($data_c, 0, 32) = substr ($authkey, 72, 32);
substr ($data_d, 16, 32) = substr ($authkey, 104, 32);
my $sha1_a = sha1 ($data_a);
my $sha1_b = sha1 ($data_b);
my $sha1_c = sha1 ($data_c);
my $sha1_d = sha1 ($data_d);
my $aes_key = substr ($sha1_a, 0, 8) . # 8 +
substr ($sha1_b, 8, 12) . # 12 +
substr ($sha1_c, 4, 12); # 12 = 32
my $aes_iv = substr ($sha1_a, 8, 12) . # 12 +
substr ($sha1_b, 0, 8) . # 8 +
substr ($sha1_c, 16, 4) . # 4 +
substr ($sha1_d, 0, 8); # 8 = 32
my $enc_data = "";
if (defined ($data))
{
# AES256 IGE decrypt:
my $dec_data = aes256_decrypt_ige ($aes_key, $aes_iv, $message);
my $h = substr (sha1 ($dec_data), 0, 16);
if ($h eq $message_key)
{
$enc_data = $data;
}
}
else
{
# AES256 IGE encrypt:
my $enc_random_data = aes256_encrypt_ige ($aes_key, $aes_iv, $message);
$enc_data = $message_key . $enc_random_data;
}
my $hash = sprintf ("\$telegram\$2*%i*%s*%s", $iter, unpack ("H*", $salt), unpack ("H*", $enc_data));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my $idx = index ($line, ':');
return if ($idx == -1);
my $hash = substr ($line, 0, $idx);
my $word = substr ($line, $idx + 1);
return unless defined $hash;
return unless defined $word;
my $signature = substr ($hash, 0, 10);
return unless ($signature eq "\$telegram\$");
my $version = substr ($hash, 10, 1);
return unless ($version eq "2");
my @split = split ('\*', $hash);
return unless (scalar (@split) == 4);
shift (@split);
my $iter = shift (@split);
my $salt = shift (@split);
my $data = shift (@split);
return unless length ($salt) == 64;
return unless length ($data) == 576;
return unless ($iter =~ /^[1-9][0-9]*$/);
return unless ($salt =~ /^[0-9a-fA-F]*$/);
return unless ($data =~ /^[0-9a-fA-F]*$/);
$salt = pack ("H*", $salt);
$data = pack ("H*", $data);
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $salt, $iter, $data);
return ($new_hash, $word);
}
1;
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Crypt::PBKDF2;
use Crypt::CBC;
sub module_constraints { [[0, 256], [32, 32], [-1, -1], [-1, -1], [-1, -1]] }
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $type = shift // 1; #random_number (1, 3);
my $iter = shift // random_number (10000, 20000);
my $iv = shift // random_hex_string (32);
my $enc = shift;
my $kdf;
if ($type == 1)
{
$kdf = Crypt::PBKDF2->new
(
hash_class => 'HMACSHA1',
iterations => $iter,
output_len => 32
);
}
elsif ($type == 2)
{
$kdf = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
iterations => $iter,
output_len => 32
);
}
elsif ($type == 3)
{
$kdf = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512),
iterations => $iter,
output_len => 32
);
}
my $salt_bin = pack ("H*", $salt);
my $key = $kdf->PBKDF2 ($salt_bin, $word);
my $iv_bin = pack ("H*", $iv);
my $data;
if (defined $enc)
{
my $aes_cbc = Crypt::CBC->new ({
cipher => "Crypt::Rijndael",
iv => $iv_bin,
key => $key,
keysize => 32,
literal_key => 1,
header => "none",
padding => "none"
});
my $enc_bin = pack ("H*", $enc);
$data = $aes_cbc->decrypt ($enc_bin);
if (substr ($data, 0, 12) ne "\x00" x 12)
{
$data = "\xff" x 16;
}
}
else
{
$data = "\x00" x 16;
}
my $aes_cbc = Crypt::CBC->new ({
cipher => "Crypt::Rijndael",
iv => $iv_bin,
key => $key,
keysize => 32,
literal_key => 1,
header => "none",
padding => "none"
});
my $enc_bin = $aes_cbc->encrypt ($data);
my $hash = sprintf ("SQLCIPHER*%d*%d*%s*%s*%s", $type, $iter, unpack ("H*", $salt_bin), unpack ("H*", $iv_bin), unpack ("H*", $enc_bin));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my $idx = index ($line, ':');
return unless $idx >= 0;
my $hash = substr ($line, 0, $idx);
my $word = substr ($line, $idx + 1);
return unless substr ($hash, 0, 9) eq 'SQLCIPHER';
my ($signature, $type, $iter, $salt, $iv, $data) = split '\*', $hash;
return unless defined $signature;
return unless defined $type;
return unless defined $iter;
return unless defined $salt;
return unless defined $iv;
return unless defined $data;
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $salt, $type, $iter, $iv, $data);
return ($new_hash, $word);
}
1;
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Digest::MD5 qw (md5_hex);
use Digest::MD5 qw (md5);
sub module_constraints { [[0, 256], [-1, -1], [0, 55], [-1, -1], [-1, -1]] }
sub module_generate_hash
{
my $word = shift;
my $digest1 = md5 ($word);
my $digest1_sub = substr ($digest1, 0, 5);
my $digest2 = md5 ($digest1_sub);
my $digest2_sub = substr ($digest2, 0, 5);
my $hash = sprintf ("%s", unpack ("H*", $digest2_sub));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $word) = split (':', $line);
return unless defined $hash;
return unless defined $word;
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed);
return ($new_hash, $word);
}
1;
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Digest::SHA1 qw (sha1);
use Digest::HMAC qw (hmac hmac_hex);
use Encode qw (encode decode);
use MIME::Base64;
sub module_constraints { [[0, 256], [0, 256], [0, 27], [0, 27], [0, 27]] }
sub module_generate_hash
{
my $word = shift;
my $unicode_word = encode ("UTF-16LE", $word);
my $digest = hmac ($unicode_word, $unicode_word, \&sha1, 64);
my $hash = sprintf ("%s", encode_base64 ($digest, ""));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $word) = split (':', $line);
return unless defined $hash;
return unless defined $word;
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed);
return ($new_hash, $word);
}
1;
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use MIME::Base64 qw (encode_base64 decode_base64);
use Digest::MD5 qw (md5);
sub module_constraints { [[-1, -1], [-1, -1], [0, 55], [-1, -1], [-1, -1]] }
my $itoa62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
sub module_generate_hash
{
my $word = shift;
my $digest = md5 ($word);
my @chksum;
for (my $i = 0, my $j = 0; $i < 16; $i += 2, $j += 1)
{
$chksum[$j] = (ord (substr ($digest, $i + 0, 1)) + ord (substr ($digest, $i + 1, 1))) % 62;
$chksum[$j] = substr ($itoa62, $chksum[$j], 1);
}
my $res = join "", @chksum;
my $hash = sprintf ("%s", $res);
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $word) = split (':', $line);
return unless defined $hash;
return unless defined $word;
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed);
return ($new_hash, $word);
}
1;
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use MIME::Base64 qw (encode_base64 decode_base64);
use Digest::SHA qw (sha512);
use Encode;
sub module_constraints { [[0, 64], [16, 16], [-1, -1], [-1, -1], [-1, -1]] }
sub module_generate_hash
{
my $word = shift;
my $salt = shift;
my $iter = shift // 100000;
my $tmp = sha512 ($salt . encode ("UTF-16LE", $word));
for (my $i = 0; $i < $iter; $i++)
{
my $num32 = pack ("L", $i);
$tmp = sha512 ($tmp . $num32);
}
my $salt_b64 = encode_base64 ($salt, "");
my $digest_b64 = encode_base64 ($tmp, "");
my $hash = sprintf ("\$office\$%d\$0\$%d\$%s\$%s", 2016, $iter, $salt_b64, $digest_b64);
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $word) = split ":", $line;
return unless defined $hash;
return unless defined $word;
my @data = split ('\$', $hash);
return unless scalar @data == 7;
return unless (shift @data eq 'office');
return unless (shift @data eq '2016');
return unless (shift @data eq '0');
my $iter = shift @data;
my $salt = shift @data;
my $digest = shift @data;
return unless defined $iter;
return unless defined $salt;
return unless defined $digest;
return unless length ($salt) == 24;
return unless length ($digest) == 88;
my $new_hash = module_generate_hash ($word, $salt, $iter);
return ($new_hash, $word);
}
1;
+277
View File
@@ -0,0 +1,277 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
# based off m10500 but added the owner password part ($o) to be able to test the edit password
# two TODOs still (now only works if no user password is set):
# 1. TODO use user password as input for md5 of o_digest if no owner password is set
# 2. TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value
# easy test shortcut for debugging
# a=$(echo 1 | tools/test.pl passthrough 10500 | tail -n1); echo $a; echo 1 | ./hashcat --potfile-disable --runtime 400 --hwmon-disable -O -D 2 --backend-vector-width 4 -a 0 -m 10500 $a
use strict;
use warnings;
use Crypt::RC4;
use Digest::MD5 qw (md5);
my $PDF_PADDING =
[
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56,
0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
];
sub module_constraints { [[0, 15], [32, 32], [-1, -1], [-1, -1], [-1, -1]] }
sub pdf_compute_encryption_key_user
{
my $word = shift;
my $padding = shift;
my $id = shift;
my $u = shift;
my $o = shift;
my $P = shift;
my $V = shift;
my $R = shift;
my $enc = shift;
## start
my $data;
$data .= $word;
$data .= substr ($padding, 0, 32 - length $word);
$data .= pack ("H*", $o);
$data .= pack ("I", $P);
$data .= pack ("H*", $id);
if ($R >= 4)
{
if (!$enc)
{
$data .= pack ("I", -1);
}
}
my $res = md5 ($data);
if ($R >= 3)
{
for (my $i = 0; $i < 50; $i++)
{
$res = md5 ($res);
}
}
return $res;
}
sub pdf_compute_encryption_key_owner
{
my $word = shift;
my $padding = shift;
my $id = shift;
my $u = shift;
my $o = shift;
my $P = shift;
my $V = shift;
my $R = shift;
my $enc = shift;
# TODO use user password as input for md5 of o_digest if no owner password is set
my $data;
$data .= $word;
$data .= substr ($padding, 0, 32 - length $word);
my $o_digest = md5 ($data);
if ($R >= 3)
{
for (my $i = 0; $i < 50; $i++)
{
$o_digest = md5 ($o_digest);
}
}
#printf("\$o_digest = %s\n", unpack ("H*", $o_digest));
my $o_key;
if ($R == 2)
{
$o_key = substr($o_digest, 0, 8); # rc4 key is always 5 for revision 2, but for 3 or greather is dependent on the value of the encryption dictionaries length entry
}
else
{
$o_key = substr($o_digest, 0, 16); #length is always 128 bits or 16 bytes
}
#printf("\$o_key = %s\n", unpack ("H*", $o_key));
return $o_key;
}
sub module_generate_hash
{
my $word = shift;
my $id = shift;
my $u = shift;
my $o = shift;
my $P = shift;
my $V = shift;
my $R = shift;
my $enc = shift;
if (defined $u == 0)
{
$u = "0" x 64;
}
my $u_save = $u;
if (defined $o == 0)
{
$o = "0" x 64;
}
my $o_save = $u;
if (defined $R == 0)
{
$R = random_number (3, 4);
}
if (defined $V == 0)
{
$V = ($R == 3) ? 2 : 4;
}
if (defined $P == 0)
{
$P = ($R == 3) ? -4 : -1028;
}
if (defined $enc == 0)
{
$enc = ($R == 3) ? 1 : random_number (0, 1);
}
my $padding;
for (my $i = 0; $i < 32; $i++)
{
$padding .= pack ("C", $PDF_PADDING->[$i]);
}
################ USER PASSWORD #################
my $res = pdf_compute_encryption_key_user($word, $padding, $id, $u, $o, $P, $V, $R, $enc);
my $digest = md5 ($padding . pack ("H*", $id));
my $m = Crypt::RC4->new ($res);
$u = $m->RC4 ($digest);
my @ress = split "", $res;
#do xor of rc4 19 times
for (my $x = 1; $x <= 19; $x++)
{
my @xor;
for (my $i = 0; $i < 16; $i++)
{
$xor[$i] = chr (ord ($ress[$i]) ^ $x);
}
my $s = join ("", @xor);
my $m2 = Crypt::RC4->new ($s);
$u = $m2->RC4 ($u);
}
################ OWNER PASSWORD #################
my $o_key = pdf_compute_encryption_key_owner($word, $padding, $id, $u, $o, $P, $V, $R, $enc);
my $n = Crypt::RC4->new ($o_key);
$o = $n->RC4(substr ($padding, 0, 32 - length "")); # TODO dynamically add user password including padding to the RC4 input for the computation of the pdf o-value
#printf("padding_empty_str = %s\n", unpack ("H*", substr ($padding, 0, 32 - length "")));
my @ress2 = split "", $o_key;
if ($R >= 3)
{
#do xor of rc4 19 times
for (my $x = 1; $x <= 19; $x++)
{
my @xor;
for (my $i = 0; $i < 16; $i++)
{
$xor[$i] = chr (ord ($ress2[$i]) ^ $x);
}
my $s = join ("", @xor);
my $n2 = Crypt::RC4->new ($s);
$o = $n2->RC4 ($o);
}
}
#printf("\$u = %s\n", unpack ("H*", $u));
$u .= substr (pack ("H*", $u_save), 16, 16);
#printf("\$o = %s\n", unpack ("H*", $o));
#printf("\$u = %s\n", unpack ("H*", $u));
my $hash = sprintf ('$pdf$%d*%d*128*%d*%d*16*%s*32*%s*32*%s', $V, $R, $P, $enc, $id, unpack ("H*", $u), unpack ("H*", $o));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash_in, $word) = split ":", $line;
return unless defined $hash_in;
return unless defined $word;
my @data = split /\*/, $hash_in;
return unless scalar @data == 11;
my $V = shift @data; $V = substr ($V, 5, 1);
my $R = shift @data;
return unless (shift @data eq '128'); # length is always 128 here
my $P = shift @data;
my $enc = shift @data;
return unless (shift @data eq '16');
my $id = shift @data;
return unless (shift @data eq '32');
my $u = shift @data;
return unless (shift @data eq '32');
my $o = shift @data;
return unless defined $id;
return unless defined $word;
$word = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word, $id, $u, $o, $P, $V, $R, $enc);
return ($new_hash, $word);
}
1;
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env perl
##
## Author......: Robert Guetzkow
## License.....: MIT
##
use strict;
use warnings;
use Crypt::PBKDF2;
use Crypt::Mode::CBC;
use Crypt::Mode::ECB;
# Details of the protocol design can be found in ISO 22510:2019 and
# application notes published by the KNX Association.
# ETS 5 allows a maximum of 20 characters in a password.
# The salt is used as Secure Session Identifier, which is 2 Bytes long.
sub module_constraints { [[0, 20], [2, 2], [-1, -1], [-1, -1], [-1, -1]] }
sub device_authentication_code
{
my $password = shift;
my $pbkdf2 = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ("HMACSHA2", 256),
iterations => 65536,
output_len => 16
);
my $device_authentication_code = $pbkdf2->PBKDF2 ("device-authentication-code.1.secure.ip.knx.org",
$password);
return $device_authentication_code;
}
sub block_formatting
{
# Simplified block formatting function, where payload is always empty
my $b0 = shift;
my $associated_data = shift;
my $associated_data_length = pack ("s>", length ($associated_data));
my $blocks_unpadded = $associated_data_length . $associated_data;
my $pad_len = int ((length ($blocks_unpadded) + 16 - 1) / 16) * 16;
my $blocks_padded = $blocks_unpadded . "\0" x ($pad_len - length ($blocks_unpadded));
return $b0 . $blocks_padded;
}
sub encrypt
{
# Simplified encryption that only performs steps required for the MAC, not full CCM
my $blocks = shift;
my $nonce = shift;
my $key = shift;
my $iv = "\0" x 16;
my $aes_cbc = Crypt::Mode::CBC->new ("AES", 0);
my $ciphertext = $aes_cbc->encrypt ($blocks, $key, $iv);
my $y_n = substr ($ciphertext, length ($ciphertext) - 16, 16);
my $aes_ecb = Crypt::Mode::ECB->new ("AES", 0);
my $s_0 = $aes_ecb->encrypt ($nonce, $key);
return $y_n ^ $s_0;
}
sub generate_session_response_mac
{
my $secure_session_identifier = shift;
my $public_value_xor = shift;
my $key = shift;
# Constants used for the cryptography in Session_Response frames
my $knx_ip_header = pack ("H*", "061009520038");
my $b0 = pack ("H*", "00000000000000000000000000000000");
my $nonce = pack ("H*", "0000000000000000000000000000ff00");
my $associated_data = $knx_ip_header . $secure_session_identifier . $public_value_xor;
my $blocks = block_formatting ($b0, $associated_data);
return encrypt ($blocks, $nonce, $key);
}
sub module_generate_hash
{
my $word = shift;
# Parameters that would be found in the Session_Request and Session_Response frames
my $secure_session_identifier = shift;
my $public_value_xor = shift // random_bytes (32);
my $device_authentication_code = device_authentication_code ($word);
my $mac = generate_session_response_mac ($secure_session_identifier,
$public_value_xor,
$device_authentication_code);
my $hash = sprintf ("\$knx-ip-secure-device-authentication-code\$*%s*%s*%s",
unpack ("H*", $secure_session_identifier),
unpack ("H*", $public_value_xor),
unpack ("H*", $mac));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my ($hash, $word) = split (':', $line);
return unless defined $hash;
return unless defined $word;
my @data = split ('\*', $hash);
return unless scalar (@data) == 4;
my $signature = shift @data;
return unless ($signature eq "\$knx-ip-secure-device-authentication-code\$");
my $secure_session_identifier = pack ("H*", shift @data); # 2 Bytes expected (using the "salt" for this purpose)
my $public_value_xor = pack ("H*", shift @data); # 32 Bytes expected (xor of client's and server's public value)
my $mac = pack ("H*", shift @data); # 16 Bytes expected
return unless (length ($secure_session_identifier) == 2);
return unless (length ($public_value_xor) == 32);
return unless (length ($mac) == 16);
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed,
$secure_session_identifier,
$public_value_xor);
return ($new_hash, $word);
}
1;
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Crypt::DES;
use Crypt::DES_EDE3;
use Digest::SHA qw (sha1);
use Digest::HMAC qw (hmac);
sub module_constraints { [[0, 256], [40, 40], [-1, -1], [-1, -1], [-1, -1]] }
sub module_generate_hash
{
my $word = shift;
my $global_salt = shift;
my $entry_salt = shift // random_hex_string (40);
my $ct = shift;
my $global_salt_bin = pack ("H*", $global_salt);
my $entry_salt_bin = pack ("H*", $entry_salt);
my $hp = sha1 ($global_salt_bin . $word);
my $pes = substr ($entry_salt_bin . ("\x00" x 20), 0, 20);
my $chp = sha1 ($hp . $entry_salt_bin);
my $k1 = hmac ($pes . $entry_salt_bin, $chp, \&sha1, 64);
my $tk = hmac ($pes, $chp, \&sha1, 64);
my $k2 = hmac ($tk . $entry_salt_bin, $chp, \&sha1, 64);
my $k = $k1 . $k2;
my $key = substr ($k, 0, 24);
my $iv = substr ($k, 32, 8);
my $pt;
if (defined $ct)
{
my $ct_bin = pack ("H*", $ct);
my $ede3 = Crypt::DES_EDE3->new ($key);
my $ct1_bin = substr ($ct_bin, 0, 8);
my $ct2_bin = substr ($ct_bin, 8, 8);
my $pt1 = $ede3->decrypt ($ct1_bin);
$pt1 = exclusive_or ($pt1, $iv);
my $pt2 = $ede3->decrypt ($ct2_bin);
$pt2 = exclusive_or ($pt2, $ct1_bin);
$pt = $pt1 . $pt2;
if ($pt ne "password-check\x02\x02")
{
$pt = "\xff" x 16;
}
}
else
{
$pt = "password-check\x02\x02";
}
my $ede3 = Crypt::DES_EDE3->new ($key);
my $pt1 = substr ($pt, 0, 8);
my $pt2 = substr ($pt, 8, 8);
$pt1 = exclusive_or ($pt1, $iv);
my $ct1_bin = $ede3->encrypt ($pt1);
$pt2 = exclusive_or ($pt2, $ct1_bin);
my $ct2_bin = $ede3->encrypt ($pt2);
my $ct_bin = $ct1_bin . $ct2_bin;
my $hash = sprintf ('$mozilla$*3DES*%s*%s*%s', unpack ("H*", $global_salt_bin), unpack ("H*", $entry_salt_bin), unpack ("H*", $ct_bin));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my $idx = index ($line, ':');
return unless $idx >= 0;
my $hash = substr ($line, 0, $idx);
my $word = substr ($line, $idx + 1);
return unless substr ($hash, 0, 9) eq '$mozilla$';
my ($signature, $type, $global_salt, $entry_salt, $ct) = split '\*', $hash;
return unless defined $signature;
return unless defined $type;
return unless defined $global_salt;
return unless defined $entry_salt;
return unless defined $ct;
return unless $type eq '3DES';
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $global_salt, $entry_salt, $ct);
return ($new_hash, $word);
}
sub exclusive_or
{
my $in1 = shift;
my $in2 = shift;
my $out = "";
for (my $i = 0; $i < length ($in1); $i++) # $i < $len
{
$out .= chr (ord (substr ($in1, $i, 1)) ^ ord (substr ($in2, $i, 1)));
}
return $out;
}
1;
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env perl
##
## Author......: See docs/credits.txt
## License.....: MIT
##
use strict;
use warnings;
use Crypt::PBKDF2;
use Crypt::CBC;
use Digest::SHA qw (sha1);
sub module_constraints { [[0, 256], [40, 40], [-1, -1], [-1, -1], [-1, -1]] }
sub module_generate_hash
{
my $word = shift;
my $global_salt = shift;
my $entry_salt = shift // random_hex_string (64);
my $iter = shift // 10000;
my $iv = shift // random_hex_string (32);
my $ct = shift;
my $kdf = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
iterations => $iter,
output_len => 32
);
my $global_salt_bin = pack ("H*", $global_salt);
my $global_key = sha1 ($global_salt_bin . $word);
my $entry_salt_bin = pack ("H*", $entry_salt);
my $entry_key = $kdf->PBKDF2 ($entry_salt_bin, $global_key);
my $iv_bin = pack ("H*", $iv);
my $pt;
if (defined $ct)
{
my $aes_cbc = Crypt::CBC->new ({
cipher => "Crypt::Rijndael",
iv => $iv_bin,
key => $entry_key,
keysize => 32,
literal_key => 1,
header => "none",
padding => "none"
});
my $ct_bin = pack ("H*", $ct);
$pt = $aes_cbc->decrypt ($ct_bin);
if ($pt ne "password-check\x02\x02")
{
$pt = "\xff" x 16;
}
}
else
{
$pt = "password-check\x02\x02";
}
my $aes_cbc = Crypt::CBC->new ({
cipher => "Crypt::Rijndael",
iv => $iv_bin,
key => $entry_key,
keysize => 32,
literal_key => 1,
header => "none",
padding => "none"
});
my $ct_bin = $aes_cbc->encrypt ($pt);
my $hash = sprintf ('$mozilla$*AES*%s*%s*%d*%s*%s', unpack ("H*", $global_salt_bin), unpack ("H*", $entry_salt_bin), $iter, unpack ("H*", $iv_bin), unpack ("H*", $ct_bin));
return $hash;
}
sub module_verify_hash
{
my $line = shift;
my $idx = index ($line, ':');
return unless $idx >= 0;
my $hash = substr ($line, 0, $idx);
my $word = substr ($line, $idx + 1);
return unless substr ($hash, 0, 9) eq '$mozilla$';
my ($signature, $type, $global_salt, $entry_salt, $iter, $iv, $ct) = split '\*', $hash;
return unless defined $signature;
return unless defined $type;
return unless defined $global_salt;
return unless defined $entry_salt;
return unless defined $iter;
return unless defined $iv;
return unless defined $ct;
return unless $type eq 'AES';
my $word_packed = pack_if_HEX_notation ($word);
my $new_hash = module_generate_hash ($word_packed, $global_salt, $entry_salt, $iter, $iv, $ct);
return ($new_hash, $word);
}
1;