Helping you keep sensitive data accessible and protected.
Port knocking: a stealthy system for network authentication across closed ports
Port Knocking has not been seen on TV
port knocking > details > knocklength

Details of Port Knocking Mechanism

Perl prototype: v0.30

  • pcaplib support added; daemon no longer requires firewall log file

2004-Nov-14 18:59 | ...more

new Net::Pcap support added to sniff packets directly ...more

Once you've perused the firewall primer, learn about the details of port knocking here. Ideas about how to use port knocking in simple situations are presented, as well as an outline of how to use encryption to avoid eavesdropping.

Knock Length

For encrypted knocks with key "password" and Blowfish cipher.

no IV

knock fields (nf)
port span
length (np)
1-7 8-15 16-23 n
256 8 16 24 int(nf/8)+8
1,024 7 13 20 ceil[k*n(256)]
k=8/log2(np)
4,096 6 11 16
16,386 5 10 14
32,768 5 11 13

with IV

IV = 36574210

knock fields (nf)
port span
length (np)
1-7 8-15 16-23 n
256 24 32 40 16+int(nf/8)+8
1,024 20 26 32 ceil[k*n(256)]
k=8/log2(np)
4,096 16 22 27
16,386 14 19 23
32,768 13 18 22

knock length

The number of ports in the port knock will depend on

  • amount of information encoded in the knock
  • whether the knock is encrypted, and how
  • number of ports used to encode the knock

When other parameters are held constant, the increasing the amount of information increases the knock length. Encrypting the knock also increases the knock length, particularly when an initialization vector (IV) is used. The only way to shorten the knock is to increase the number of ports used to encode the knock.

The fundamentals of constructing the knock are described in the application section. Here, I illustrate how increasing the number of encoding ports affects the knock.

port span

Let's use the following knock format

knock = CLIENT,PORT,FLAG0,CHECKSUM

where CLIENT=10.3.2.1, PORT=3306, FLAG0=5. The CHECKSUM, which is automatically generated, has a value of 11 for these data. The CLIENT is an IP address which is stored as 4 integers in the range 0-255 (char). The PORT is an integer in the range 0-65,535 and is stored as 2 chars. Both FLAG0 and CHECKSUM are stored as chars. Thus, the knock data is made up of 8 chars.

knock = 10 3 2 1 12 234 5 11

Once encrypted (see the application section for details), the knock grows to 16 chars,

knock = 108 185 168  63   196  17  74  93 
        117 149  61 122     9 212 227 212

Since the values are already chars, encoding them into a range of 256 ports (port span) is simple. If the range is given by { p(i), i=0..255 }, then the encoded knock is

knock = p(108) p(185) p(168) ... p(212)

where p(i) is some function that computes the ith port in the port span. The encoded knock will contain the same number of elements, 16, as the original knock before encoding. The encoding in this case is nothing more than a simple mapping.

However, if the number of ports in the port span is not 256, then a little more work has to be done to perform the encoding. The reward is, however, a shorter knock if the port span has more than 256 ports. For the method outlined below, the port span must contain 2^N ports. Practically, the largest span size is 2^15 = 32,768, since you cannot allocate all your ports for port knocking if you are going to bind some of them to applications.

encoding knocks in a large span

Let's take a port span with 2^10=1,024 ports.

p(i) = 1000+i, i=0..1024

First, the data in the knock (108, 185, 168, ..., 212) is expressed in binary representation. Note that each value is zero-padded to 8 bits.

knock = 01101100 10111001 10101000 00111111 
        11000100 00010001 01001010 01011101  
        01110101 10010101 00111101 01111010
        00001001 11010100 11100011 11010100

Next, the elements of this representation are concatenated and split into N=10 bit binary numbers.

knock = 0110110010 1110011010 1000001111 1111000100 
        0001000101 0010100101 1101011101 0110010101 
        0011110101 1110100000 1001110101 0011100011 
        11010100

Each binary value is converted back to decimal. We now have 13 integers, instead of 16.

