locked
VBScript Truly Random Number/Password Generators? RRS feed

  • Question

  • So... I have a script that generates random passwords for use in a project in VBS.  The code used to generate the password is something that is very similar to this blog post:  http://blogs.msdn.com/b/gstemp/archive/2004/02/23/78434.aspx.  And considering every random-number generator in VBS that I was able to find appears to be based (at least, in part) off of this code, I'm looking for something that is reliable. 

    The above blog article basically states that the builtin Rnd and Randomize functions are not functionally secure for password generation because of the small number of "seeds" that vbscript has builtin to it's randomize/rnd functions.  And because of this small number of seeds, it's relatively easy to deduce the password because of it.  So ultimately, I'm asking to know if there is a reliable way to generate, "truly random" numbers that are secure enough for password values (through random number -> ANSI character translation)?

    The article talks about the Crypto API being able to generate truly random numbers but I wasn't able to find any documentation on how to access the Crypto API from VBS.  Can anyone provide any assistance on either of these two questions?  Thanks.

    Friday, October 5, 2012 3:23 PM

Answers

  • I discuss random number generation in VBScript on this page:

    http://www.rlmueller.net/Numbers.htm

    There is a link to a VBScript program that duplicates the Rnd function, which proves it is based on a 24-bit Linear Congruential Generator. For most purposes this is fine, but another link on the page displays the results of randomness tests. This shows the 24-bit LCG does very poorly compared to other functions. The page has a link to a 32-bit Multiply With Carry psuedo random number generator that performs much better. It requires dealing with 64-bit numbers, so the VBScript code is complex, but is still fast, reliable, and does much better on randomness tests. If you really need a good generator, there is also a link for a Combo generator, which is essentially the combination of 12 32-bit Multiply With Carry generators. This does even better on randomness tests.

    To be honest, these functions would be overkill for password generation, but you are free to use them if you want. If you are generating a million numbers it can help to have a better generator. For a few passwords the Rnd function is fine. The strength of the password depends on the number of characters and the number of possible characters far more than the "randomness" of the generator.


    Richard Mueller - MVP Directory Services

    • Marked as answer by thepip3r Tuesday, October 9, 2012 7:23 PM
    Friday, October 5, 2012 6:38 PM
  • there was a bug in my original code where in the off-chance that an entire guid worth of hexidecimal values were scanned and a number wasn't generated that met within the bounds of the given criteria, the function would return the last value attempted, period.  in my case, i saw numbers that were less-than 33.  i've fixed the code and it appears to be working as desired just in case anyone chooses to use this function in the future:

    Function GetRandomInt(iLowPart,iHighpart) 
    	iLowPartLen = Len(iLowPart)
    	iHighPartLen = Len(iHighPart)
    	' Loop through dynamically generated GUIDs until we find a string of characters that meet the criteria
    	Do 
    		' Generate a starting point to read out of a 32-bit GUID string
    		Randomize
    		iGuidLow = Int(((32 - 8) - 1 + 1) * Rnd + 1)
    		
    		' Generate a GUID
    		objGuid = CreateObject("Scriptlet.TypeLib").Guid
    		' Strip out the dashes and braces and pull an 8-character section of the GUID from the provided starting point
    		strGuid = Mid(Replace(Replace(Replace(CStr(objGuid), "-", ""), "{", ""), "}", ""), iGuidLow, 8)
    		' Convert back to a number
    		iGuid = Abs(CLng("&h" & strGuid))
    		
    		iGuidLen = Len(iGuid)
    		
    		' Generate the span of characters to generate given the high/low vals
    		Randomize
    		iLengthVal = Int((iHighPartLen - iLowPartLen + 1) * Rnd + iLowPartLen)
    		
    		' Loop through the GUID-fragment-converted-to-number to find a value between the given numeric span
    		For iStartPos=1 to iGuidLen
    			iSegment = CInt(Mid(iGuid, iStartPos, iLengthVal))
    			
    			If iSegment >= iLowPart And iSegment <= iHighPart Then
    				bStatus = vbTrue
    				Exit Do
    			End If
    		Next
    		
    	Loop Until bStatus = vbTrue
    	
    	GetRandomInt = iSegment
    End Function

    • Marked as answer by thepip3r Tuesday, October 9, 2012 7:23 PM
    Tuesday, October 9, 2012 6:17 PM

