From WikiChip
Editing mirc/identifiers/$hotp

Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.

The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.

This page supports semantic in-text annotations (e.g. "[[Is specified as::World Heritage Site]]") to build structured and queryable content provided by Semantic MediaWiki. For a comprehensive description on how to use annotations or the #ask parser function, please have a look at the getting started, in-text annotation, or inline queries help pages.

Latest revision Your text
Line 2: Line 2:
  
 
== Synopsis ==
 
== Synopsis ==
<pre>$hotp( [key] , count [, hash [, digits ]])</pre>
+
<pre>$hotp(key, count [, hash [, digits ]])</pre>
  
== Parameters ==
+
== Paramters ==
* '''key''' - The can be in a base16 format of either 40, 64 or 128 chars; or in a base32 format (not containing the '=' character) of any length that's a multiple of 8 greater than length 8; or plain text. The "Google Authenticator format" is implemented by allowing the base32 and base16 lengths to be upper/lower case-insensitive and optionally contain spaces, where the string length is evaluated before removing spaces, instead of the correct method where the string length is verified after spaces are removed. Also incorrect is allowing the spaces to be present when not following exactly 4 encoding characters. If the 40/64/128 lengths of base16 contain an odd number of non-space hex digits, the decoding method is undiscovered. For even-number count of hex digits, the hex is decoded to binary, with 0x00 bytes discarded. The remaining bytes are individually UTF-8 encoded. The correct method should have been to use these 3 lengths are the hex encoding of binary strings of length 20/32/64 bytes.
+
* '''key''' - the key is required and can be in a base16 format of either 40, 64 or 128 chars; or in a base32 format of either 16, 24 or 32 chars; or a base32 format of any multiple of 8 greater than length 8; or plain text; or it can also be in a Google Authenticator format ie lowercase with spaces.
* '''count''' - required. The 'counter' should never be used more than once with the same key, as it always returns the same number for the same number value. This is a 64-bit number with valid range 0 through 2^64-1. Non-numeric are removed from the end, and "+" removed from the beginning.
+
* '''count''' - required, the 'counter' that should never be used more than once with the same key. This is a 64-bit number with valid range 0 through 2^64-1. Non-numeric are removed from the end, and "+" removed from the beginning.
 
* '''hash''' - optional, hashing algorithm, default to sha1. Also allowed: sha256, sha512, sha384, md5
 
* '''hash''' - optional, hashing algorithm, default to sha1. Also allowed: sha256, sha512, sha384, md5
 
* '''digits''' - optional, number of digits, default to 6. Valid range 3 through 9.
 
* '''digits''' - optional, number of digits, default to 6. Valid range 3 through 9.
 
* Note: /help says the key is required, but $hotp(,0) is valid syntax and returns same as when the key is all 0x00's.
 
  
 
== Properties ==
 
== Properties ==
 
None
 
None
 +
 
== Details ==
 
== Details ==
 
The /help describes 4 formats for the key. Here is the sequence of parsing:.<br />
 
The /help describes 4 formats for the key. Here is the sequence of parsing:.<br />
  
Rule #1. If key is a length 40/64/128 case-insensitive string that's a mix of spaces and/or case-insensitive hex digits, spaces are removed then the remaining pairs of hex digits are translated into a byte value. If the pair of hex digits is value 00, it is not added to the key. Otherwise, the byte values 1-255 are individually UTF-8 encoded and added to the key string. When there are an odd number of space characters causing an odd number of hex digits, the key is created in an un-discovered way where shifting the location of the spaces in the original string doesn't alter the output.<br/>
+
1. If key is a length 40/64/128 case-insensitive hex string, it is decoded to become a text string where each byte value 128-255 is UTF-8 encoded, and any 0x00's stripped. If the string is entirely 0x00's, the key is $null.<br/>
Rule#2. Else: If key length is 16 or greater and a multiple of 8 (except for matching rule #1) containing only A-F 2-7 a-f or spaces, and if removing spaces results in a valid case-insensitive Base32 encoded string, the spaces are stripped from the string, and the key is the binary decoded contents whose length is approximately 5/8ths the length of the remaining Base32 string. The decoded binary bytes are used as the key, with no 0x00's stripped and no UTF-8 encoding as done in #1 for hex-encoded strings.<br />
+
2. Else: If key length is 16 or greater and a multiple of 8 (except for lengths 40/64/128 containing only 0-9a-f), and if it's a valid case-insensitive Base32 encoded string without spaces or '=' padding, the key is the binary decoded contents whose length is 5/8ths the length of the Base32 string, with no 0x00's stripped and no UTF-8 encoding as if the contents are text.<br />
Rule #3. Else: Any remaining strings not matching the 1st 2 patterns are considered literal case-sensitive text keys, and the input is assumed to already be UTF-8 encoded where necessary.<br />
+
3. Else: If the key length is 16/24/32 and contains only spaces or case-insensitive Base-32 characters, the spaces are stripped and the remaining Base-32 characters of arbitrary length are decoded into a binary key.<br />
 +
