Community discussions

MikroTik App
 
msatter
Forum Guru
Forum Guru
Topic Author
Posts: 2897
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Password, Pin and and Hash

Sun Nov 15, 2020 4:17 pm

I have completed the script to generate different types of random ranges of characters, numbers or combinations from those.

A One-Time-Password hash generator in RouterOS is used to have randomness to be used in the generator.
Calling the function is quite flexiable and the ordering of the parameters is not relevant except for the length of the wished string.
:put [$genpassword 12 letters numbers];
This will return a password containing lower and capital letters with numbers. If the length is omitted then default length is set to 10. If no preference is given for a type of password then the available ASCI table is used .

Usage: $genpassword length default/letters/numbers/special/pin/dummyhash

On generating the password, the base-string (default/letters/numbers/special/pin/dummyhash) is mixed to a random sequence and so each password has it's own random base sequence.
The function is made global and all the variables used here are local and so on exit of the function erased by RouterOS. I also put two short global functions named genpin and dummyhash that make it easier of generate a pin or and dummyhash and the default length is four for genpin and 4 for dummyhash. For dummy hash the supplied length is always converted to an even number.

If you see room for improvements then let me know in this thread. Storing the generated passwords/Pin in hashes with salting is not something that is easy doable with the tools available in RouterOS, saldly.

I put the code itself in the second posting in this thread.

Update: added the option to provide your own base string/range to base a password or mixed output on.

Example:
:set $pass  [$genpassword 20 ownbase="10"]; :put $pass
result: 10111011101001110100
Last edited by msatter on Wed Nov 18, 2020 2:36 pm, edited 3 times in total.
 
msatter
Forum Guru
Forum Guru
Topic Author
Posts: 2897
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: Password, Pin and and Hash

Sun Nov 15, 2020 4:17 pm

