'Generate random password in Delphi

I have a following function to generate random passwords:

function GeneratePassword(ALength: Integer; Mode: TPasswordMode): string;
const
  cLower   = 'abcdefghijklmnopqrstuvwxyz';
  cUpper   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  cNumbers = '0123456789';
  cExtra   = '_.';
var
  i : Integer;
  S : string;
  iM: BYTE;
begin
  if Mode = [] then Exit;
  i := 0;
  Randomize;
  while (i < ALength)  do
  begin
    iM := RANDOM(4);
    case iM of
      0: if (pmLower in Mode) then begin
            S := S + cLower[1+RANDOM(Length(cLower))];
           Inc(i);
          end;
     1: if (pmUpper in Mode) then begin
           S := S + cUpper[1+RANDOM(Length(cUpper))];
          Inc(i);
         end;
      2: if (pmNumbers in Mode) then begin
           S := S + cNumbers[1+RANDOM(Length(cNumbers))];
           Inc(i);
          end;
      3: if (pmExtra in Mode) then begin
           S := S + cExtra[1+RANDOM(Length(cExtra))];
           Inc(i);
         end;
    end;
  end;
  Result := S;
end;

How to make this function so that a capital letter and a special character appear only once, but always? Sometimes there is no capital letter or special character when I'm generating passwords.



Solution 1:[1]

To be sure to have one special char and one uppercase you can do that :

function GeneratePassword(ALength: Integer; Mode: TPasswordModes): string;
const
  cLower   = 'abcdefghijklmnopqrstuvwxyz';
  cUpper   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  cNumbers = '0123456789';
  cExtra   = '_.';
var
  iM: Byte;
  i: integer;
begin
  if Mode = [] then Exit;

  Result := '';
  i := 0;

  if pmUpper in Mode then
    Inc(i);

  if pmExtra in Mode then
    Inc(i);

  // add lower case and/or number
  while Result.Length < (ALength - i)  do
  begin
    iM := Random(2);
    case iM of
      0: if (pmLower in Mode) then begin
           Result := Result + cLower[1 + Random(Length(cLower))];
         end;
      1: if (pmNumbers in Mode) then begin
           Result := Result + cNumbers[1 + Random(Length(cNumbers))];
         end;
    end;
  end;

  // add uppercase and/or extra
  if i > 0 then
  begin
    if pmUpper in Mode then
      Result := Result.Insert(1 + Random(Length(Result)), cUpper[1 + Random(Length(cUpper))]);

    if pmExtra in Mode then
      Result := Result.Insert(1 + Random(Length(Result)), cExtra[1 + Random(Length(cExtra))]);
  end;
end;

Solution 2:[2]

type
  TPasswordMode = (pmLower, pmUpper, pmNumbers, pmExtra);
  TPasswordModes = set of TPasswordMode;

implementation

function GeneratePassword(ALength: Integer; Mode: TPasswordModes): string;
const
  cLower   = 'abcdefghijklmnopqrstuvwxyz';
  cUpper   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  cNumbers = '0123456789';
  cExtra   = '_.';
var
  i : Integer;
  S : string;
  iM: BYTE;
begin
  if Mode = [] then Exit;
  i := 0;
  Randomize;
  while (i < ALength)  do
  begin
    iM := RANDOM(4);
    case iM of
      0: if (pmLower in Mode) then begin
           S := S + cLower[1+RANDOM(Length(cLower))];
           Inc(i);
         end;
      1: if (pmUpper in Mode) then begin
           S := S + cUpper[1+RANDOM(Length(cUpper))];
           Inc(i);
           Mode := Mode - [pmUpper]; // This I added
         end;
      2: if (pmNumbers in Mode) then begin
           S := S + cNumbers[1+RANDOM(Length(cNumbers))];
           Inc(i);
         end;
      3: if (pmExtra in Mode) then begin
           S := S + cExtra[1+RANDOM(Length(cExtra))];
           Inc(i);
           Mode := Mode - [pmExtra];  // This I added
         end;
    end;
  end;
  Result := S;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(GeneratePassword(10,[pmLower,pmUpper,pmNumbers,pmExtra]));
end;

This is not a complete solution but with this you will at least remove Upper and Extra from the requirements as soon as they get taken. You now check in the end if they ever were ever added if required and then add them if so required.


Edit:

I was in a hurry when I typed the above. You just need to check in the end if the generated password contains an Upper and Extra character. If not, you still need to add them as that was one of your requirements.

Solution 3:[3]

Here is example that first makes sure all extra modes are filled and the rest. It prefills Result with spaces and then replaces with random chars until all spaces are replaced.

function GetRandomEmptyPos(const aStr: string): integer; inline;
begin
  // find random empty position
  repeat
    Result := Random(Length(aStr)) + 1;
  until aStr[Result] = ' ';
end;

function GeneratePassword2(aLength: Integer; aModes: TPasswordModes): string;
const
  cLower   = 'abcdefghijklmnopqrstuvwxyz';
  cUpper   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  cNumbers = '0123456789';
  cExtra   = '_.';
var
  i,vPos: integer;
  vMode: TPasswordMode;
begin
  if (aLength = 0) or (aModes = []) then Exit;
  Randomize;

  // Prefill Result with empty spaces
  Result := StringOfChar(' ', aLength);

  // Add extra characters at random places
  for vMode in aModes do
  begin
    vPos := GetRandomEmptyPos(Result);
    case vMode of
      pmLower:    Result[vPos] := cLower[Random(Length(cLower)) + 1];
      pmUpper:    Result[vPos] := cUpper[Random(Length(cUpper)) + 1];
      pmNumbers:  Result[vPos] := cNumbers[Random(Length(cNumbers)) + 1];
      pmExtra:    Result[vPos] := cExtra[Random(Length(cExtra)) + 1];
    end;
  end;

  // Add random char on emtpy spaces
  for i := 1 to Result.Length do
    if Result[i] = ' ' then
      Result[i] := String(cLower + cNumbers)[Random(Length(cLower) + Length(cNumbers)) + 1];
end;

Solution 4:[4]

unrefined code but maybe it can be useful ...

function RandomPassword(PLen: Integer): string;
  var
    strBase: string;
    strUpper: string;
    strSpecial: string;
    strRecombine: string;
  begin
    strRecombine:='';
    Result := '';
    Randomize;
    //string with all possible chars
    strBase   := 'abcdefghijklmnopqrstuvwxyz1234567890';
    strUpper:='ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    strSpecial:='@!_';
    // Start Random
    strRecombine:= strUpper[Random(Length(strUpper)) + 1];
    Result:=strRecombine;
    strRecombine:= strSpecial[Random(Length(strSpecial))+1];
    repeat
      Result := Result +  strBase[Random(Length(strBase)) + 1];
    until (Length(Result) = PLen);
      RandomRange(2, Length(strBase)); 
      Result[RandomRange(2, PLen)]:=strRecombine[1];
  //result:=Result+strRecombine;
end;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Tom Brunberg
Solution 3
Solution 4 Tyler2P