4. Else: Any remaining strings not matching the 1st 3 patterns are considered literal text keys, and the input is assumed to already be UTF-8 encoded where necessary.<br />
  
* Note: The actual Google Authenticator format should be: Case-insensitive base32 string of length 16/26/32 encoding a key of 80/128/160 bits, not the 16/24/32 lengths decribed in /help. Base32 string can optionally have spaces inserted into that 16/26/32 character string only if immediately preceded by exactly 4 base32 characters. The purpose of adding spaces is to improve accuracy when entering the key by hand into a 2nd device while viewing the encoded string on a 1st device. $hotp is not recognizing the 128-bit key because 26 isn't a multiple of 8, unless it's padded with 6 spaces so the key length is 32 and becomes a multiple of 8. $hotp is not supporting the base32 encoded string padded by spaces into being divided into groups of 4 because that space-padded string isn't a multiple of 8, so you must $remove(key,$chr(32)) before $hotp correctly recognizes the 16 and 32 character strings as 80-bit or 160-bit keys. Instead of supporting length 16/26/32 AFTER spaces are removed, $hotp is incorrectly using base32 strings of lengths 16/24/32 BEFORE spaces are removed, then extended that same behavior to strings whose length including spaces is any multiple of 8 greater than 8.
+
* Note: Google Authenticator format is: Base32 string of length 16/26/32 encoding a key of 80/128/160 bits. String can be upper or lower case chars, and can be divided by spaces into groups of 4 characters. $hotp is not recognizing the 128-bit key because 26 isn't a multiple of 8, unless it's padded with 6 non-consecutive spaces to be length 32. $hotp is not supporting the encoded string divided by spaces into groups unless there are enough spaces to cause the total length including spaces to be a multiple of 8.
  
* Note: Because $hotp uses decoded base16 strings after deleting 0x00 bytes and then UTF-8 encoding byte values 128-255 into 2 bytes, keys are replaced by UTF-8 encoded text strings an average of 50% longer, and hex strings with 0x00's in different positions produce matching keys. Because the $hmac format appends 0x00's to a key shorter than 512 bits, all strings which decode into the $null string or a string entirely consisting of 1-128 0x00's or have differing number of trailing 0x00's produce identical outputs.<br />
+
* Note: Because $hotp uses decoded base16 strings after deleting 0x00 bytes and UTF-8 encoding byte values 128-255, keys are replaced by UTF-8 encoded text strings an average of 50% longer, and hex strings with 0x00's in different positions produce matching keys.<br />
  
The '''count''' parameter should be in the range 0 to 2^64-1. All values greater than 2^64-1 return the same password as when 2^64-1 is used. For 64-bit negative numbers, negative N returns the same password as when 2^64 is added to the negative number. If using a count value greater than 2^53, $hotp correctly translates it to binary, but $calc() does not perform accurate integer math for all values above 2^53.<br />
+
The '''count''' parameter should be in the range 0 to 2^64-1. Values greater than 2^64-1 return the same password as when 2^64-1 is used. For 64-bit negative numbers, negative N returns the same password as when +2^64 less N is used.<br />
  
 
The OTP in HOTP stands for One Time Password, which is only 'one time' if the application makes sure that the same 'count' value is never used twice. Either the server and the client keep track of the last count used with each key, or each use the current time for a very short interval as defined for $totp().
 
The OTP in HOTP stands for One Time Password, which is only 'one time' if the application makes sure that the same 'count' value is never used twice. Either the server and the client keep track of the last count used with each key, or each use the current time for a very short interval as defined for $totp().
  
* This alias shows how $hotp calculates the output number when the key parameter is handled as a literal string and not handled as if it needs to be decoded out of base16 or base32 text. If the outputs are different, that '''should''' be because $hotp is handling them as if they're hex-encoded or base32-encoded keys. The HOTP password is calculated as follows:
+
== Examples ==
 
 
1. key should be any text string that doesn't match the above rules #1/#2<br />
 
