gnoga_2.1.2_5f127c56/demo/password_gen/password_generation.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
-- Password_Generation: A package to contain the common logic used by Password_Gen and pwdgen
-- Copyright (C) 2017 by PragmAda Software Engineering.  All rights reserved.
-- **************************************************************************
--
-- Generation of secure passwords from a domain, master password, and symbol
--
-- V1.0  2017 Nov 15     Move password-generation logic into a package
--
with Ada.Characters.Handling;
with GNAT.SHA512;
with PragmARC.Unbounded_Integers;

package body Password_Generation is
   function Generate
     (Domain      : String;
      Master      : String;
      Length      : Length_Value;
      Symbol      : String;
      Hash_Symbol : Boolean := True)
      return String
   is
      subtype Digit is Character range '0' .. '9';
      subtype Lower is Character range 'a' .. 'z';
      subtype Upper is Character range 'A' .. 'Z';

      function Digest
        (Source : String)
         return String;
      -- Perform an SHA512 digest on Source and convert the result to base 36

      function Digest
        (Source : String)
         return String
      is
         -- Empty
      begin -- Digest
         return
           PragmARC.Unbounded_Integers.Image
             (PragmARC.Unbounded_Integers.Value ("16#" & GNAT.SHA512.Digest (Source) & '#'), Base => 36);
      end Digest;

      Hash_Domain : constant String := Ada.Characters.Handling.To_Lower (Domain);

      Salted_Input : constant String := ':' & Hash_Domain & ':' & Master & ':';

      Digit_1_Index : Natural  := 0;
      Digit_1       : Natural  := 0;
      Digit_1_Rem   : Natural;
      Hash_Length   : Positive := Length; -- The length of the hash (Result) to use in the password
      Letter_Count  : Natural  := 0;
      Has_Lower     : Boolean  := False;
      Has_Upper     : Boolean  := False;
      Result        : String   :=
        Digest (Salted_Input & Integer'Image (Length) & ':' & (if Hash_Symbol then Symbol & ':' else ""));
   begin -- Generate
      if Symbol /= No_Symbol then
         Hash_Length := Hash_Length - 1;
      end if;

      Find_Digit_1 :
      for I in 1 .. Hash_Length loop
         if Result (I) in Digit then
            Digit_1_Index := I;
            Digit_1       := Integer'Value (Result (I .. I));

            exit Find_Digit_1;
         end if;
      end loop Find_Digit_1;

      if Digit_1_Index = 0 then -- Needs a digit
         Digit_1_Index := 1;
         Digit_1       := 0;
         Result (1)    := '0';
      end if;

      Digit_1_Rem := Digit_1 rem 2;

      Lower_Some :
      for I in 1 .. Hash_Length loop
         if Result (I) in Upper then
            Letter_Count := Letter_Count + 1;

            if Letter_Count rem 2 = Digit_1_Rem then
               Result (I) := Ada.Characters.Handling.To_Lower (Result (I));
            end if;
         end if;
      end loop Lower_Some;

      Check_Letters :
      for I in 1 .. Hash_Length loop
         if Result (I) in Lower then
            Has_Lower := True;
         end if;

         if Result (I) in Upper then
            Has_Upper := True;
         end if;
      end loop Check_Letters;

      -- Hash_Length >= 7, so if not Has_Lower, then Result (1 .. Hash_Length) has at most 1 letter, which was not lowered by
      -- Lower_Some
      -- if not Has_Upper, then Result (1 .. Hash_Length) has at most 1 letter, which was lowered by Lower_Some
      -- if both not Has_Lower and not Has_Upper, then Result (1 .. Hash_Length) is all digits
      -- In all of these cases, there are digits afer Digit_1_Index that can be replaced by the desired kind of letter

      if not Has_Lower then -- Needs a lower-case letter
         Find_Digit_Lower :
         for I in Digit_1_Index + 1 .. Hash_Length loop
            if Result (I) in Digit then
               Result (I) := 'a';

               exit Find_Digit_Lower;
            end if;
         end loop Find_Digit_Lower;
      end if;

      if not Has_Upper then -- Needs an upper-case letter
         Find_Digit_Upper :
         for I in Digit_1_Index + 1 .. Hash_Length loop
            if Result (I) in Digit then
               Result (I) := 'A';

               exit Find_Digit_Upper;
            end if;
         end loop Find_Digit_Upper;
      end if;

      if Symbol = No_Symbol then
         return Result (1 .. Hash_Length);
      end if;

      return
        Result (1 .. Integer'Min (Digit_1, Hash_Length)) &
        (if Symbol = Auto_Symbol then (1 => Symbol_Set (Digit_1)) else Symbol) & Result (Digit_1 + 1 .. Hash_Length);
   end Generate;
end Password_Generation;