You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hashcat/tools/test_modules/m25900.pm

145 lines
4.3 KiB

#!/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;