encryption_utilities_20220701.0.0_1b883b94/src/tf_crypt.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
-- Encryptuion/dectryption of files with the Threefish cipher for blocks of 256 bits (Threefish-256)
-- Copyright (C) 2022 by PragmAda Software Engineering
-- Released under the terms of the GPL license version 3; see https://opensource.org/licenses

with Ada.Command_Line;
with Ada.Directories;
with Ada.Numerics.Discrete_Random;
with Ada.Sequential_IO;
with Ada.Text_IO;
with Ada.Unchecked_Conversion;
with Password_Line;
with PragmARC.Encryption.Threefish.Block_256;

procedure TF_Crypt is
   use PragmARC.Encryption;

   package Byte_IO is new Ada.Sequential_IO (Element_Type => Threefish.Byte);

   Bytes_Per_Block  : constant := 32;
   Bytes_Per_Couple : constant := 16;

   subtype Block_As_Bytes is Threefish.Block_256.Block_As_Bytes;

   subtype Block_As_String  is String (1 .. Bytes_Per_Block);
   subtype Couple_As_String is String (1 .. Bytes_Per_Couple);

   function To_Block  (Source : Block_As_String)  return Threefish.Block_256.Block;
   function To_Couple (Source : Couple_As_String) return Threefish.Couple;

   procedure Usage; -- Displays usage instructions

   procedure Encrypt (KS: in Threefish.Block_256.Key_Schedule_Handle; Name : in String); -- Encrypts Name using KS
   procedure Decrypt (KS: in Threefish.Block_256.Key_Schedule_Handle; Name : in String); -- Decrypts Name using KS

   function To_Block  (Source : Block_As_String)  return Threefish.Block_256.Block is
      function To_List is new Ada.Unchecked_Conversion (Source => Block_As_String, Target => Block_As_Bytes);

      List : constant Block_As_Bytes := To_List (Source);
   begin -- To_Block
      return Threefish.Block_256.Block_From_Bytes (List);
   end To_Block;

   function To_Couple (Source : Couple_As_String) return Threefish.Couple is
      subtype Couple_As_Bytes is Threefish.Byte_List (1 .. Source'Length);

      function To_List is new Ada.Unchecked_Conversion (Source => Couple_As_String, Target => Couple_As_Bytes);

      List : constant Couple_As_Bytes := To_List (Source);

      Result : Threefish.Couple;
   begin -- To_Couple
      Result (Result'First) := Threefish.Word_From_Bytes (List (List'First .. List'First + Threefish.Word_As_Bytes'Length - 1) );
      Result (Result'Last)  := Threefish.Word_From_Bytes (List (List'First + Threefish.Word_As_Bytes'Length .. List'Last) );

      return Result;
   end To_Couple;

   procedure Usage is
      -- Empty
   begin -- Usage
      Ada.Text_IO.Put_Line (Item => "usage: tf_crypt [-d] <file name>");
      Ada.Text_IO.Put_Line (Item => "   prompts the user to enter a passphrase");
      Ada.Text_IO.Put_Line (Item => "   if -d given, decrypts <file name>, else encrypts <file name>");
      Ada.Text_IO.Put_Line (Item => "   the passphrase should be 48 characters, but it will be truncated");
      Ada.Text_IO.Put_Line (Item => "      or padded with X to achieve that length");
      Ada.Text_IO.Put_Line (Item => "   the output file when encrypting, will have the same name as the input, with .tfe appended");
      Ada.Text_IO.Put_Line (Item => "   when decrypting,");
      Ada.Text_IO.Put_Line (Item => "      if <file name> ends with .tfe, the output file will be the input with .tfe removed");
      Ada.Text_IO.Put_Line (Item => "      else the output file will be the same name as the input, with .tfd appended");
   end Usage;

   procedure Encrypt (KS: in Threefish.Block_256.Key_Schedule_Handle; Name : in String) is
      Input      : Byte_IO.File_Type;
      Output     : Byte_IO.File_Type;
      Length     : Threefish.Word_As_Bytes;
      Byte_Block : Block_As_Bytes;
      Word_Block : Threefish.Block_256.Block;
      Size       : Threefish.Word;
      Written    : Threefish.Word := 0;

      use type Threefish.Word;
   begin -- Encrypt
      Byte_IO.Open (File => Input, Mode => Byte_IO.In_File, Name => Name);
      Byte_IO.Create (File => Output, Name => Name & ".tfe");
      Size := Threefish.Word (Ada.Directories.Size (Name) );
      Length := Threefish.Bytes_From_Word (Size);

      Write_Length : for I in Length'Range loop
         Byte_IO.Write (File => Output, Item => Length (I) );
      end loop Write_Length;

      All_Blocks : loop
         exit All_Blocks when Byte_IO.End_Of_File (Input);

         if Size - Written < Bytes_Per_Block then -- Last block has unused bytes; fill them with random values
            Random_Fill : declare
               package Random is new Ada.Numerics.Discrete_Random (Result_Subtype => Threefish.Byte);

               Gen : Random.Generator;
            begin -- Random_Fill
               Random.Reset (Gen => Gen);
               Byte_Block := (others => Random.Random (Gen) );
            end Random_Fill;
         end if;

         One_Block : for I in Byte_Block'Range loop
            exit One_Block when Byte_IO.End_Of_File (Input);

            Byte_IO.Read (File => Input, Item => Byte_Block (I) );
         end loop One_Block;

         Word_Block := Threefish.Block_256.Block_From_Bytes (Byte_Block);
         Threefish.Block_256.Encrypt (Key_Schedule => KS, Text => Word_Block);
         Byte_Block := Threefish.Block_256.Bytes_From_Block (Word_Block);

         Write_Block : for I in Byte_Block'Range loop
            Byte_IO.Write (File => Output, Item => Byte_Block (I) );
            Written := Written + 1;
         end loop Write_Block;
      end loop All_Blocks;

      Byte_IO.Close (File => Input);
      Byte_IO.Close (File => Output);
   end Encrypt;

   procedure Decrypt (KS: in Threefish.Block_256.Key_Schedule_Handle; Name : in String) is
      Input      : Byte_IO.File_Type;
      Output     : Byte_IO.File_Type;
      Len_Bytes  : Threefish.Word_As_Bytes;
      Length     : Threefish.Word;
      Count      : Threefish.Word := 0;
      Byte_Block : Block_As_Bytes;
      Word_Block : Threefish.Block_256.Block;

      use type Threefish.Word;
   begin -- Decrypt
      Byte_IO.Open (File => Input, Mode => Byte_IO.In_File, Name => Name);

      if Name'Length > 4 and then Name (Name'Last - 3 .. Name'Last) = ".tfe" then
         Byte_IO.Create (File => Output, Name => Name (Name'First .. Name'Last - 4) );
      else
         Byte_IO.Create (File => Output, Name => Name & ".tfd");
      end if;

      Read_Length : for I in Len_Bytes'Range loop
         Byte_IO.Read (File => Input, Item => Len_Bytes (I) );
      end loop Read_Length;

      Length := Threefish.Word_From_Bytes (Len_Bytes);

      All_Blocks : loop
         exit All_Blocks when Byte_IO.End_Of_File (Input);

         Read_Block : for I in Byte_Block'Range loop
            Byte_IO.Read (File => Input, Item => Byte_Block (I) );
         end loop Read_Block;

         Word_Block := Threefish.Block_256.Block_From_Bytes (Byte_Block);
         Threefish.Block_256.Decrypt (Key_Schedule => KS, Text => Word_Block);
         Byte_Block := Threefish.Block_256.Bytes_From_Block (Word_Block);

         Write_Bytes : for I in Byte_Block'Range loop
            exit Write_Bytes when Count >= Length;

            Byte_IO.Write (File => Output, Item => Byte_Block (I) );
            Count := Count + 1;
         end loop Write_Bytes;
      end loop All_Blocks;

      Byte_IO.Close (File => Input);
      Byte_IO.Close (File => Output);
   end Decrypt;

   Encrypting : Boolean := True;
   Name_Arg   : Positive := 1;
   Key        : Threefish.Block_256.Block;
   Tweak      : Threefish.Couple;
   KS         : Threefish.Block_256.Key_Schedule_Handle;

   use type Ada.Directories.File_Kind;