2. count should be any integer from 0-2^53-1 supported by $calc ($hotp supports up to 2^64-1 but the alias below doesn't)<br />
 
3. hashname should be any of sha1/sha512/sha256/sha384/md5 - defaults to sha1<br />
 
4. digits should be any integer 3-9 - defaults to 6<br />
 
  
 
<source lang="mIRC">
 
<source lang="mIRC">
;syntax: //noop $fake_hotp(key,count,hashname,digits)
+
//var %a 1234567890 | echo -a $hotp(%a,1,sha512) $hotp($encode(%a,a),1,sha512)
alias fake_hotp {
 
  var %count $gettok($2,1,46) , %hash $3 , %digits $gettok($4,1,46)
 
  if (%digits !isnum 3-9) var %digits 6 | if (!$istok(md5 sha1 sha256 sha512 sha384,%hash,32)) var %hash sha1
 
  if ((%count !isnum 0-) || ($1 == $null) || !$isid) { echo -stc info *$fake_hotp(<key>,<count>[[,hashname],digits3-9]) | return }
 
  bset &count 1 $regsubex($base(%count,10,16,16),/(..)/g,$base(\t ,16,10) $chr(32))
 
  var %hmac $hmac(&count,$1,%hash,1) , %offset $calc(1+2*$base($right(%hmac,1),16,10)) , %truncate $mid(%hmac $+ 000000,%offset,8) , %divisor 1 $+ $str(0,%digits) , %num $and($base(%truncate,16,10),$calc(2^31-1))
 
  echo -a key: $1 keylen $len($1) count: %count hash: %hash digits: %digits hmac %hmac offset %offset truncate: %truncate num: %num calculated hotp: $right(000000000 $+ $calc(%num % %divisor ) , %digits) compare to $ $+ hotp: $hotp($1,%count,%hash,%digits)
 
}
 
 
 
* Note: Even though HOTP supports hash=md5, you should avoid using md5 because HOTP was not designed for hashes shorter than 160 bits, and a few values are returned a high percentage of the time. When the hmac string ends with 'f', md5 gives only 8 possible passwords. When the output is 6 digits, each output number should appear approximately once per 1 million random tests. Below is a very slow alias that calculates a 'random' $hotp number. It shows there are 8 outputs which appear over 60000 times in a million tests when using md5 as the hash:
 
 
 
//var %list 658240 093696 529152 964608 400064 835520 270976 706432 , %i 0 , %x 0 | while (%i < 1000000) { if ($istok(%list,$hotp($rand(1,9999999),$rand(1,9999999),md5,6),32)) inc %x | inc %i } | echo -a 8 passwords appeared %x out of %i times
 
 
 
This is not due to the weakness in the hash output of md5, but instead is caused by how the 32-bit 'truncate' value is chosen from within the shorter md5 hash, often at offsets where - not only are there not 8 hex digits available at that position, but often one of the 8 hex digits used is the same digit used to determine the location of the 31-bit value. When using md5 as the hash, every implementation of HOTP i've seen appends hex digit 0's when there are only 2/4/6 hex digits available at that offset in the hash digests, causing some outputs to appear much more often than they should. SHA1 is a long enough hash digest which doesn't need to append the 0's and doesn't ever re-use the locator hex-digit as part of the 31-bit value.
 
</source>
 
 
 
== Examples ==
 
<source lang="mIRC">
 
; //noop $handshake($nick,#channel)
 
 
 
alias handshake {
 
  inc %handshake_counter
 
  var %a $hotp(%secret_key,%handshake_counter,sha256,6)
 
  if ($1 ison $2) /notice $1 password is %a for counter %handshake_counter
 
}
 
 
 
In a perfect world, both sides of the handshake keep track of the last counter used, then increment it to create a new output value without publicly sharing that counter. If one side of the transaction loses the data, there needs to be a mechanism for both sides to agree on a new counter without reusing a counter. Probably the best method is to either select a new key+counter, or select a new counter that's guaranteed to be greater than the previously used counters, such as using $ctime.
 
 
 
//var %a 1234567890 , %a2 $encode(%a,a) | echo -a key %a is $hotp(%a,1,sha512) key %a2 is $hotp($encode(%a,a),1,sha512)
 
 
Both return identical passwords because the 2nd usage identifies the key as a valid Base-32 encoded string that's a multiple of 8 greater than 8.
 
Both return identical passwords because the 2nd usage identifies the key as a valid Base-32 encoded string that's a multiple of 8 greater than 8.
  
 
//var %a $str(A,16) | echo -a $hotp($upper(%a),1) $hotp($lower(%a),1)
 
//var %a $str(A,16) | echo -a $hotp($upper(%a),1) $hotp($lower(%a),1)
These return the same value because they are recognized as valid Base32 encoded strings, regardless of upper/lower case. To ensure your key cannot be mistaken for a case-insensitive Base16 or Base32 encoding, you should prevent your key from having a length that's a multiple of 8 and which also matches rules #1 or #2 above.
+
These return the same value because they are recognized as valid Base32 encoded strings, regardless of upper/lower case. To ensure your key cannot be mistaken for a case-insensitive Base16 or Base32 encoding, you should prevent your key from being a length that's a multiple of 8 or ensure it contains a space or other character not part of base-32  encoding.
  
//var %a A , %b $base($asc(%a),10,16,2) , %key1 $str(%a,20) , %key2 $str(%b,20) | echo -a key %key1 is $hotp($str(%a,20),1,sha256) - key %key2 is $hotp($str(%b,20),1,sha256)
+
//var %a A | var %b $base($asc(%a),10,16,2) | echo -a $hotp($str(%a,20),1,sha256) $hotp($str(%b,20),1,sha256) - %a vs %b
Both return identical passwords because the 2nd usage identifies the key as a valid Base-16 encoded string of length 40,64, or 128 which decodes to the identical string.
+
Both return identical passwords because the 2nd usage identifies the key as a valid Base-16 encoded string of length 40,64, or 128.
  
 
Both above encoded strings return the same value regardless whether the encoded string contains upper or lower or a mix of upper/lower characters.
 
Both above encoded strings return the same value regardless whether the encoded string contains upper or lower or a mix of upper/lower characters.
 
+
//var %a $chr(233) | var %b $base($asc(%a),10,16,2) | echo -a $hotp($str(%a,20),1,sha256) $hotp($str(%b,20),1,sha256) - %a vs %b
//var %a $str($chr(233),20) | var %b $str($base($asc(%a),10,16,2),20) | echo -a key %a is $hotp(%a,1,sha256) key %b is $hotp(%b,1,sha256)
 
 
This pair also return identical passwords, showing that HOTP UTF8-encodes the underlying decoded hex strings before including them in the key.
 
This pair also return identical passwords, showing that HOTP UTF8-encodes the underlying decoded hex strings before including them in the key.
  
 
//echo -a $hotp(11223344556677889900aabbccddeeff11223344,1)
 
//echo -a $hotp(11223344556677889900aabbccddeeff11223344,1)
 
//echo -a $hotp(112233445566778899aabbccddee00ff11223344,1)
 
//echo -a $hotp(112233445566778899aabbccddee00ff11223344,1)
In addition to UTF8-encoding bytes in a hex string of length 40/64/128, $hotp also removes decoded 0x00's from the decoded output. The above keys are identical except the '00' was cut/pasted to another byte position in the key. Since 0x00's are not added to the key when decoding hex-encoded keys, the output remains unchanged.
+
In addition to UTF8-encoding bytes in a hex string of length 40/64/128, $hotp also removes decoded 0x00's from the decoded output. The above '00' can be cut/pasted to any other byte position in the key and the output remains unchanged.
  
 
//var %digits 3 | while (%digits isnum 3-9) { echo -a %digits $hotp(password,123,sha1,%digits) | inc %digits }
 
//var %digits 3 | while (%digits isnum 3-9) { echo -a %digits $hotp(password,123,sha1,%digits) | inc %digits }
 
All passwords are similar, differing only in the number of digits displayed.
 
All passwords are similar, differing only in the number of digits displayed.
  
//var %a CuRiOsItY KiLlEd ThE CaT | echo -a length $len(%a) $hotp($upper(%a),1) $hotp($lower(%a),1) $hotp(%a,1)
+
//var %a CuRiOsItY KiLlEd ThE CaT | echo -a $hotp($upper(%a),1) $hotp($lower(%a),1) $hotp(%a,1)
Mistakenly decodes as if this is a case-insensitive base32 string. Changing any character to the number 1 while retaining the length 24 changes this into a case-sensitive literal string because the string no longer contains only case-insensitive characters from the base32 alphabet. Even though $decode handles the number 1 as the letter L and number 8 as the letter B, $hotp/$totp do not.
+
Mistakenly decodes as if this is a case-insensitive base32 string
  
 
//echo -a $hotp(test,123) $hotp(test,123abc456)
 
//echo -a $hotp(test,123) $hotp(test,123abc456)
Identical results because mIRC removed non-numeric string front the end of the 'counter' parameter.
+
Identical results because mIRC removed non-numeric string front the end.
  
 
//echo -a $hotp(test,abc) $hotp(test,def) $hotp(test,0)
 
//echo -a $hotp(test,abc) $hotp(test,def) $hotp(test,0)
 
Identical because non-numeric count parameters are treated as if count is zero.
 
Identical because non-numeric count parameters are treated as if count is zero.
  
//var %key $regsubex($str(x,40),/x/g,$iif(\n <= 24,$chr($calc(97+ (\n % 6))) $+ $chr( 0),$chr($calc(97+ (\n % 6))))) | echo -a $len(%key) key %key $hotp(%key,123,sha256,9) / $hotp($upper(%key),123,sha256,9)
+
Even though HOTP supports hash=md5, you should avoid using md5 because HOTP was not designed for hashes shorter than 160 bits, and a few values are returned a high percentage of the time. When the output is 6 digits, each output number should appear approximately once per 1 million random tests. However there are 8 outputs which appear over 60000 times in a million tests:
//var %key $regsubex($str(x,40),/x/g,$iif(\n <= 24,$chr($calc(97+ (\n % 6))) $+ $chr(32),$chr($calc(97+ (\n % 6))))) | echo -a $len(%key) key %key $hotp(%key,123,sha256,9) / $hotp($upper(%key),123,sha256,9)
+
 
Both produce the same output because both are hexadecimal-or-space strings of length 40-or-64-or-128, and after the spaces are removed they have the same case-insensitive hex string.
+
//var %list 658240 093696 529152 964608 400064 835520 270976 706432 , %i 0 , %x 0 | while (%i < 1000000) { if ($istok(%list,$hotp($rand(1,9999999),$rand(1,9999999),md5,6),32)) inc %x | inc %i } | echo -a 8 passwords appeared %x out of %i times
  
//echo -a $hotp($str(0,40),9,sha1,9) / $hotp($str(0,64),9,sha1,9) / $hotp($str(0,128),9,sha1,9) 
+
This is not a weakness in using md5 as a parameter for $hmac, but instead is caused by how the 32-bit 'truncate' value is chosen from within the shorter md5 hash, often at offsets where there are not 8 hex digits available at that position.
//var %key 00 0 00000000000000000000000000000000000 | echo -a $hotp(%key,9,sha1,9) $len(%key) key %key
+
 
These show that all keys entirely consisting of all 0x00 bytes...
+
alias handshake {
//var %key 33 0 00000000000000000000000000000000000 | echo -a $hotp(%key,9,sha1,9) $len(%key) key %key
+
  inc %handshake_counter
//var %key 330 0 0000000000000000000000000000000000 $+ $str(0,24) | echo -a $hotp(%key,9,sha1,9) $len(%key) key %key
+
  var %a $hotp(%secret_key,%handshake_counter,sha256,6)
... or keys identical except ending with different number of 0x00 bytes produce identical output (hex encoded keys of length 40/64/128)
+
  /notice other_nick password is %a for counter %handshake_counter
 +
}
  
//var %key 00 0000000000000000000000000000000000000 | echo -a $hotp(%key,9,sha1,9) $len(%key) key %key
 
//var %key 000 000000000000000000000000000000000000 | echo -a $hotp(%key,9,sha1,9) $len(%key) key %key
 
//var %key 000 00 0 0 0 000000000000000000000000000 | echo -a $hotp(%key,9,sha1,9) $len(%key) key %key
 
These produce a different string than the all-zeroes hex string does, showing that keys of length 40/64/128 containing an odd number of hexadecimal digits do strip the spaces before decoding the remaining string, but they do not append or prepend the 0 digit to handle the not-paired hex digit, so they are processing the string in an unknown manner. Moving the space to a different position shows the spaces are stripped before decoding the string into identical keys. Having an odd-number of zeroes in the 40-byte string always returns the same number that's different than the number returned when there's an even number of zeroes. But so far I have not been able to figure out how mIRC decodes a hex string containing an odd number of hex digits.
 
 
</source>
 
</source>
== Warning ==
 
Through v7.53, $hmac and $hotp and $totp using hash method sha512 or sha384 and a key whose length is 65-128 returned an incorrect value due to replacing the key with its own hash for lengths 65+ instead of only for 129+.
 
<pre>//echo -a $hotp($str(z,65),0,sha512,9) should be 187092660</pre>
 
 
== Compatibility ==
 
== Compatibility ==
 
{{mIRC compatibility|7.42}}
 
{{mIRC compatibility|7.42}}
 +
 
== See Also ==
 
== See Also ==
 
{{collist
 
{{collist

Please note that all contributions to WikiChip may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see WikiChip:Copyrights for details). Do not submit copyrighted work without permission!

Cancel | Editing help (opens in new window)