All replies

  • Hi,

    Try generating a GUID, they're "better" random numbers than VBS ones.

      Dim TypeLib
      Set TypeLib = CreateObject("Scriptlet.TypeLib")
      MsgBox Right(TypeLib.Guid, 8)

    That will generate a random number (in hexadecimal) from 00000000 to FFFFFFFF


    Sebastian Sajaroff Senior DBA Pharmacies Jean Coutu

    Friday, October 5, 2012 3:37 PM
  • I had to use Mid() instead of Right() since your code grabs the closing '}' curly brace on the GUID.  Cool idea though -- now I just need to figure out the high->low part for range generation.  Thx again!

    WScript.Echo GetRandomInt()
    WScript.Echo GetRandomInt()
    WScript.Echo GetRandomInt()
    WScript.Echo GetRandomInt()
    Function GetRandomInt()     
    	GetRandomInt = Abs(CLng("&h" & Mid(CreateObject("Scriptlet.TypeLib").Guid, 28, 8) ))
    End Function

    • Edited by thepip3r Friday, October 5, 2012 5:02 PM
    Friday, October 5, 2012 5:00 PM
  • I'm not sure a GUID is what you want to use if you are concerned about randomness. See these:

    GUIDs are designed to be unique, not random

    GUIDs are globally unique, but substrings of GUIDs aren't

    Bill

    Friday, October 5, 2012 5:27 PM
  • So here's how I created quasi-randomness in the GUID substr:

    Low = 33
    High = 122
    For i=0 to 100 
    	WScript.Echo "END:  " & GetRandomInt(Low,High)
    Next
    Function GetRandomInt(iLowPart,iHighpart) 
    	Do 
    		iGUID = Abs(CLng("&h" & Mid(CreateObject("Scriptlet.TypeLib").Guid, 28, 8) ))
    		iGUIDLen = Len(iGUID)
    		
    		For iStartPos=1 to iGUIDLen
    			For iLengthVal=1 to iGUIDLen
    				iSegment = CInt(Mid(iGUID, iStartPos, iLengthVal))
    				
    				If iSegment >= iLowPart And iSegment <= iHighPart Then
    					Exit Do
    				Else
    					If iSegment > iHighPart Then
    						Exit For
    					End If
    				End If
    			Next
    		Next
    		
    	Loop Until iStartPos > iGUIDLen
    	
    	GetRandomInt = iSegment
    End Function

    ... I scan the int (generated from the GUID substring) until I find values that are between the given range and pass it back -- so there is SOME added randomness in this method.  However, as Eric points out in yoru articles, just as if you know the time the GUID was generated, with this method, if you know the TIME and the number range, you could deduce the password.

    So I'm still having the same problem because Eric Lippert is the guy who tore up the article I posted about VBS's internal number generator but in the threads you posted (Bill), he says don't use a GUID, use a number generator... =/

    So I guess I'm simply back to... does anyone know how to use the Crypto API in VBS, period, and then if so, how do you generate numbers with it?

    Friday, October 5, 2012 6:08 PM
  • I discuss random number generation in VBScript on this page:

    http://www.rlmueller.net/Numbers.htm

    There is a link to a VBScript program that duplicates the Rnd function, which proves it is based on a 24-bit Linear Congruential Generator. For most purposes this is fine, but another link on the page displays the results of randomness tests. This shows the 24-bit LCG does very poorly compared to other functions. The page has a link to a 32-bit Multiply With Carry psuedo random number generator that performs much better. It requires dealing with 64-bit numbers, so the VBScript code is complex, but is still fast, reliable, and does much better on randomness tests. If you really need a good generator, there is also a link for a Combo generator, which is essentially the combination of 12 32-bit Multiply With Carry generators. This does even better on randomness tests.

    To be honest, these functions would be overkill for password generation, but you are free to use them if you want. If you are generating a million numbers it can help to have a better generator. For a few passwords the Rnd function is fine. The strength of the password depends on the number of characters and the number of possible characters far more than the "randomness" of the generator.


    Richard Mueller - MVP Directory Services

    • Marked as answer by thepip3r Tuesday, October 9, 2012 7:23 PM
    Friday, October 5, 2012 6:38 PM
  • @Richard

    The Rnd() function does not appear to be good enough for me.  I'm testing password generation on different machines at startup and writing those values to AD.  What I've seen is a preponderance of either "very close" or identical passwords from using this general code:

    Randomize
    rndNum = Int((122 - 33 + 1) * Rnd + 33)

    ...who's value gets converted to a character and appended to a string that gets repeated x times to generate a password.  To show you what spawned this entire thread, my computers (that are all running this at startup), are generating passwords that look like this:

    "ua*td"4poYAp=SlL
    #ua+td"4ppZBq>SlL
    #vb+ue#5qpZBr>TmM
    $wc-vf$6rq[Ds?UnN
    $wc-vf$6rq[Ds?UnN
    $wc-vf$6rr[Ds@UnN
    -%k5$n,>zzdL!H^vV
    %wd-vf%6sr\Ds@VoN
    &ye.xh&8ts]EtAWpP
    &ye/xh&8tt]FuBWpP
    (!g0zj(:vu_GwCYrR
    (!g0zj(:vu_HwCYrR
    )!g1zj(:vv`HwDZrR
    )!h1!k):wv`HwDZsS
    *"h2!k);wwaIxEZsS
    *"i2!k*;xwaIxE[tS
    *#i2"l*<xwaIyE[tT
    *#i3"l*<xxbJyF[tT
    *6"F5%=O11u]2Yo-g
    ,$j4#m+=yycKzG]uU
    ,%k5$n,>zycL!H]vV
    .'m6&p.@"!eM"I_xX
    /'n7&p/@#"fN#J`yX
    :2xB1!9K--qY.Uk)c
    :2xB1!9K--qY.Uk)c
    :2xB1!9K--qY.Uk)c
    :2yB1!9K.-qY.Uk)c
    :2yB1!9K.-qY.Uk)c
    :2yB2":K.-qY.Uk*d
    :3yC2":L..qZ/Vk*d
    :3yC2":L..qZ/Vk*d
    ;3yC2":L..rZ/Vl*d
    ;4zC3#;M/.rZ0Vl+e
    ;4zC3#;M/.rZ0Vl+e
    ;4zD3#;M/.r[0Vl+e
    ?7#G6&>P22v^3Zo.h
    ?7#G6&>P22v^3Zo.h
    ?7#G6&>P22v^3Zp.h
    ?7#G6&>P32v^3Zp.h
    ?7$G6&>P32v^3Zp.h
    ?7$G7'?P32v^3Zp/i
    ?8$G7'?Q32v^3Zp/i
    ?8$Gn^v.jjS<k8MfF
    ?8$H7'?Q32v_4Zp/i
    @8$H7'?Q33w_4[q/i
    @9%I8(@R43w`5[q0j
    @9%I8(@R44w`5\q0j
    [S?cRBZlNN8zOv2J*
    [S@cRB[lON8zOv2K*
    [S@cRBZlON8zOv2J*
    [T@cSC[mON8zPv2K+
    [T@cSC[mON8zPv2K+
    [T@dSC[mOO9!Pw2K+
    \kW!jZr*ffP8g4IbB
    \T@dSC[mOO9!Pw2K+
    \T@dSC[mOO9!Pw3K+
    \T@dSC[mOO9!Pw3K+
    \T@dSC[mOO9!Pw3K+
    \TAdSC[mPO9!Pw3L+
    \UAdTD\nPO9!Qw3L,
    \UAeTD\nPP:"Qx3L,
    ]UAeTD\nPP:"Qx4L,
    ]VBeUE]oQP:"Qx4M-
    ]VBfUE]oQP:#Rx4M-
    ^VBfUE]oQQ;#Ry4M-
    ^VCfUE]oRQ;#Ry5M-
    ^WCfVF^pRQ;#Ry5N.
    ^WCfVF^pRQ;#Ry5N.
    _WCgVF^pRR<$Sz5N.
    _WCgVF^pRR<$Sz5N.
    _WCgVF^pRR<$Sz6N.
    _WCgVF^pRR<$Sz6N.
    _XDhWG_qSS<%T!6O/
    `XEhWG_qTS=%T!7O/
    `YEhXH`rTS=%T!7P0
    `YEhXH`rTS=%U!7P0
    `YEhXH`rTS=%U!7P0
    `YEhXH`rTS=%U!7P0
    `YEhXH`rTS=&U!7P0
    `YEiXH`rTS=&U!7P0
    `YEiXH`rTS=&U!7P0
    `YEiXH`rTT=&U"7P0
    +#i3"l*<xxbJyF[tT
    +#i3"l*<xxbJyF[tT
    +$j3#m+=yxbJyF\uU
    +$j4#m+=yxbKzF\uU
    <4!D4$<M0/s[0Wm,f
    <4zD3#;M//s[0Wl+e
    <5!D4$<N0/s[0Wm,f
    <5!E4$<N0/s\1Wm,f
    =5!E4$<N00t\1Xm,f
    =5"E4$=N10t\1Xn-f
    =6"E5%=O10t\2Xn-g
    =6"E5%=O10t]2Xn-g
    =6"F5%=O11t]2Yn-g
    =6"F5%=O11t]2Yn-g
    >6"F5%=O11u]2Yn-g
    >6#F5%=O21u]2Yo-g
    >6#F5&>O21u]2Yo.g
    >7#F6&>P21u]2Yo.h
    >7#G6&>P21u^3Yo.h
    0(n8'q/A##gO$KayY
    0(n8'q/A##gO$KayY
    0(o8(r0A$#gO$KazZ
    0(o8'q/A$#gO$KayY
    0)o9(r0B$#gP%KazZ
    0)o9(r0B$#gP%KazZ
    0)o9(r0B$$gP%LazZ
    1)o9(r0B$$hP%LbzZ
    1)o9(r0B$$hP%LbzZ
    1*p:)s1C%$hQ&Lb![
    1*p:)s1C%%hQ&Mb![
    1*p9)s1C%$hQ&Lb![
    2*p:)s1C%%iQ&Mb![
    2+q:*t2D&%iQ&Mc"\
    2+q:*t2D&%iQ'Mc"\
    4,s<,v4E('kS(Oe$^
    4'm7&p.@"!eN#I_xX
    4-s<,v4F('kT)Oe$^
    5.t=-w5G)(lT)Pf%_
    5.t=-w5G)(lT*Pf%_
    5.t=-w5G)(lU*Pf%_
    5.t>-w5G))lU*Qf%_
    5.t>-w5G))lU*Qf%_
    5-s=,v4F((lT)Pe$^
    6.t>-w5G))mU*Qf%_
    6.t>-w5G))mU*Qg%_
    6/u?.x6H*)mV+Qg&`
    6/u?.x6H**mV+Rg&`
    6/u?.x6H**nV+Rg&`
    7/u?.x6H**nV+Rh&`
    70v?/y7I+*nV,Rh'a
    70v@/y7I+*nW,Rh'a
    70v@/y7I++nW,Sh'a
    81wA0z8J,,oX-Ti(b
    81wA0z8J,+oX-Si(b
    81wA0z8J,+oX-Si(b
    81wA0z8J,+oX-Si(b
    91wA0z8J,,pX-Tj(b
    91xA0z8J-,pX-Tj)b
    92xA1!9K-,pX.Tj)c
    92xA1!9K-,pX-Tj)c
    92xA1!9K-,pX-Tj)c
    92xB1!9K-,pY.Tj)c
    A:&I9)AS54x`5\r1k
    A:&I9)AS54x`6\r1k
    A9%I8(@R44x`5\q0j
    A9&I9)AR54x`5\r1j
    aYEiXH`rTT>&U"7P0
    aYEiXH`rTT>&U"7P0
    aYEiXH`rTT>&U"8P0
    aYEiXH`rTT>&U"8P0
    aYEiXH`rTT>&U"8P0
    aZFiYIasUT>&U"8Q1

    What you see above is a snippet of the passwords that each computer generates and writes to AD and then sorted through PowerShell to show the similarities/identicals.  I'm only running this on 300ish systems and they seem to be relatively close.  As you stated, I'm not sure if the number generators you provided (which were awesome btw) are necessary but unless I'm implementing the Randomize/Rnd functions completely wrong, these passwords seem to be WAY to close for comfort.  I think I'm going to try a variation of the GUID usage and run it for awhile to see how "frequent" passwords appear similarly:

    Low = 33
    High = 122
    For i=0 to 100 
    	WScript.Echo "END:  " & GetRandomInt(Low,High)
    Next
    Function GetRandomInt(iLowPart,iHighpart) 
    	iLowPartLen = Len(iLowPart)
    	iHighPartLen = Len(iHighPart)
    	' Loop through dynamically generated GUIDs until we find a string of characters that meet the criteria
    	Do 
    		' Generate a starting point to read out of a 32-bit GUID string
    		Randomize
    		iGuidLow = Int(((32 - 8) - 1 + 1) * Rnd + 1)
    		
    		' Generate a GUID
    		objGuid = CreateObject("Scriptlet.TypeLib").Guid
    		' Strip out the dashes and braces and pull an 8-character section of the GUID from the provided starting point
    		strGuid = Mid(Replace(Replace(Replace(CStr(objGuid), "-", ""), "{", ""), "}", ""), iGuidLow, 8)
    		' Convert back to a number
    		iGuid = Abs(CLng("&h" & strGuid))
    		
    		iGuidLen = Len(iGuid)
    		
    		' Generate the span of characters to generate given the high/low vals
    		Randomize
    		iLengthVal = Int((iHighPartLen - iLowPartLen + 1) * Rnd + iLowPartLen)
    		
    		' Loop through the GUID-fragment-converted-to-number to find a value between the given numeric span
    		For iStartPos=1 to iGuidLen
    			iSegment = CInt(Mid(iGuid, iStartPos, iLengthVal))
    			
    			If iSegment >= iLowPart And iSegment <= iHighPart Then
    				Exit Do
    			Else
    				If iSegment > iHighPart Then
    					Exit For
    				End If
    			End If
    		Next
    		
    	Loop Until iStartPos > iGuidLen
    	
    	GetRandomInt = iSegment
    End Function

    Friday, October 5, 2012 8:52 PM
  • I agree the numbers you show are suspicious. However, I suspect you are invoking the Randomize function repeatedly, perhaps before the generation of each password (or each character). The Randomize function should be invoked just once at the start of the script. It seeds the Rnd function based on the system clock. If you generate passwords quickly, and invoke Randomize repeatedly, you may get the same initial seed, or very close. In any case, before discarding Rnd, check this function that quickly generates as many passwords as you like. In my tests the passwords are very different. You only should be able to detect the limitations of this function after you generated many hundreds of thousands of passwords.


    Option Explicit

    Randomize

    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword
    Wscript.Echo GetPassword

    Function GetPassword
        ' Function to generate a random 16 character password,
        ' between ASCII 33 and ASCII 122.
        Dim k, strPassword

        GetPassword = ""
        For k = 1 To 16
            GetPassword = GetPassword & Chr(Fix(90 * Rnd()) + 33)
        Next
    End Function

    -----

    If passwords are generated at startup by each computer, they should only get the same password if they invoke Randomize at the same instant. I don't see how the passwords can be close, as you show. I frankly never figured out how Randomize works, but it should not matter if it is invoked only once.


    Richard Mueller - MVP Directory Services


    Saturday, October 6, 2012 1:36 AM
  • @Richard.... the code you're using is very similiar to the code I implemented but you're right, I am calling the Randomize() function many times -- however, I still don't see how hundreds of computers running the code independently of each other, at startup, would generate such similar passwords.  Here are the relevant pieces of that code:

    strAdminPassword = generatePassword(16) Function generateRandomInteger(iLower, iUpper) ' Seed Rnd with a random value(Randomize) and then calculate the random value using the given bounds Randomize generateRandomInteger = Int((iUpper - iLower + 1) * Rnd + iLower) End Function Function generatePassword(iLength) For i = 0 To iLength iRandomNumber = generateRandomInteger(33, 122) strTempPassword = strTempPassword & Chr(iRandomNumber) Next generatePassword = strTempPassword End Function

    The code above is what is generating the duplicates.  Using the Guid generation script I posted in my most recent post, the values appear to be coming in MUCH more random:

    ">47OpaQ1H*:4ZD\3
    "7G#y"@Z"UQkR%ZF2
    #W_TX#'o]MQBqK20j
    $#cTk4HBoB6<0W(]z
    (?.,iCV'/7f3.[FSF
    ).F6\AD#.2_%jKu7_
    *FDX2Hz[$csCBLKc?
    ?:U#b07;I5T[&87s)
    [K067te_=$My+S[xR
    \%s.F0+G@J3J$C-0e
    ]!V9a&E6WO=M(qIGN
    ]J:c?'C4_[o3:7^D"
    ^!?mD-LG=xLKOQ4v,
    ^%EGwfB5R`Sb:nF[9
    ^@rI2Pb<M.t@v;K3"
    `#A&Q,nQcSy7"!f3S
    +/Cc3#:EI],,65:H7
    +]Dn23_V1173bEG?+
    0D2OXPZ-P7w!!bPGA
    2sII0-n'/-eC[&-*[
    46P#tVg?3807VvX=/
    5>X;b?^-:P9]Vw+H>
    6hP,ab\9%mPl7rA>:
    72GEUF@-+fXb7DvLK
    -8.JA?V.btYA$@X"H
    8O[Gs42CZ>3+X\-oC
    998f3:*^_e4`QuQWE
    D\6f!8$\;;K\$3v=`
    D1Hm9'L)G#>O>>blC
    EW#Hxrp^t.5\c)5Or
    g8;O_Xyo#"*O:E+?A
    Hn&3Kx\0x`7c:ZAL^
    ja3f8-B5"@B_P&%vi
    l<YVM/0W;%$1H$B.c
    m!<L3T]z^YumRIF*1
    N'#KzUJK,=G>aR=zb
    q)!P\[=.GF%G&4+A^
    RoOOHDLCm+*<@!;pt
    SzRH[:`I3TK)9y`+o
    Z-$UxD7.dKD6:x=cc

    Those are all of the passwords that have been generated since I psoted the update to the script, sorted, to show the alpha-similarity.  It's only 40 systems so far and I'll keep an eye on it but at this point, it appears to be working as desired. 

    If you have any recommendations/explanations as to why the passwords using the normal VBS RNG are showing so similarly even though they are running on a per computer basis, at startup (via GPO), I'd love to hear how I'm implementing it wrong.  =D

    Thanks

    Tuesday, October 9, 2012 3:29 PM
  • there was a bug in my original code where in the off-chance that an entire guid worth of hexidecimal values were scanned and a number wasn't generated that met within the bounds of the given criteria, the function would return the last value attempted, period.  in my case, i saw numbers that were less-than 33.  i've fixed the code and it appears to be working as desired just in case anyone chooses to use this function in the future:

    Function GetRandomInt(iLowPart,iHighpart) 
    	iLowPartLen = Len(iLowPart)
    	iHighPartLen = Len(iHighPart)
    	' Loop through dynamically generated GUIDs until we find a string of characters that meet the criteria
    	Do 
    		' Generate a starting point to read out of a 32-bit GUID string
    		Randomize
    		iGuidLow = Int(((32 - 8) - 1 + 1) * Rnd + 1)
    		
    		' Generate a GUID
    		objGuid = CreateObject("Scriptlet.TypeLib").Guid
    		' Strip out the dashes and braces and pull an 8-character section of the GUID from the provided starting point
    		strGuid = Mid(Replace(Replace(Replace(CStr(objGuid), "-", ""), "{", ""), "}", ""), iGuidLow, 8)
    		' Convert back to a number
    		iGuid = Abs(CLng("&h" & strGuid))
    		
    		iGuidLen = Len(iGuid)
    		
    		' Generate the span of characters to generate given the high/low vals
    		Randomize
    		iLengthVal = Int((iHighPartLen - iLowPartLen + 1) * Rnd + iLowPartLen)
    		
    		' Loop through the GUID-fragment-converted-to-number to find a value between the given numeric span
    		For iStartPos=1 to iGuidLen
    			iSegment = CInt(Mid(iGuid, iStartPos, iLengthVal))
    			
    			If iSegment >= iLowPart And iSegment <= iHighPart Then
    				bStatus = vbTrue
    				Exit Do
    			End If
    		Next
    		
    	Loop Until bStatus = vbTrue
    	
    	GetRandomInt = iSegment
    End Function

    • Marked as answer by thepip3r Tuesday, October 9, 2012 7:23 PM
    Tuesday, October 9, 2012 6:17 PM
  • Your script using Rnd calls Randominze once per character. The resulting sequence will not be random. However, you won't be able to tell in 16 characters. This does not account for passwords generated on different computers being so similar.

    I always assumed that Randomize seeded Rnd based on a value from the system clock. When I investigated years ago I could not determine anything about how Randomize works. Based on your experience, I wonder if the value is based on a clock/timer that is zeroed at startup. Even this seems unlikely to account for what you experience. When I run your code, which invokes Randomize before each character, I don't see the affect in the passwords (like repeating characters), even if I generate several passwords at once.

    Regarding the passwords you posted, with so many similar or identical values, the Rnd function can only generate 16,777,215 unique passwords. There can only be that many initial states for the generator before it repeats. However, if you randomly pick 300 passwords from this collection, the odds should be small that you have as many duplicates as you show. If you generate 1000 passwords randomly from a collection of 16,777,215, I think the odds of any two matching is about 0.0293 (variation of the birthday problem). This tells me something else is going on in your situation we don't understand.

    However, if you have a solution that works, your problem is solved.


    Richard Mueller - MVP Directory Services

    Tuesday, October 9, 2012 7:17 PM
  • Thanks for the assistance.

    Tuesday, October 9, 2012 7:23 PM
  • For anyone interested in this thread with calls to vbscrit's Randomize()/Rnd(), I found something interesting.  So I took the script that generates passwords using VBScript's builtin functions and ran it against 4 random machines and generated 10,000 passwords on each:

    For x=0 to 10000
    	WScript.Echo generatePassword(16)
    Next
    Function generateRandomInteger(iLower, iUpper)
    	Randomize
    	generateRandomInteger = Int((iUpper - iLower + 1) * Rnd + iLower)
    End Function
    Function generatePassword(iLength)
    	
    	For i = 0 To iLength
    		iRandomNumber = generateRandomInteger(33, 126)
    		strTempPassword = strTempPassword & Chr(iRandomNumber)
    	Next
    	
    	generatePassword = strTempPassword
    End Function

    With each computer running the above code independently (invoked through powershell), i was able to aggregate the results and while each host generates 10000 passwords that appear unique, when the results are coupled together, the similiarities are very high.

    I think i'm going to alter to script to do Randomize() once to see if that makes any difference... hopefully the moderators won't mind me extending the thread a bit longer for the sake of research.  =D

    Tuesday, October 9, 2012 8:31 PM
  • Interestingly enough, it appears that the problem is with the multiple calls to Randomize... if I change the code to:

    Randomize
    For x=0 to 10000
    	WScript.Echo generatePassword(16)
    Next
    Function generatePassword(iLength)
    	For i = 0 To iLength
    		iRandomNumber = Int((126 - 33 + 1) * Rnd + 33)
    		strTempPassword = strTempPassword & Chr(iRandomNumber)
    	Next
    	
    	generatePassword = strTempPassword
    End Function

    The passwords are MUCH more random per host than the near identical passwords that get generated with the multiple calls to Randomize.

    Thanks for allowing me to see this through and hopefully someone else finds these posts useful in the future.

    Tuesday, October 9, 2012 8:48 PM
  • Interestingly enough, it appears that the problem is with the multiple calls to Randomize... if I change the code to:

    Randomize
    For x=0 to 10000
    	WScript.Echo generatePassword(16)
    Next
    Function generatePassword(iLength)
    	For i = 0 To iLength
    		iRandomNumber = Int((126 - 33 + 1) * Rnd + 33)
    		strTempPassword = strTempPassword & Chr(iRandomNumber)
    	Next
    	
    	generatePassword = strTempPassword
    End Function

    The passwords are MUCH more random per host than the near identical passwords that get generated with the multiple calls to Randomize.

    Thanks for allowing me to see this through and hopefully someone else finds these posts useful in the future.

    If you look up above you will see that that was already covered by Richard Mueller:

    "The Randomize function should be invoked just once at the start of the script. It seeds the Rnd function based on the system clock. If you generate passwords quickly, and invoke Randomize repeatedly, you may get the same initial seed"


    ¯\_(ツ)_/¯

    Tuesday, October 9, 2012 10:27 PM
  • Hi everyone, I know this is an old thread but I thought that I would add the results of my research and experimentation. To make a long story short, I found that the Scriptlet.TypeLib's GUID value proved to be an excellent seed value for use with the PRNGs designed by Richard at his site above. If you are interested in more details then read on...

    First, Richard - Thank you very much for sharing your MWC generators - I have found them to be most helpful. After some testing I wanted to strengthen the initial seed value of the combo generator, so instead of being based on the system timer I decided to derive it from a GUID generated by Scriptlet.TypeLib (more on the randomness of this value below). Basically, I converted the GUID output to a digit-based representation (not a true hex to decimal conversion due to speed constraints in manually performing long arithmetic of this sort). So, for example, F0C3294A would be converted into a string of digits "15012329410", which I would then obtain a modulo of that against the maximum seed value which I wished to use (in the case of your MWC combo it is 2^32-1). Note that I did have to write a function to handle long division supporting a dividend of arbitrary length and a divisor of up to 15 digits (so as to avoid vbscript's scientific notation), but I feel that it runs quite well and has no constraints on the size of the dividend or the quotient. (the modulo will always be 15 digits or less because I do not allow a divisor to exceed that length).

    As far as the seeming randomness of Scriptlet.TypeLib GUID goes, I ran a test across 4 separate servers to see if there would be any overlaps when generated at the same time. I basically had a scheduled task fire, and then the script running on each server would generate 4 GUIDs within the same timer value (millisecond). To my satisfaction I found that every GUID generated was different - multiple GUIDs generated per millisecond differed from one-another on both the machine level and across multiple servers. I could not find documentation explaining which algorithm is used to generate them, but yes, they do appear to be quite unique and random. Perhaps not random enough to be considered cryptographically secure, but certainly random enough to generate a (seemingly) unpredictable seed for use with another PRNG.

    So, to make a long story short, I've implemented Richard's combo generator, and I seed it with a number derived from the GUID. If you would like to know any more details feel free to write back.

    Friday, January 16, 2015 5:30 AM