###################################################################################################
# Written for RouterOS from Mikrotik
# Written by Msatter (alias on forum.mikrotik.com) 
# only for non commercial use
# version 20201125-2.58 DO NOT FORGET TO UPDATE ALSO THE :global version underneath, to keep them in sync 
# generate password and store it all locally (function and variables to gain security)
# Extra external function called mixBase which mix-up the base string used
{ # script starter
:global genpassword do={
  : if ([:len $0] = 0) do={:log warning "genpassword ran without a \$0 containing the name of the function"; :error "Exiting function due to a empty \$0"}
  :local arrayString [:toarray ""]

# set VERSION of this script
 :global version 20201125-2.58
 
# Only make help available if exists. Not needed if it an local function  
#:if ([:typeof $helpgenpass] ~ "(nil|nothing)") do={} else={:global helpgenpass}; # Define help function for this script when it exists

### create local function helpgenpass #############################################################
 :local helpgenpass do={
# make global version accessable
 :global version 
## Help for the allowed parameters for myFunc
# allHelp is codeword for show the whole help text and not for a specific parameter
:set $prevParam "allHelp";
 :foreach count,parameter in={$1;$2;$3;$4;$5;$6;$7;$8;$9} do={ \
  :if ([:len $parameter] >= 2) do={:set $lengthParam [:len $parameter]; \    
   :if ([:pick $parameter 0 $lengthParam]=[:pick "-version" 0 $lengthParam]) do={:if ([:typeof $help]="nothing") do={:put "\nVersion: $version";:set $version;:error ""};};
   :if ([:pick $parameter 0 $lengthParam]=[:pick "-help"    0 $lengthParam]) do={:if ([:typeof $help]="nothing") do={:set $help "$prevParam\t--"; :set $parameter;};};
   :set $prevParam $parameter; # prevParam holds the previous parameter to allow -help to know for which parameter help is requested.;
   :if ($count > 0) do={:set $prevParam "allHelp\t--"}; # Whith more than one parameter to provide help for, display them all help.;
  }; # :if :len parameter
 }; # :foreach parameter
# Removes global version needed if the version was not needed to be displayed
 :set $version
## <-- The string with the complete helptext and through usage of "\t--" ("\t" is a TAB)at the end of single parameters those can also be displayed solely. 
:set $helpText "
Creates a password, pin or hash based on their base-strings, it also accept custom base-strings using the parameter ownstring=\"xyz\".\r
Help is is also able to display specific help about a single parameter, if it is entered with -help behind it.\r
\nThe following parameters are available:\r
length\t-- you can define the length of the returned result by stating a number before any other parameter (not applicable for \$onlymix)\r
default\t-- this will use the default length of 10 and the default base-string consisting of numbers, lower and upper case and special characters\r
letters\t-- only lower and upper case characters\r
numbers\t-- exists of numbers from 0-9 (also available as global function \$pin)\r
hash\t-- only hexadecimal form 0-F (also available as global function \$dummyhash)\r
ownbase\t-- provide your own base-string for form a result. (ownbase=\"xyz\") \r
onlymix\t-- mixes the base-string you provided. (onlymix=\"xyz\") (also available as global function \$onlymix).\r
help\t-- when used with an parameter in front then information about that is displayed. \"-help\" alone will display all the parameters.\r
version\t-- displays the version of this script.\r
"; # There should be no extra space(s) at the end of the line(s) or incomplete parameters are shown. -->

# In the foreach above, the variable \$help is filled with the parameter needing explaination. When the content is allHelp then the whole help text is displayed.  
# If there is content in \$help but not present in the help text as label then \"No help found\" is displayed and a new correct parameter provided.
 :if (([:typeof $help] != "nothing") && ($help = "allHelp\t--")) do={[:te st varname-local];:put $helpText;[:te st syntax-noterm];:error ""}
 :if ( [:typeof $help] != "nothing") do={:if [:find $helpText $help 0] do={[:te st varname-local];:put [:pick $helpText ([:find $helpText $help]) ([:find $helpText "\r" ([:find $helpText $help])])]; :error ""} else={ \
  [:te st syntax-noterm];:put "No help found. Was the parameter: $[:pick $help 0 ([:len $help]-1)] fully and correctly written\? You can see them all by stating only -help as parameter."; :error ""} }
 }
### End of function helpgenpass ####################################################################

### Local function to mix the content of the string/numberset and return it ########################
:local mixBase do={
 :set $baseString $1
 :if ([:len $baseString] < 4) do={:set $version;:return $baseString}; # sanity check if there atleast is something to be mixed
 :set $baseLength (([:len $baseString] + 9) / 10); # each retrieved OTP hash, contains 10 HEX values
 
 # Generate enough times hash to have enough characters to fit the password.
# Store commandstring into a variable, the print and remove is temporary until Miktrok fixes the auto removal in OTP.
 :local createOTPHash [/certificate scep-server otp; ([generate minutes-valid=0 as-value]->"password")];
 
 :for j from=1 to=$baseLength do={
   # Generate 2 hashes of each 10 HEX values calling code in a variable $createOTPHash
   :set $hashA $createOTPHash
   :set $hashB $createOTPHash
   [/certificate scep-server otp; remove [find password=$hashA]; remove [find password=$hashB]]; # Remove both in one go

# Calculate which specific mix is going to be used
   :set $selPos ("0x$[:pick $hashA 0 2 ]" / 27); # 0..9
   :set $selMix ("0x$[:pick $hashB $selPos ($selPos+2)]" / 37); # 0..7

# Debug
#:put "BaseLength:$baseLength Pos:$selPos Mix:$selMix"

# Group hashes in pairs. Substracting one because it starts a zero.
   :set $decimalsUP 10000000000000000; # Times 10000000000000000 to increase calculating accuracy. @no-decimals
     :for i from=0 to=19 step=2 do={     
        :set $hexA "0x$[:pick $hashA $i ($i+2)]"
   	:set $hexB "0x$[:pick $hashB $i ($i+2)]"
   	:set $hexA ($hexA / 2); # prefers lower half of the $baseString for $hashA
     
     	:set $hexA (($hexA * $decimalsUP) / ((255*$decimalsUP)  / ([:len $baseString]))); # max. 9223372036854775553
     	:set $hexB (($hexB * $decimalsUP) / ((255*$decimalsUP)  / ([:len $baseString])))

# swapping $HexA and $HexB so that HexA has always the lowest value of the two
	:if ($hexA > $hexB) do={:set $tempHexA $hexA; :set $hexA $hexB; :set $hexB $tempHexA}

# Switch the values in the $baseString but not if the hex values are equal
       :if (($hexA != $hexB) || ($hexA>0) || ($hexB<160)) do={
	:set $baseA  "$[:pick $baseString 0 ($hexA-1)]"
        :set $baseAA "$[:pick $baseString ($hexA-1) $hexA]"
        :set $baseC  "$[:pick $baseString ($hexA) ($hexB-1)]"
        :set $baseB  "$[:pick $baseString ($hexB) [:len $baseString]]"
	:set $baseBB "$[:pick $baseString ($hexB-1) $hexB]"
	
	# Use different sequences and this one is for four round but it can be easily extended
	:if ($selMix=0)  do={:set $baseString "$baseA$baseBB$baseB$baseAA$baseC"}; # A/BB/B/AA/C
	:if ($selMix=1)  do={:set $baseString "$baseB$baseBB$baseA$baseAA$baseC"}; # B/BB/A/AA/C
	:if ($selMix=2)  do={:set $baseString "$baseC$baseBB$baseB$baseAA$baseA"}; # C/BB/B/AA/A
	:if ($selMix=3)  do={:set $baseString "$baseBB$baseB$baseA$baseC$baseAA"}; # BB/B/A/C/AA
	:if ($selMix=4)  do={:set $baseString "$baseAA$baseB$baseA$baseC$baseBB"}; # AA/B/A/C/BB
	:if ($selMix=5)  do={:set $baseString "$baseBB$baseC$baseA$baseB$baseAA"}; # BB/C/A/B/AA
	:if ($selMix>=6) do={:set $baseString "$baseBB$baseB$baseC$baseA$baseAA"}; # BB/B/C/A/AA
	
       }; # if ($hexA....
     }; # for i
  }; # for j
# return the mixed baseString
 :set $version 
 :return $baseString
}; # :local mixBase... [function]


### End mixBase #######################

#######################################
### Begin of genpassword ##############
#######################################

# Sets the length of the password
 :if ([:typeof [:tonum $1]]="nil") do={:set $pwdLength 10} else={:set $pwdLength $1}
# When no length is stated shift the parameters to their expected place when stated
 :if ([:typeof [:tonum $1]]="nil") do={:set $4 $3; :set $3 $2; :set $2 $1; :set $1}
# Always create pairs of HEX when the any parameter contains the word "hash"
 :foreach varname in={"$2"; "$3"; "$4"} do={:if ($varname ~ "hash") do={:set $pwdLength ($pwdLength - ($pwdLength % 2))} }
 
# Using provided base to form a result
 :if ([:typeof $ownbase] ~ "(nil|nothing)") do={} else={:set $2 "ownBase";:set $3;:set $4}
 :if ([:typeof $onlymix] ~ "(nil|nothing)") do={} else={:set $2 "onlyMix";:set $3;:set $4}

# From these strings the password is formed. They are stored in a array.
 :set ($arrayString->"default")      "!&()*+/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]abcdefghijklmnopqrstuvwxyz{}"
 :set ($arrayString->"letters")      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 :set ($arrayString->"pin")          "0123456789"
 :set ($arrayString->"numbers")      "0123456789"
 :set ($arrayString->"special")      "!&()*+/:;<=>@[]{}"
 :set ($arrayString->"dummyhash")    "0123456789ABCDEF"
 :set ($arrayString->"ownBase")	     $ownbase
 :set ($arrayString->"onlyMix")	     $onlymix
 
# Call help if requested and available. Help exits by using :onerror "" so not a neat exit but efficient
 :if ([:typeof $helpgenpass] ~ "(nil|nothing)") do={} else={
  :foreach v in={$2;$3;$4} do={:if ($v ~ "-(help|Help|h|H|version)") do={$helpgenpass $prevPar $v} else={:set $prevPar $v} } }   

# Select the string or combination of strings used to generate the password from. If no string provided select the default one in array
 :if ([:typeof $2] ~ "(nothing|nil)") do={:set $pwdComposedOf (get [$arrayString as-value]->"default")} \
     else={:foreach labelName in={$2;$3;$4} do={:set $pwdComposedOf "$pwdComposedOf$(get [$arrayString as-value]->$labelName)"}
    
# Check if the parameters are matching the key(s) of the array. $compareLabel is adaptive to the number of strings.	
    :foreach label,dummy in=$arrayString do={:set $compareLabel "$compareLabel$label|"}    
    :set $compareLabel "$[:pick $compareLabel 0 ([:len $compareLabel]-1)]"; :set $compareLabel "($compareLabel)"    
    :foreach varname in={"$2"; "$3"; "$4"} do={:if ($varname ~ $compareLabel || [:typeof $varname]~"(nothing|nil)") do={} else={:set $pwdComposedOf "" } }
 }; # if ([typeof $2]....

# Mixing up the string to have each time different base for every password/pin generation run
 :if ([:len $pwdComposedOf] > 0) do={:set $pwdComposedOf [$mixBase $pwdComposedOf]}
 
# exit now when a onlymix was requested
  :if ([:typeof $onlymix] ~ "(nil|nothing)") do={} else={:set $version;:return $pwdComposedOf}

# Only generate a password when there is a string present in $pwdComposedOf. Else throw an on-error. It not a perfect check.
 :if ([:len $pwdComposedOf] > 0) do={
# Generate OTP enough times hash to have enough characters to fit the password (x2).
   :for i from=1 to=(($pwdLength + 9) / 10) do={/certificate scep-server otp; :set $tempHash ([generate minutes-valid=0 as-value]->"password"); remove [find password=$tempHash]; :set $hash "$hash$tempHash"}
   
# Group them each time in pairs. Substracting 1 because it starts a zero.
   :set $decimalsUP 10000000000000000; # Times 10000000000000000 to increase calculating accuracy. @no-decimals
   :for i from=0 to=(($pwdLength * 2)-1) step=2 do={
	:set $hex "0x$[:pick $hash $i ($i+2)]"
	:set $hex (($hex * $decimalsUP) / ((255 * $decimalsUP)  / ([:len $pwdComposedOf])))
	:set $pwdString "$pwdString$[:pick $pwdComposedOf ($hex) ($hex+1)]"
   }; # for i
   
# Compensate if the string is being too short
 :set $addToString  ($pwdLength - [:len $pwdString])
 :set $middleString ($pwdLength / 2) 
 :if ($addToString>0) do={:set $pwdString "$pwdString$[:pick $pwdString $middleString ($middleString + $addToString)]"}

# Debug
#:put "short:$addToString"

   #:log info "Generated password: $pwdString with a length of $pwdLength"
   :set $version
   :return $pwdString
} \
else={
:foreach label,dummy in=$arrayString do={:set $parameters "$parameters$label, "} 
 :set $parameters [:pick $parameters 0 ([:len $parameters]-2)]
 :error "\r\nInvalid parameter(s) used. No password generated. Valid parameters are:\r\n$parameters" }
 
}; # :local genpassword (function)

# Create a mini-me or dedicated looped function
:global genpin do={
:global genpassword
:if ([:typeof [:tonum $1]] = "nil") do={:set $1 4}
:return [$genpassword [:tonum $1] numbers]
}; # :global $genpin
# Create a mini-me or dedicated looped function, creating even pairs of HEX
:global dummyhash do={
:global genpassword
:if ([:typeof [:tonum $1]] = "nil") do={:set $1 4}
:return [$genpassword [:tonum $1] dummyhash]
}; # :global $dummyhash
# Create a mini-me or dedicated looped function, creating even pairs of HEX
:global mixstring do={
:global genpassword
:return [$genpassword onlymix=$1]
}; # :global $mixstring
}; # script end
Last edited by msatter on Wed Nov 25, 2020 3:24 pm, edited 8 times in total.
 
User avatar
Jotne
Forum Guru
Forum Guru
Posts: 3291
Joined: Sat Dec 24, 2016 11:17 am
Location: Magrathean

Re: Password, Pin and and Hash

Tue Nov 17, 2020 8:04 am

Nice script. This is some that should be included in RouterOS as default.

As for the script, you are not consistent with semicolon ; at the end of each line.
Its only needed when there are multiple commands on same line, so it could be removed.

Eks with ;
:set $baseString $1;
and without ;
:set $hashB "$hash$([/certificate scep-server otp generate minutes-valid=0 as-value]->"password")"
 
msatter
Forum Guru
Forum Guru
Topic Author
Posts: 2897
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: Password, Pin and and Hash

Tue Nov 17, 2020 1:04 pm

Thanks Jotne and the ";" presence is known and it is due to I tried to find why I could not put the code in :global. In the end it was due to me using multiple TABs to structure the code.

I will propose the code to Mikrotik and hope that the will have look at it and be so nice to provide a way to also create hashes with salting so that the real password is only known to the user.

Update: removed the unneeded ";" and added the option to provide an own base string/range to form a password/range from.
 
msatter
Forum Guru
Forum Guru
Topic Author
Posts: 2897
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: Password, Pin and and Hash

Thu Nov 19, 2020 10:37 am

I made a second update to the code and added the option to only mix a supplied string (minimal length 4) and the code was already is in the genpassword script. To make this also directly available a small function was added next to the already exisisting genpin and dummyhash to call it directly and so making it easier.

It will return all supplied characters/numbers, which genpassword does not do because it will take in account the wished length of the password and randomize the result.

Example:
:local mixme "abcdef"
:set $mixme [$mixstring $mixme]
:put $mixme
result: bfacde

Also made preparations for the caller for the help function that explains the options and results in general and also specific. This script is already there and I have to adapt for this function.
 
msatter
Forum Guru
Forum Guru
Topic Author
Posts: 2897
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: Password, Pin and and Hash

Thu Nov 19, 2020 5:19 pm

The code has been updated in posting two of this thread. I added a simple help to code and I could insert it locally in the global function $genpassword because it was less than 45 lines of code including the help text.

You can display help by typing: $genpassword -help and displaying the version number of the script : $genpassword -version

The complete help text:

Creates a password, pin or hash based on their base-strings, it also accept custom base-strings using the parameter ownstring="xyz".
Help is is also able to display specific help about a single parameter, if it is entered with -help behind it.

The following parameters are available:
length -- you can define the length of the returned result by stating a number before any other parameter (not applicable for $onlymix)
default -- this will use the default length of 10 and the default base-string consisting of numbers, lower and upper case and special characters
letters -- only lower and upper case characters
numbers -- exists of numbers from 0-9 (also available as global function $pin)
hash -- only hexadecimal form 0-F (also available as global function $dummyhash)
ownbase -- provide your own base-string for form a result. (ownbase="xyz")
onlymix -- mixes the base-string you provided. (onlymix="xyz") (also available as global function $onlymix).
help -- when used with an parameter in front then information about that is displayed. "-help" alone will display all the parameters.
version -- displays the version of this script.


Displaying the version: $genpassword -version
Version: 20201119-2.52

And help on a single parameter: $genpassword numbers -help
numbers -- exists of numbers from 0-9 (also available as global function $pin)
 
msatter
Forum Guru
Forum Guru
Topic Author
Posts: 2897
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: Password, Pin and and Hash

Wed Nov 25, 2020 3:05 pm

I have put a updated version in the second post of this tread.

Removed some bugs, corrected typos and changed some incorrect code. Streamlined the removal of generated OTP hashes so that the generated hashes that became obsolete are removed directly.

Introduced a dedicated variable $decimalUP to have better rounding of integers when dividing integer by integer. Multiplication factor 10000000000000000

Who is online

Users browsing this forum: rogerioqueiroz and 28 guests