begin -- TF_Crypt
   if Ada.Command_Line.Argument_Count = 0 then
      Usage;

      return;
   end if;

   if Ada.Command_Line.Argument_Count >= 2 and then Ada.Command_Line.Argument (1) = "-d" then
      Encrypting := False;
      Name_Arg := 2;
   end if;

   if not Ada.Directories.Exists (Ada.Command_Line.Argument (Name_Arg) ) or else
      Ada.Directories.Kind (Ada.Command_Line.Argument (Name_Arg) ) /= Ada.Directories.Ordinary_File
   then
      Ada.Text_IO.Put_Line (Item => Ada.Command_Line.Argument (Name_Arg) & " cannot be read");
      Usage;

      return;
   end if;

   Ada.Text_IO.Put_Line (Item => "Enter passphrase:");

   Get_Passphrase : declare
      Line : constant String := Password_Line;
      Pass : constant String := Line & (Line'Last + 1 .. Line'First + Bytes_Per_Block + Bytes_Per_Couple - 1 => 'X');
   begin -- Get_Passphrase
      Key := To_Block (Pass (Pass'First .. Pass'First + Bytes_Per_Block - 1) );
      Tweak := To_Couple (Pass (Pass'First + Bytes_Per_Block .. Pass'First + Bytes_Per_Block + Bytes_Per_Couple - 1) );
   end Get_Passphrase;

   Threefish.Block_256.Create_Key_Schedule (Key => Key, Tweak => Tweak, Key_Schedule => KS);

   if Encrypting then
      Encrypt (KS => KS, Name => Ada.Command_Line.Argument (Name_Arg) );
   else
      Decrypt (KS => KS, Name => Ada.Command_Line.Argument (Name_Arg) );
   end if;
end TF_Crypt;