knock = 434 922 527 964   69 165 861 405 
        245 928 629 227   212

Finally, these values are mapped onto the port span

knock =  434 1922 1527 1964   1069 1165 1861 1405 
        1245 1928 1629 1227   1212

The effect of allocating 1,024 ports as knock listeners was a 20% savings (3/16) in the length of the knock. The largest savings are realized when a 32,768 port span is used. Here is an example of a non-contiguous 2^15=32,768 span

portspan = 1024-5000,6000-25000,26000-35789

The encoding into this larger span is done in the same manner, except that the concatenated binary representation is split into 15 bit elements.

knock = 011011001011100 110101000001111 111100010000010 001010010100101 
        110101110101100 101010011110101 111010000010011 101010011100011 
        11010100

The split yields 9 values, which when converted to decimal give

knock = 13916 27151 30850 5285   
        27564 21749 29715 21731 
          212

When these values are mapped onto the 32,768 port span, the port knock sequence is

knock = 15939 30173 33872  7308
        30586 23772 32737 23754
         1236

The benefit of using a large port span is clear - a shorter port knock is generated. The tables in the side panel show the length of an encrypted port knock for a given number of data fields in the knock, length of span and initialization vector use.

decoding from a large port span

Decoding a knock encoded in a large port span is a process which is nearly the reverse of the encoding procedure described above. There is one small difference, however. It has to do with how the padding of the last binary element is handled.

Consider the process of decoding these knock values, from a knock encoded into a 2^14=16,386 port span. Using the knock data from the examples above, the encoded knock is

knock = 6958 6787 16144  4426 
        5981 6483 13800  2516
        14581 0

Representing these 10 values as 14-bit binaries,

knock = 01101100101110 01101010000011 11111100010000 01000101001010 
        01011101011101 01100101010011 11010111101000 00100111010100
        11100011110101 0

We are going to concatenate these numbers and split them into 8-bit binary numbers. This step requires that the total number of digits is a multiple of 8. However, we have 9*14+1 = 127 numbers. To make a multiple of 8, we need to zero-pad the last value to a length of 2.

knock = 01101100101110 01101010000011 11111100010000 01000101001010 
        01011101011101 01100101010011 11010111101000 00100111010100
        11100011110101 00

Now this can be split into 8-bit values

knock = 01101100 10111001 10101000 00111111
        11000100 00010001 01001010 01011101
        01110101 10010101 00111101 01111010
        00001001 11010100 11100011 11010100

And converted to binary, to get the original encrypted knock represented by chars.

knock = 108 185 168  63   196  17  74  93 
        117 149  61 122     9 212 227 212

decoding in perl

The code in the knockdaemon to decode the incoming port knock is given below. The ports on which the client knocks are stored in @data and the $portmap HASHREF is used to map between the port and its ordinality in the port span.

  # cardinality of the sequence element in the port span
  my @d1 = map { $portmap->{$_} } @data;

  # binary representation
  my @d2 = map { dec2bin($_) } @d1;

  # compute length of padded binary representation, with last element unpadded
  my $padlength = $CONF{lognumports} * (@d2-1) + length($d2[-1]);
  my $padlength_fill = $padlength % 8;

  # pad binary represention of first N-1 elements in sequence to $CONF{lognumports} digits
  my @d3 = map { sprintf("%0$CONF{lognumports}s",$_) } @d2[0..@d2-2];
  if($padlength_fill) {
    # pad last element to padlength_fill
    my $padlength_last = 8 - $padlength_fill + length($d2[-1]);
    push(@d3,sprintf("%0${padlength_last}s",$d2[-1]));
  } else {
    push(@d3,$d2[-1]);
  }
  my $binlength = length(join("",@d3));

  # split binary representation into 8-bit fields
  my @d4 = join("",@d3) =~ /(.{1,8})/g;

  # convert to decimal
  my @d5 = map { bin2dec($_) } @d4;
  return @d5;
last updated 2010-Oct-17 10:16
Port Knocking (c) 2002-2017 Martin Krzywinski