PUBLIC ofrmpasswordmanager ofrmpasswordmanager=NEWOBJECT("frmpasswordmanager") ofrmpasswordmanager.Show RETURN *#INCLUDE "\\skyapps\apps\foxv\classes\encrypt.h" * DEFINE CLASS frmpasswordmanager AS form Height = 546 Width = 574 DoCreate = .T. BorderStyle = 3 Caption = "Password Manager" passwordgenerator = .NULL. currentpasswordpk = .NULL. lastpasswordpk = .NULL. help_id = 2008-01403 oldlibraries = "" Name = "frmPasswordManager" ADD OBJECT shape1 AS shape WITH ; Top = 146, ; Left = 1, ; Height = 363, ; Width = 572, ; BorderStyle = 0, ; FillStyle = 0, ; FillColor = RGB(255,219,183), ; ZOrderSet = 0, ; Name = "shape1" ADD OBJECT txtcurrentpassword AS textbox WITH ; FontName = "Consolas", ; FontSize = 12, ; Height = 23, ; Left = 119, ; Margin = 0, ; ReadOnly = .T., ; Top = 30, ; Width = 367, ; ForeColor = RGB(255,0,128), ; ZOrderSet = 7, ; Name = "txtCurrentPassword" ADD OBJECT lblnewpassword AS label WITH ; Caption = "New Password", ; Left = 1, ; Top = 172, ; ZOrderSet = 8, ; Name = "lblNewPassword" ADD OBJECT txtnewpassword AS textbox WITH ; FontName = "Consolas", ; FontSize = 12, ; Format = "T", ; Height = 23, ; Left = 119, ; Margin = 0, ; Top = 169, ; Width = 367, ; ZOrderSet = 9, ; Themes = .F., ; Name = "txtNewPassword" ADD OBJECT cmdgenerate AS commandbutton WITH ; Top = 147, ; Left = 1, ; Height = 23, ; Width = 117, ; FontName = "Arial Narrow", ; FontSize = 10, ; Caption = "New random password", ; ZOrderSet = 10, ; Name = "cmdGenerate" ADD OBJECT ctrchecklist AS ctrCheckListClass WITH ; Top = 200, ; Left = 119, ; Width = 367, ; Height = 307, ; SpecialEffect = 0, ; ZOrderSet = 11, ; Name = "ctrCheckList" ADD OBJECT commandbutton1 AS commandbutton WITH ; Top = 30, ; Left = 488, ; Height = 23, ; Width = 84, ; Caption = "To Clipboard", ; ZOrderSet = 12, ; Name = "commandbutton1" ADD OBJECT label3 AS label WITH ; Caption = "Validity Checklist", ; Left = 1, ; Top = 202, ; ZOrderSet = 13, ; Name = "label3" ADD OBJECT txtlastpasswordused AS textbox WITH ; FontName = "Consolas", ; FontSize = 12, ; Height = 23, ; Left = 119, ; Margin = 0, ; ReadOnly = .T., ; Top = 2, ; Width = 367, ; ForeColor = RGB(0,0,255), ; ZOrderSet = 14, ; Name = "txtLastPasswordUsed" ADD OBJECT commandbutton2 AS commandbutton WITH ; Top = 2, ; Left = 488, ; Height = 23, ; Width = 84, ; Caption = "To Clipboard", ; ZOrderSet = 15, ; Name = "commandbutton2" ADD OBJECT lbllastpasswordused AS label WITH ; Caption = "Last Password Used", ; Left = 1, ; Top = 5, ; ForeColor = RGB(0,0,255), ; ZOrderSet = 16, ; Name = "lblLastPasswordUsed" ADD OBJECT cmdshowdetails AS commandbutton WITH ; Top = 195, ; Left = 488, ; Height = 23, ; Width = 84, ; Caption = "Show Details", ; ZOrderSet = 17, ; Name = "cmdShowDetails" ADD OBJECT lblcounter AS label WITH ; FontName = "Consolas", ; FontSize = 12, ; Caption = "1234567890123456789012345678901234567890", ; Left = 121, ; Top = 149, ; ForeColor = RGB(128,128,128), ; ZOrderSet = 18, ; Name = "lblCounter" ADD OBJECT cmdsave AS commandbutton WITH ; Top = 151, ; Left = 488, ; Height = 41, ; Width = 84, ; Picture = "..\..\bmps\save1.bmp", ; Caption = "Save", ; PicturePosition = 1, ; PictureSpacing = 1, ; ZOrderSet = 19, ; Name = "cmdSave" PROCEDURE Load Dodefault() thisform.OldLibraries = Set("Library") set library to (m._encryptdll) additive thisform.PasswordGenerator = Newobject('RandomPasswordGenerator', 'x:\foxv\classes\PasswordGenerator.prg') ENDPROC PROCEDURE Destroy local lcOldLibraries lcOldLibraries = thisform.OldLibraries set library to &lcOldLibraries ENDPROC PROCEDURE txtcurrentpassword.Refresh if not Isnull(thisform.CurrentPasswordPK) if Indexseek(thisform.CurrentPasswordPK, .t., 'Passwords', 'PK') this.Value = Decrypt(Alltrim(Passwords.Password), Evaluate(_ITKEY), 1024) endif endif ENDPROC PROCEDURE txtnewpassword.GotFocus textbox::gotFocus() ENDPROC PROCEDURE txtnewpassword.InteractiveChange Dodefault() thisform.Refresh() ENDPROC PROCEDURE cmdgenerate.Click thisform.txtNewPassword.Value = thisform.PasswordGenerator.newPassword() thisform.Refresh() ENDPROC PROCEDURE commandbutton1.Click _cliptext = Alltrim(thisform.txtCurrentPassword.Value) ENDPROC PROCEDURE txtlastpasswordused.Refresh if not Isnull(thisform.LastPasswordPK) if Indexseek(thisform.LastPasswordPK, .t., 'Passwords', 'PK') this.Value = Decrypt(Alltrim(Passwords.Password), Evaluate(_ITKEY), 1024) endif endif ENDPROC PROCEDURE commandbutton2.Click _cliptext = Alltrim(thisform.txtLastPasswordUsed.Value) ENDPROC PROCEDURE cmdshowdetails.Click local llPasswordOK, lcPassword, lcError, lcPreviousPassword lcError = '' lcPassword = Alltrim(thisform.txtNewPassword.Value) lcPreviousPassword = Alltrim(thisform.txtLastPasswordUsed.Value) llPasswordOK = thisform.PasswordGenerator.CheckPassword(lcPassword, lcPreviousPassword, @lcError) if llPasswordOK Messagebox('The password seems to be OK') else Messagebox(lcError, 16, 'Invalid password: ' + lcPassword) endif ENDPROC PROCEDURE cmdsave.Click insert into Passwords ; ( ; CreatedOn, ; CreatedBy, ; Password ; ) ; values ; ( ; Datetime(), ; _TempID, ; Encrypt(Alltrim(thisform.txtNewPassword.Value), Evaluate(_ITKEY), 1024) ; ) thisform.ctrUnusedPasswords.lstUnusedPasswords.Requery() thisform.ctrUnusedPasswords.lstUnusedPasswords.Refresh() ENDPROC ENDDEFINE DEFINE CLASS ctrCheckListClass as Container ADD OBJECT chkvalidlength AS checkbox WITH ; Top = 3, ; Left = 3, ; Height = 34, ; Width = 359, ; FontName = "Tahoma", ; WordWrap = .T., ; AutoSize = .F., ; Alignment = 0, ; Caption = "The password must be at least 12 characters long but no more than 40 characters long", ; Enabled = .F., ; DisabledForeColor = RGB(0,128,0), ; Name = "chkValidLength" ADD OBJECT chkminimuncharacters AS checkbox WITH ; Top = 38, ; Left = 3, ; Height = 56, ; Width = 359, ; FontName = "Tahoma", ; WordWrap = .T., ; AutoSize = .F., ; Alignment = 0, ; Caption = "The password must contain at least one lower-case alphabetic character, one upper-case alphabetic character and two numeric characters", ; Enabled = .F., ; DisabledForeColor = RGB(0,128,0), ; Name = "chkMinimunCharacters" ADD OBJECT checkbox3 AS checkbox WITH ; Top = 90, ; Left = 3, ; Height = 45, ; Width = 359, ; FontName = "Tahoma", ; WordWrap = .T., ; AutoSize = .F., ; Alignment = 0, ; Caption = "The password must contain only a combination of the following characters: 'a' to 'z', 'A' to 'Z' and '0' to '9'", ; Enabled = .F., ; DisabledForeColor = RGB(0,128,0), ; Name = "checkbox3" ADD OBJECT checkbox4 AS checkbox WITH ; Top = 134, ; Left = 3, ; Height = 46, ; Width = 359, ; FontName = "Tahoma", ; WordWrap = .T., ; AutoSize = .F., ; Alignment = 0, ; Caption = "The first eight characters of the password must contain at least one numeric character and two alpabetic characters", ; Enabled = .F., ; DisabledForeColor = RGB(0,128,0), ; Name = "checkbox4" ADD OBJECT checkbox5 AS checkbox WITH ; Top = 187, ; Left = 3, ; Height = 58, ; Width = 359, ; FontName = "Tahoma", ; WordWrap = .T., ; AutoSize = .F., ; Alignment = 0, ; Caption = "The new password must differ from the previous password and cannot be a reverse or circular shift of the previous password. For this comparison, uppercase and lowercase letters are considered to be equal", ; Enabled = .F., ; DisabledForeColor = RGB(128,128,128), ; Name = "checkbox5" ADD OBJECT checkbox6 AS checkbox WITH ; Top = 252, ; Left = 3, ; Height = 53, ; Width = 359, ; FontName = "Tahoma", ; WordWrap = .T., ; AutoSize = .F., ; Alignment = 0, ; Caption = "The new password must have at least three characters that are different from the old password. For this comparison, uppercase and lowercase letters are considered to be equal", ; Enabled = .F., ; DisabledForeColor = RGB(0,128,0), ; Name = "checkbox6" PROCEDURE chkvalidlength.Refresh this.Value = thisform.PasswordGenerator.CheckLength(thisform.txtNewPassword.Value) this.DisabledForeColor = Iif(this.Value, Rgb(0, 128, 0), Rgb(128, 0, 0)) ENDPROC PROCEDURE chkminimuncharacters.Refresh local lcPassword lcPassword = thisform.txtNewPassword.Value with thisform.PasswordGenerator this.Value = .CheckEnoughLowerCaseLetters(lcPassword) and .CheckEnoughUpperCaseLetters(lcPassword) and .CheckEnoughDigits(lcPassword) endwith this.DisabledForeColor = Iif(this.Value, Rgb(0, 128, 0), Rgb(128, 0, 0)) ENDPROC PROCEDURE checkbox3.Refresh local lcPassword lcPassword = thisform.txtNewPassword.Value with thisform.PasswordGenerator this.Value = .CheckValididyOfChars(lcPassword) endwith this.DisabledForeColor = Iif(this.Value, Rgb(0, 128, 0), Rgb(128, 0, 0)) ENDPROC PROCEDURE checkbox4.Refresh local lcRootPassword lcRootPassword = Left(thisform.txtNewPassword.Value, 8) with thisform.PasswordGenerator this.Value = .CheckEnoughStartingDigits(lcRootPassword) and .CheckEnoughStartingLetters(lcRootPassword) endwith this.DisabledForeColor = Iif(this.Value, Rgb(0, 128, 0), Rgb(128, 0, 0)) ENDPROC PROCEDURE checkbox6.Refresh with thisform.PasswordGenerator this.Value = .CheckDifferentCharactersCount(thisform.txtNewPassword.Value, thisform.txtCurrentPassword.Value) > 3 endwith this.DisabledForeColor = Iif(this.Value, Rgb(0, 128, 0), Rgb(128, 0, 0)) ENDPROC enddefinePassword Manager, rule 7 is missing...
* Rules are: * The FTPS server performs the following validations on the password before accepting it: * 1 · The password must be ASCII character encoded text. * 2 · The password must be at least 12 characters long but no more than 40 characters long. * 3 · The password must contain at least one lower-case alphabetic character, one uppercase * alphabetic character, and two numeric characters. * 4 · The password must contain only a combination of the following characters: ‘a’ to ‘z’, * ‘A’ to ‘Z’, and ‘0’ to ‘9’. * 5 · The first eight characters of the password must contain at least one numeric character * and two alphabetic characters. * 6 · The password cannot be a circular shift of the user id (note that such a password * would be invalid anyway because it would be only nine characters long and therefore * too short). * 7 · The new password must differ from the previous password and cannot be a reverse or * circular shift of the previous password. For this comparison, uppercase letters and * lowercase letters are considered to be equal. * 8 · The new password must have at least three characters that are different from the old * password. For this comparison, uppercase letters and lowercase letters are considered * to be equal.k #define MAX_LENGTH 40 && Rule 2 #define MIN_LENGTH 12 && Rule 2 #define MIN_LOWERCASE 1 && Rule 3 #define MIN_UPPERCASE 1 && Rule 3 #define MIN_DIGITS 2 && Rule 3 #define ROOT_LENGTH 8 && Rule 5 #define ROOT_ALPHACOUNT 2 && Rule 5 #define ROOT_DIGITSCOUNT 2 && Rule 5 #define ALL_DIGITS '0123456789' #define ALL_LETTERS 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #define MIN_DIFFCHARCOUNT 3 * Commented code is for testing purposes only *!* local lnLength *!* loRPG = Createobject('RandomPasswordGenerator') *!* clear *!* *? 'Len' + Chr(9) + 'Digits' + Chr(9) + 'Lower' + Chr(9) + 'Upper' + Chr(9) + 'Root N' + Chr(9) + 'Root Char' + Chr(13)+Chr(10) *!* for i =1 to 25 *!* lcPassword = loRPG.newPassword() *!* lcError = '' *!* llPasswordOK = loRPG.CheckPassword(lcPassword, @lcError) *!* if llPasswordOK *!* ? lcPassword, 'OK' *!* else *!* Messagebox(lcError, 16, 'Invalid password: ' + lcPassword) *!* endif *!* next i *!* lcError = '' *!* lcPassword = Inputbox('Enter the password') *!* llPasswordOK = loRPG.CheckPassword(lcPassword, @lcError) *!* if llPasswordOK *!* ? lcPassword, 'OK' *!* else *!* Messagebox(lcError, 16, 'Invalid password: ' + lcPassword) *!* endif *!* return define class RandomPasswordGenerator as Session protected Random as Object function Init() as Boolean this.Random = Newobject('RandomFunctions') return .t. endfunc procedure Destroy() as VOID this.Random = null return null endproc function newPassword() as String local lnLength, lnLowerCaseCount, lnUpperCaseCount, lnDigitsCount, lnRootNumCount, lnRootCharCount local lcPassword, lcRoot lnLength = this.Random.getRandomInteger(MIN_LENGTH, MAX_LENGTH) lnDigitsCount = this.Random.getRandomInteger(MIN_DIGITS, lnLength - MIN_UPPERCASE - MIN_LOWERCASE) lnLowerCaseCount = this.Random.getRandomInteger(MIN_LOWERCASE, lnLength - lnDigitsCount - MIN_UPPERCASE) lnUpperCaseCount = lnLength - lnDigitsCount - lnLowerCaseCount lnRootNumCount = this.Random.getRandomInteger(ROOT_DIGITSCOUNT, Min(lnDigitsCount, ROOT_LENGTH - ROOT_DIGITSCOUNT)) lnRootCharCount = ROOT_LENGTH - lnRootNumCount * Get the first 8 Characters, first the digits then the chars, later this will be shuffled lcUnshuffledRoot = '' * Getting the digits for lnPosition = 1 to lnRootNumCount lcUnshuffledRoot = lcUnshuffledRoot + this.Random.getRandomDigit() next lnPosition * Getting the chars, we need to keep a count on how many UpperCase and LowerCase chars are used for lnPosition = 1 to lnRootCharCount do case case lnUpperCaseCount > 0 and lnLowerCaseCount > 0 lcChar = this.Random.getRandomLetter() case lnUpperCaseCount > 0 lcChar = this.Random.getRandomUpperCase() case lnLowerCaseCount > 0 lcChar = this.Random.getRandomLowerCase() otherwise **** OOOOPSSS. Take a deep breath and think about this. endcase lcUnshuffledRoot = lcUnshuffledRoot + lcChar if Isupper(lcChar) lnUpperCaseCount = lnUpperCaseCount - 1 else lnLowerCaseCount = lnLowerCaseCount - 1 endif next lnPosition lcRoot = this.Random.StringShuffle(lcUnshuffledRoot) lnDigitsCount = lnDigitsCount - lnRootNumCount lcPassword = '' for lnPosition = 1 to lnDigitsCount lcPassword = lcPassword + this.Random.getRandomDigit() next lnPosition for lnPosition = 1 to lnUpperCaseCount lcPassword = lcPassword + this.Random.getRandomUpperCase() next lnPosition for lnPosition = 1 to lnLowerCaseCount lcPassword = lcPassword + this.Random.getRandomLowerCase() next lnPosition return lcRoot + this.Random.StringShuffle(lcPassword) endfunc function CheckLength(tcPassword as String) as Boolean return Between(Len(tcPassword), MIN_LENGTH, MAX_LENGTH) endfunc function CheckValididyOfChars(tcPassword as String) as Boolean local lcInvalidCharacters lcInvalidCharacters = Chrtran(Upper(tcPassword), ALL_DIGITS + ALL_LETTERS, '') return Len(lcInvalidCharacters) = 0 endfunc function CheckEnoughStartingDigits(tcPasswordRoot as String) as Boolean local lnDigitsRoot lnDigitsRoot = Len(tcPasswordRoot) - Len(Chrtran(tcPasswordRoot, ALL_DIGITS, '')) return lnDigitsRoot >= ROOT_DIGITSCOUNT endfunc function CheckEnoughStartingLetters(tcPasswordRoot as String) as Boolean local lnLettersRoot lnLettersRoot = Len(tcPasswordRoot) - Len(Chrtran(tcPasswordRoot, ALL_DIGITS, '')) return lnLettersRoot >= ROOT_ALPHACOUNT endfunc function CheckEnoughDigits(tcPassword as String) as Boolean local lnDigits lnDigits = Len(tcPassword) - Len(Chrtran(tcPassword, ALL_DIGITS, '')) return lnDigits >= MIN_DIGITS endfunc function CheckEnoughLowerCaseLetters(tcPassword as String) as Boolean local lnTotalLowerCase lnTotalLowerCase = Len(tcPassword) - Len(Chrtran(tcPassword, Lower(ALL_LETTERS), '')) return lnTotalLowerCase >= MIN_LOWERCASE endfunc function CheckEnoughUpperCaseLetters(tcPassword as String) as Boolean local lnTotalUpperCase lnTotalUpperCase = Len(tcPassword) - Len(Chrtran(tcPassword, ALL_LETTERS, '')) return lnTotalUpperCase >= MIN_UPPERCASE endfunc function CheckDifferentCharactersCount(tcCurrentPassword as String, tcPreviousPassword as String) as Integer return Len(Chrtran(Upper(tcCurrentPassword), Upper(tcPreviousPassword), '')) endfunc function CheckPassword(tcPassword as String, tcPreviousPassword, tcResult as String) as Boolean local lcRoot, lcResult, llSetResult, lcInvalidCharacters, lcPreviousPassword, lcNewCharacters llSetResult = Pcount() = 3 lcResult = '' lcPreviousPassword = Iif(Vartype(tcPreviousPassword) = 'C', tcPreviousPassword, '') * Check the length if not Between(Len(tcPassword), MIN_LENGTH, MAX_LENGTH) lcResult = 'Invalid Length. Current value: ' + Transform(Len(tcPassword)) endif lcInvalidCharacters = Chrtran(tcPassword, ALL_DIGITS + ALL_LETTERS + Lower(ALL_LETTERS), '') if Len(lcInvalidCharacters) # 0 lcResult = lcResult + Iif(Empty(lcResult), '', Chr(13)) + 'The password has invalid characters: [' + lcInvalidCharacters + ']' endif lcRoot = Left(tcPassword, ROOT_LENGTH) lnDigitsRoot = Len(lcRoot) - Len(Chrtran(lcRoot, ALL_DIGITS, '')) lnUpperCRoot = Len(lcRoot) - Len(Chrtran(lcRoot, ALL_LETTERS, '')) - lnDigitsRoot lnLowerCRoot = Len(lcRoot) - lnDigitsRoot - lnUpperCRoot * At least two MIN_DIGITS digits at the beginning if lnDigitsRoot < MIN_DIGITS lcResult = lcResult + Iif(Empty(lcResult), '', Chr(13)) + 'Insuficient digits at start: ' + Transform(lnDigitsRoot) endif * At least two ROOT_ALPHACOUNT chars within the firt ROOT_ALPHACOUNT characters if lnUpperCRoot + lnLowerCRoot < ROOT_ALPHACOUNT lcResult = lcResult + Iif(Empty(lcResult), '', Chr(13)) + 'Insuficient letters at start: ' + Transform(lnUpperCRoot + lnLowerCRoot) endif * At Lest MIN_LOWERCASE lower case char lnTotalLowerCase = Len(tcPassword) - Len(Chrtran(tcPassword, Lower(ALL_LETTERS), '')) if lnTotalLowerCase < MIN_LOWERCASE lcResult = lcResult + Iif(Empty(lcResult), '', Chr(13)) + 'Insuficient lower case letters: ' + Transform(lnTotalLowerCase) endif * At Lest MIN_UPPERCASE lower case char lnTotalUpperCase = Len(tcPassword) - Len(Chrtran(tcPassword, ALL_LETTERS, '')) if lnTotalUpperCase < MIN_UPPERCASE lcResult = lcResult + Iif(Empty(lcResult), '', Chr(13)) + 'Insuficient upper case letters: ' + Transform(lnTotalUpperCase) endif * Check how many characters are different lcNewCharacters = Chrtran(Upper(tcPassword), Upper(lcPreviousPassword), '') if Len(lcNewCharacters) < MIN_DIFFCHARCOUNT lcResult = lcResult + Iif(Empty(lcResult), '', Chr(13)) + 'Insuficient new characters.' + Chr(13) + Chr(9) + 'Try using some from this group: [' + Chrtran(ALL_DIGITS + ALL_LETTERS + Lower(ALL_LETTERS), lcPreviousPassword, '') + ']' endif if llSetResult tcResult = lcResult endif return Empty(lcResult) endfunc enddefineRandom generator class:
******************************************************************************************************************* * * * Collection of functions to return random numbers or letters * * * * Methods: * * * * ReSeed - Set a new seed for the internal random number generator * * getRandomDigit - Returns a random digit as character ('0'..'9') * * getRandomLowerCase - Returns a random lower case letter ('a'..'z') * * getRandomUpperCase - Returns a random upper case letter ('A'..'Z') * * getRandomLetter - Returns a random upper or lower case letter ('A'..'Z'|'a'..'z') * * * * * ******************************************************************************************************************* define class RandomFunctions as Session function init() as Boolean Rand(-1) return .t. endfunc procedure ReSeed(tnNewSeed as Number) as VOID Rand(Iif(Vartype(tnNewSeed) = 'N', tnNewSeed, -1)) return null endproc function getRandomDigit() as Character return this.getRandomAscii(Asc('0'), Asc('9')) endfunc function getRandomLowerCase() as Character return this.getRandomAscii(Asc('a'), Asc('z')) endfunc function getRandomUpperCase() as Character return this.getRandomAscii(Asc('A'), Asc('Z')) endfunc function getRandomLetter() as Character return Iif(Rand() < 0.5, this.getRandomUpperCase(), this.getRandomLowerCase()) endfunc function getRandomNumber() as Number return Rand() endfunc function getRandomInteger(tnLowerLimit as Integer, tnUpperLimit as Integer) as Integer local lnLowerLimit, lnUpperLimit, lnAux lnLowerLimit = Iif(Vartype(tnLowerLimit) # 'N' or Vartype(tnUpperLimit) # 'N', 0, Int(tnLowerLimit)) lnUpperLimit = Iif(Vartype(tnUpperLimit) # 'N', Iif(Vartype(tnLowerLimit) # 'N', 1, Int(tnLowerLimit)), Int(tnUpperLimit)) if lnUpperLimit < lnLowerLimit lnAux = lnLowerLimit lnLowerLimit = lnUpperLimit lnUpperLimit = lnAux endif * Default will be getRandomInteger(0, 1) * getRandom(5) = getRandomInteger(0, 5) return Int(Rand() * (lnUpperLimit - lnLowerLimit + 1)) + lnLowerLimit endfunc function getRandomAscii(tnLowerLimit as Integer, tnUpperLimit as Integer) as Character local lnLowerLimit, lnUpperLimit * Default will be getRandomAscii(0, 255) lnLowerLimit = Iif(Vartype(tnLowerLimit) # 'N' or Vartype(tnUpperLimit) # 'N', 0, Int(tnLowerLimit)) lnUpperLimit = Iif(Vartype(tnUpperLimit) # 'N', Iif(Vartype(tnLowerLimit) # 'N', 255, Int(tnLowerLimit)), Int(tnUpperLimit)) return Chr(this.getRandomInteger(lnLowerLimit, lnUpperLimit)) endfunc * http://en.wikipedia.org/wiki/Fisher-Yates_shuffle function StringShuffle(tcUnshuffled as String) as String local lnChars, lnChar, lcShuffled, lnPosition, lnRandomChar, lcTempChar lnChars = Len(tcUnshuffled) lcShuffled = tcUnshuffled for lnChar = lnChars to 1 step -1 lnPosition = this.getRandomInteger(1, lnChar) lcRandomChar = Substr(lcShuffled, lnPosition, 1) lcTempChar = Substr(lcShuffled, lnChar, 1) lcShuffled = Stuff(lcShuffled, lnChar, 1, lcRandomChar) lcShuffled = Stuff(lcShuffled, lnPosition, 1, lcTempChar) next lnChar return lcShuffled endfunc enddefineHopefully this mess can help you, let me know if something is missing other than the things I mentioned.