2
0
Эх сурвалжийг харах

Add support for full encryption which encrypts everything and requires the password to be specified on the command line. Todo: cleanup existing and new code.

Martijn Laan 1 сар өмнө
parent
commit
1f43ff5e24

+ 2 - 1
Projects/ISCmplr.dpr

@@ -52,7 +52,8 @@ uses
   PBKDF2 in '..\Components\PBKDF2.pas',
   ECDSA in '..\Components\ECDSA.pas',
   ISSigFunc in '..\Components\ISSigFunc.pas',
-  StringScanner in '..\Components\StringScanner.pas';
+  StringScanner in '..\Components\StringScanner.pas',
+  Shared.EncryptionFunc in 'Src\Shared.EncryptionFunc.pas';
 
 {$IMAGEBASE $00800000}
 {$SETPEOSVERSION 6.1}

+ 1 - 0
Projects/ISCmplr.dproj

@@ -121,6 +121,7 @@
         <DCCReference Include="..\Components\ECDSA.pas"/>
         <DCCReference Include="..\Components\ISSigFunc.pas"/>
         <DCCReference Include="..\Components\StringScanner.pas"/>
+        <DCCReference Include="Src\Shared.EncryptionFunc.pas"/>
         <BuildConfiguration Include="Base">
             <Key>Base</Key>
         </BuildConfiguration>

+ 2 - 1
Projects/Setup.dpr

@@ -101,7 +101,8 @@ uses
   ISSigFunc in '..\Components\ISSigFunc.pas',
   StringScanner in '..\Components\StringScanner.pas',
   Compression.SevenZipDLLDecoder in 'Src\Compression.SevenZipDLLDecoder.pas',
-  Compression.SevenZipDLLDecoder.Interfaces in 'Src\Compression.SevenZipDLLDecoder.Interfaces.pas';
+  Compression.SevenZipDLLDecoder.Interfaces in 'Src\Compression.SevenZipDLLDecoder.Interfaces.pas',
+  Shared.EncryptionFunc in 'Src\Shared.EncryptionFunc.pas';
 
 {$SETPEOSVERSION 6.1}
 {$SETPESUBSYSVERSION 6.1}

+ 1 - 0
Projects/Setup.dproj

@@ -175,6 +175,7 @@
         <DCCReference Include="..\Components\StringScanner.pas"/>
         <DCCReference Include="Src\Compression.SevenZipDLLDecoder.pas"/>
         <DCCReference Include="Src\Compression.SevenZipDLLDecoder.Interfaces.pas"/>
+        <DCCReference Include="Src\Shared.EncryptionFunc.pas"/>
         <BuildConfiguration Include="Base">
             <Key>Base</Key>
         </BuildConfiguration>

+ 29 - 3
Projects/SetupLdr.dpr

@@ -2,7 +2,7 @@ program SetupLdr;
 
 {
   Inno Setup
-  Copyright (C) 1997-2024 Jordan Russell
+  Copyright (C) 1997-2025 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -28,7 +28,10 @@ uses
   Shared.Int64Em in 'Src\Shared.Int64Em.pas',
   SHA256 in '..\Components\SHA256.pas',
   SetupLdrAndSetup.RedirFunc in 'Src\SetupLdrAndSetup.RedirFunc.pas',
-  Shared.VerInfoFunc in 'Src\Shared.VerInfoFunc.pas';
+  Shared.VerInfoFunc in 'Src\Shared.VerInfoFunc.pas',
+  Shared.EncryptionFunc in 'Src\Shared.EncryptionFunc.pas',
+  ChaCha20 in '..\Components\ChaCha20.pas',
+  PBKDF2 in '..\Components\PBKDF2.pas';
 
 {$SETPEOSVERSION 6.1}
 {$SETPESUBSYSVERSION 6.1}
@@ -73,7 +76,7 @@ type
 var
   InitShowHelp: Boolean = False;
   InitDisableStartupPrompt: Boolean = False;
-  InitLang: String;
+  InitLang, InitPassword: String;
   ActiveLanguage: Integer = -1;
   PendingNewLanguage: Integer = -1;
   SetupHeader: TSetupHeader;
@@ -96,6 +99,8 @@ begin
       InitDisableStartupPrompt := True
     else if CompareText(Copy(Name, 1, 6), '/Lang=') = 0 then
       InitLang := Copy(Name, 7, Maxint)
+    else if CompareText(Copy(Name, 1, 10), '/Password=') = 0 then
+      InitPassword := Copy(Name, 11, Maxint)
     else if (CompareText(Name, '/HELP') = 0) or
             (CompareText(Name, '/?') = 0) then
       InitShowHelp := True;
@@ -404,8 +409,29 @@ begin
       if TestID <> SetupID then
         SetupCorruptError;
       try
+        var CryptKey: TSetupEncryptionKey;
+        SourceF.Read(SetupHeader.EncryptionUse, SizeOf(SetupHeader.EncryptionUse));
+        if SetupHeader.EncryptionUse = euFull then begin
+          SourceF.Read(SetupHeader.EncryptionKDFSalt, SizeOf(SetupHeader.EncryptionKDFSalt));
+          SourceF.Read(SetupHeader.EncryptionKDFIterations, SizeOf(SetupHeader.EncryptionKDFIterations));
+          SourceF.Read(SetupHeader.EncryptionBaseNonce, SizeOf(SetupHeader.EncryptionBaseNonce));
+          SourceF.Read(SetupHeader.PasswordTest, SizeOf(SetupHeader.PasswordTest));
+          SourceF.Read(SetupHeader.CompressMethod, SizeOf(SetupHeader.CompressMethod));
+          var PasswordOk: Boolean;
+          if InitPassword <> '' then begin
+            GenerateEncryptionKey(InitPassword, SetupHeader.EncryptionKDFSalt, SetupHeader.EncryptionKDFIterations, CryptKey);
+            PasswordOk := TestPassword(CryptKey, SetupHeader.EncryptionBaseNonce, SetupHeader.PasswordTest);
+          end else
+            PasswordOk := False;
+          if not PasswordOk then
+            raise Exception.Create(SIncorrectPassword);
+        end;
+
         Reader := TCompressedBlockReader.Create(SourceF, TLZMA1SmallDecompressor);
         try
+          if SetupHeader.EncryptionUse = euFull then
+            Reader.InitDecryption(CryptKey, SetupHeader.EncryptionBaseNonce, -2);
+
           SECompressedBlockRead(Reader, SetupHeader, SizeOf(SetupHeader),
             SetupHeaderStrings, SetupHeaderAnsiStrings);
 

+ 3 - 0
Projects/SetupLdr.dproj

@@ -88,6 +88,9 @@
         <DCCReference Include="..\Components\SHA256.pas"/>
         <DCCReference Include="Src\SetupLdrAndSetup.RedirFunc.pas"/>
         <DCCReference Include="Src\Shared.VerInfoFunc.pas"/>
+        <DCCReference Include="Src\Shared.EncryptionFunc.pas"/>
+        <DCCReference Include="..\Components\ChaCha20.pas"/>
+        <DCCReference Include="..\Components\PBKDF2.pas"/>
         <BuildConfiguration Include="Base">
             <Key>Base</Key>
         </BuildConfiguration>

+ 22 - 5
Projects/Src/Compiler.SetupCompiler.pas

@@ -300,7 +300,7 @@ implementation
 uses
   Commctrl, TypInfo, AnsiStrings, Math, WideStrUtils,
   PathFunc, TrustFunc, ISSigFunc, ECDSA, Shared.CommonFunc, Compiler.Messages, Shared.SetupEntFunc,
-  Shared.FileClass, Compression.Base, Compression.Zlib, Compression.bzlib,
+  Shared.FileClass, Shared.EncryptionFunc, Compression.Base, Compression.Zlib, Compression.bzlib,
   Shared.LangOptionsSectionDirectives, Shared.ResUpdateFunc, Compiler.ExeUpdateFunc,
 {$IFDEF STATICPREPROC}
   ISPP.Preprocess,
@@ -2836,7 +2836,12 @@ begin
         SetSetupHeaderOption(shEnableDirDoesntExistWarning);
       end;
     ssEncryption: begin
-        SetSetupHeaderOption(shEncryptionUsed);
+        if CompareText(Value, 'full') = 0 then
+          SetupHeader.EncryptionUse := euFull
+        else if StrToBool(Value) then
+          SetupHeader.EncryptionUse := euFiles
+        else
+          SetupHeader.EncryptionUse := euNone;
       end;
     ssEncryptionKeyDerivation: begin
         if Value = 'pbkdf2' then
@@ -5015,7 +5020,7 @@ type
           Inc6464(TotalBytesToCompress, FileListRec.Size);
           if SetupHeader.CompressMethod <> cmStored then
             Include(NewFileLocationEntry^.Flags, floChunkCompressed);
-          if shEncryptionUsed in SetupHeader.Options then
+          if SetupHeader.EncryptionUse <> euNone then
             Include(NewFileLocationEntry^.Flags, floChunkEncrypted);
           if SolidBreak and UseSolidCompression then begin
             Include(NewFileLocationEntryExtraInfo^.Flags, floSolidBreak);
@@ -6886,7 +6891,14 @@ var
     const StartPosition = F.Position;
 
     F.WriteBuffer(SetupID, SizeOf(SetupID));
-
+    F.WriteBuffer(SetupHeader.EncryptionUse, SizeOf(SetupHeader.EncryptionUse));
+    if SetupHeader.EncryptionUse = euFull then begin
+      F.WriteBuffer(SetupHeader.EncryptionKDFSalt, SizeOf(SetupHeader.EncryptionKDFSalt));
+      F.WriteBuffer(SetupHeader.EncryptionKDFIterations, SizeOf(SetupHeader.EncryptionKDFIterations));
+      F.WriteBuffer(SetupHeader.EncryptionBaseNonce, SizeOf(SetupHeader.EncryptionBaseNonce));
+      F.WriteBuffer(SetupHeader.PasswordTest, SizeOf(SetupHeader.PasswordTest));
+      F.WriteBuffer(SetupHeader.CompressMethod, SizeOf(SetupHeader.CompressMethod));
+    end;
     SetupHeader.NumLanguageEntries := LanguageEntries.Count;
     SetupHeader.NumCustomMessageEntries := CustomMessageEntries.Count;
     SetupHeader.NumPermissionEntries := PermissionEntries.Count;
@@ -6912,6 +6924,9 @@ var
     W := TCompressedBlockWriter.Create(F, TLZMACompressor, InternalCompressLevel,
       InternalCompressProps);
     try
+      if SetupHeader.EncryptionUse = euFull then
+        W.InitEncryption(CryptKey, SetupHeader.EncryptionBaseNonce, -2);
+
       SECompressedBlockWrite(W, SetupHeader, SizeOf(SetupHeader),
         SetupHeaderStrings, SetupHeaderAnsiStrings);
 
@@ -6988,6 +7003,8 @@ var
       { ^ When disk spanning is enabled, the Setup Compiler requires that
         FileLocationEntries be a fixed size, so don't compress them }
     try
+      if SetupHeader.EncryptionUse = euFull then
+        W.InitEncryption(CryptKey, SetupHeader.EncryptionBaseNonce, -3);
       for J := 0 to FileLocationEntries.Count-1 do
         W.Write(FileLocationEntries[J]^, SizeOf(TSetupFileLocationEntry));
       W.Finish;
@@ -7907,7 +7924,7 @@ begin
       else
         VersionInfoProductTextVersion := VersionInfoProductVersionOriginalValue;
     end;
-    if (shEncryptionUsed in SetupHeader.Options) and (Password = '') then begin
+    if (SetupHeader.EncryptionUse <> euNone) and (Password = '') then begin
       LineNumber := SetupDirectiveLines[ssEncryption];
       AbortCompileFmt(SCompilerEntryMissing2, ['Setup', 'Password']);
     end;

+ 43 - 2
Projects/Src/Compression.Base.pas

@@ -2,7 +2,7 @@ unit Compression.Base;
 
 {
   Inno Setup
-  Copyright (C) 1997-2010 Jordan Russell
+  Copyright (C) 1997-2025 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -12,7 +12,7 @@ unit Compression.Base;
 interface
 
 uses
-  Windows, SysUtils, Shared.Int64Em, Shared.FileClass;
+  Windows, SysUtils, ChaCha20, Shared.Int64Em, Shared.FileClass, Shared.Struct;
 
 type
   ECompressError = class(Exception);
@@ -77,6 +77,8 @@ type
     FTotalBytesStored: Cardinal;
     FInBufferCount, FOutBufferCount: Cardinal;
     FInBuffer, FOutBuffer: array[0..4095] of Byte;
+    FEncrypt: Boolean;
+    FCryptContext: TChaCha20Context;
     procedure CompressorWriteProc(const Buffer; Count: Longint);
     procedure DoCompress(const Buf; var Count: Cardinal);
     procedure FlushOutputBuffer;
@@ -84,6 +86,8 @@ type
     constructor Create(AFile: TFile; ACompressorClass: TCustomCompressorClass;
       CompressionLevel: Integer; ACompressorProps: TCompressorProps);
     destructor Destroy; override;
+    procedure InitEncryption(const CryptKey: TSetupEncryptionKey;
+      const EncryptionBaseNonce: TSetupEncryptionNonce; const UniqueIndex: Integer);
     procedure Finish;
     procedure Write(const Buffer; Count: Cardinal);
   end;
@@ -97,11 +101,15 @@ type
     FInBufferNext: Cardinal;
     FInBufferAvail: Cardinal;
     FInBuffer: array[0..4095] of Byte;
+    FDecrypt: Boolean;
+    FCryptContext: TChaCha20Context;
     function DecompressorReadProc(var Buffer; Count: Longint): Longint;
     procedure ReadChunk;
   public
     constructor Create(AFile: TFile; ADecompressorClass: TCustomDecompressorClass);
     destructor Destroy; override;
+    procedure InitDecryption(const CryptKey: TSetupEncryptionKey;
+      const EncryptionBaseNonce: TSetupEncryptionNonce; const UniqueIndex: Integer);
     procedure Read(var Buffer; Count: Cardinal);
   end;
 
@@ -282,6 +290,19 @@ procedure TStoredDecompressor.Reset;
 begin
 end;
 
+{ TCompressedBlockWriter & Reader }
+
+procedure InitCryptContext(const CryptKey: TSetupEncryptionKey;
+  const EncryptionBaseNonce: TSetupEncryptionNonce; const UniqueIndex: Integer;
+  var CryptContext: TChaCha20Context);
+begin
+  { Recreate the unique nonce from the base nonce }
+  var Nonce := EncryptionBaseNonce;
+  Nonce.RandomXorFirstSlice := Nonce.RandomXorFirstSlice xor UniqueIndex;
+
+  XChaCha20Init(CryptContext, CryptKey[0], Length(CryptKey), Nonce, SizeOf(Nonce), 0);
+end;
+
 { TCompressedBlockWriter }
 
 type
@@ -324,6 +345,9 @@ procedure TCompressedBlockWriter.FlushOutputBuffer;
 var
   CRC: Longint;
 begin
+  if FEncrypt then
+    XChaCha20Crypt(FCryptContext, FOutBuffer, FOutBuffer, FOutBufferCount);
+
   CRC := GetCRC32(FOutBuffer, FOutBufferCount);
   FFile.WriteBuffer(CRC, SizeOf(CRC));
   Inc(FTotalBytesStored, SizeOf(CRC));
@@ -332,6 +356,13 @@ begin
   FOutBufferCount := 0;
 end;
 
+procedure TCompressedBlockWriter.InitEncryption(const CryptKey: TSetupEncryptionKey;
+  const EncryptionBaseNonce: TSetupEncryptionNonce; const UniqueIndex: Integer);
+begin
+  InitCryptContext(CryptKey, EncryptionBaseNonce, UniqueIndex, FCryptContext);
+  FEncrypt := True;
+end;
+
 procedure TCompressedBlockWriter.CompressorWriteProc(const Buffer; Count: Longint);
 var
   P: ^Byte;
@@ -450,6 +481,13 @@ begin
   inherited;
 end;
 
+procedure TCompressedBlockReader.InitDecryption(const CryptKey: TSetupEncryptionKey;
+  const EncryptionBaseNonce: TSetupEncryptionNonce; const UniqueIndex: Integer);
+begin
+  InitCryptContext(CryptKey, EncryptionBaseNonce, UniqueIndex, FCryptContext);
+  FDecrypt := True;
+end;
+
 procedure TCompressedBlockReader.ReadChunk;
 var
   CRC: Longint;
@@ -471,6 +509,9 @@ begin
   FInBufferAvail := Len;
   if CRC <> GetCRC32(FInBuffer, Len) then
     raise ECompressDataError.Create(SCompressedBlockDataError);
+
+  if FDecrypt then
+    XChaCha20Crypt(FCryptContext, FInBuffer, FInBuffer, Len);
 end;
 
 function TCompressedBlockReader.DecompressorReadProc(var Buffer;

+ 34 - 23
Projects/Src/Setup.MainFunc.pas

@@ -228,7 +228,6 @@ function ShouldProcessIconEntry(const WizardComponents, WizardTasks: TStringList
   const WizardNoIcons: Boolean; const IconEntry: PSetupIconEntry): Boolean;
 function ShouldProcessRunEntry(const WizardComponents, WizardTasks: TStringList;
   const RunEntry: PSetupRunEntry): Boolean;
-function TestPassword(const EncryptionKey: TSetupEncryptionKey): Boolean;
 procedure UnloadSHFolderDLL;
 function WindowsVersionAtLeast(const AMajor, AMinor: Byte; const ABuild: Word = 0): Boolean;
 function IsWindows8: Boolean;
@@ -242,7 +241,7 @@ uses
   SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, Setup.Install, SetupLdrAndSetup.InstFunc,
   Setup.InstFunc, SetupLdrAndSetup.RedirFunc, PathFunc,
   Compression.Base, Compression.Zlib, Compression.bzlib, Compression.LZMADecompressor,
-  Shared.SetupEntFunc, Setup.SelectLanguageForm,
+  Shared.SetupEntFunc, Shared.EncryptionFunc,  Setup.SelectLanguageForm,
   Setup.WizardForm, Setup.DebugClient, Shared.VerInfoFunc, Setup.FileExtractor,
   Shared.FileClass, Setup.LoggingFunc,
   SimpleExpression, Setup.Helper, Setup.SpawnClient, Setup.SpawnServer,
@@ -373,21 +372,6 @@ begin
   Result := -1;
 end;
 
-{ This function assumes EncryptionKey is based on the password }
-function TestPassword(const EncryptionKey: TSetupEncryptionKey): Boolean;
-begin
-  { Do same as compiler did in GeneratePasswordTest and compare results }
-  var Nonce := SetupHeader.EncryptionBaseNonce;
-  Nonce.RandomXorFirstSlice := Nonce.RandomXorFirstSlice xor -1;
-
-  var Context: TChaCha20Context;
-  XChaCha20Init(Context, EncryptionKey[0], Length(EncryptionKey), Nonce, SizeOf(Nonce), 0);
-  var PasswordTest := 0;
-  XChaCha20Crypt(Context, PasswordTest, PasswordTest, SizeOf(PasswordTest));
-
-  Result := PasswordTest = SetupHeader.PasswordTest;
-end;
-
 class function TDummyClass.ExpandCheckOrInstallConstant(Sender: TSimpleExpression;
   const Constant: String): String;
 begin
@@ -2740,7 +2724,7 @@ var
     end;
   end;
 
-  function HandleInitPassword(const NeedPassword: Boolean): Boolean;
+  function HandleInitPassword(const NeedPassword: Boolean; out CryptKey: TSetupEncryptionKey): Boolean; overload;
   { Handles InitPassword and returns the updated value of NeedPassword }
   { Also see WizardForm.CheckPassword }
   begin
@@ -2749,21 +2733,26 @@ var
     if NeedPassword and (InitPassword <> '') then begin
       var PasswordOk := False;
       var S := InitPassword;
-      var CryptKey: TSetupEncryptionKey;
       GenerateEncryptionKey(S, SetupHeader.EncryptionKDFSalt, SetupHeader.EncryptionKDFIterations, CryptKey);
       if shPassword in SetupHeader.Options then
-        PasswordOk := TestPassword(CryptKey);
+        PasswordOk := TestPassword(CryptKey, SetupHeader.EncryptionBaseNonce, SetupHeader.PasswordTest);
       if not PasswordOk and (CodeRunner <> nil) then
         PasswordOk := CodeRunner.RunBooleanFunctions('CheckPassword', [S], bcTrue, False, PasswordOk);
 
       if PasswordOk then begin
         Result := False;
-        if shEncryptionUsed in SetupHeader.Options then
+        if SetupHeader.EncryptionUse <> euNone then
           FileExtractor.CryptKey := CryptKey;
       end;
     end;
   end;
 
+  function HandleInitPassword(const NeedPassword: Boolean): Boolean; overload;
+  begin
+    var CryptKey: TSetupEncryptionKey;
+    Result := HandleInitPassword(NeedPassword, CryptKey);
+  end;
+
   procedure SetupInstallMode;
   begin
     if InitVerySilent then
@@ -3088,6 +3077,7 @@ begin
   SetupMessages[msgSetupFileMissing] := SSetupFileMissing;
   SetupMessages[msgSetupFileCorrupt] := SSetupFileCorrupt;
   SetupMessages[msgSetupFileCorruptOrWrongVer] := SSetupFileCorruptOrWrongVer;
+  SetupMessages[msgIncorrectPassword] := SIncorrectPassword;
 
   { Read setup-0.bin, or from EXE }
   if not SetupLdrMode then begin
@@ -3105,12 +3095,30 @@ begin
     if TestID <> SetupID then
       AbortInit(msgSetupFileCorruptOrWrongVer);
     try
+      var CryptKey: TSetupEncryptionKey;
+      SetupFile.Read(SetupHeader.EncryptionUse, SizeOf(SetupHeader.EncryptionUse));
+      if SetupHeader.EncryptionUse = euFull then begin
+        SetupFile.Read(SetupHeader.EncryptionKDFSalt, SizeOf(SetupHeader.EncryptionKDFSalt));
+        SetupFile.Read(SetupHeader.EncryptionKDFIterations, SizeOf(SetupHeader.EncryptionKDFIterations));
+        SetupFile.Read(SetupHeader.EncryptionBaseNonce, SizeOf(SetupHeader.EncryptionBaseNonce));
+        SetupFile.Read(SetupHeader.PasswordTest, SizeOf(SetupHeader.PasswordTest));
+        { FileExtractor (a function called early to initialize its CryptKey) requires CompressMethod to be set }
+        SetupFile.Read(SetupHeader.CompressMethod, SizeOf(SetupHeader.CompressMethod));
+        { HandleInitPassword requires this }
+        SetupHeader.Options := SetupHeader.Options + [shPassword];
+        if HandleInitPassword(True, CryptKey) then { HandleInitPassword returns True on failure }
+          AbortInit(msgIncorrectPassword)
+      end;
+
       var Reader := TCompressedBlockReader.Create(SetupFile, TLZMA1Decompressor);
       try
+        if SetupHeader.EncryptionUse = euFull then
+          Reader.InitDecryption(CryptKey, SetupHeader.EncryptionBaseNonce, -2);
+
         { Header }
         SECompressedBlockRead(Reader, SetupHeader, SizeOf(SetupHeader),
           SetupHeaderStrings, SetupHeaderAnsiStrings);
-        { Language entries }
+      { Language entries }
         ReadEntriesWithoutVersion(Reader, seLanguage, SetupHeader.NumLanguageEntries,
           SizeOf(TSetupLanguageEntry));
         { CustomMessage entries }
@@ -3187,7 +3195,7 @@ begin
         LogCompatibilityMode;
         LogWindowsVersion;
 
-        NeedPassword := shPassword in SetupHeader.Options;
+        NeedPassword := (SetupHeader.EncryptionUse <> euFull) and (shPassword in SetupHeader.Options);
         NeedSerial := False;
         NeedsRestart := shAlwaysRestart in SetupHeader.Options;
 
@@ -3259,6 +3267,9 @@ begin
       end;
       Reader := TCompressedBlockReader.Create(SetupFile, TLZMA1Decompressor);
       try
+        if SetupHeader.EncryptionUse = euFull then
+          Reader.InitDecryption(CryptKey, SetupHeader.EncryptionBaseNonce, -3);
+
         { File location entries }
         ReadEntriesWithoutVersion(Reader, seFileLocation, SetupHeader.NumFileLocationEntries,
           SizeOf(TSetupFileLocationEntry));

+ 3 - 3
Projects/Src/Setup.WizardForm.pas

@@ -347,7 +347,7 @@ uses
   PathFunc, RestartManager, SHA256,
   SetupLdrAndSetup.Messages, Setup.MainForm, Setup.MainFunc, Shared.CommonFunc.Vcl,
   Shared.CommonFunc, Setup.InstFunc, Setup.SelectFolderForm, Setup.FileExtractor,
-  Setup.LoggingFunc, Setup.ScriptRunner, Shared.SetupTypes, Shared.SetupSteps,
+  Setup.LoggingFunc, Setup.ScriptRunner, Shared.SetupTypes, Shared.EncryptionFunc, Shared.SetupSteps,
   Setup.ScriptDlg, SetupLdrAndSetup.InstFunc, Setup.Install;
 
 {$R *.DFM}
@@ -2438,13 +2438,13 @@ procedure TWizardForm.NextButtonClick(Sender: TObject);
     end;
 
     if shPassword in SetupHeader.Options then
-      Result := TestPassword(CryptKey);
+      Result := TestPassword(CryptKey, SetupHeader.EncryptionBaseNonce, SetupHeader.PasswordTest);
     if not Result and (CodeRunner <> nil) then
       Result := CodeRunner.RunBooleanFunctions('CheckPassword', [S], bcTrue, False, Result);
 
     if Result then begin
       NeedPassword := False;
-      if shEncryptionUsed in SetupHeader.Options then
+      if SetupHeader.EncryptionUse <> euNone then
         FileExtractor.CryptKey := CryptKey;
       PasswordEdit.Text := '';
     end else begin

+ 2 - 1
Projects/Src/SetupLdrAndSetup.Messages.pas

@@ -2,7 +2,7 @@ unit SetupLdrAndSetup.Messages;
 
 {
   Inno Setup
-  Copyright (C) 1997-2024 Jordan Russell
+  Copyright (C) 1997-2025 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -47,6 +47,7 @@ const
     'obtain a new copy of the program.';
   SMsgsFileMissing = 'Messages file "%s" is missing. Please correct ' +
     'the problem or obtain a new copy of the program.';
+  SIncorrectPassword = 'The password you entered is not correct. Please try again.';
 
 implementation
 

+ 56 - 0
Projects/Src/Shared.EncryptionFunc.pas

@@ -0,0 +1,56 @@
+unit Shared.EncryptionFunc;
+
+{
+  Inno Setup
+  Copyright (C) 1997-2025 Jordan Russell
+  Portions by Martijn Laan
+  For conditions of distribution and use, see LICENSE.TXT.
+
+  Encryption function used by ISCmplr, Setup, and SetupLdr
+}
+
+interface
+
+uses
+  Shared.Struct;
+
+procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
+  const Iterations: Integer; out Key: TSetupEncryptionKey);
+function TestPassword(const EncryptionKey: TSetupEncryptionKey;
+  const EncryptionBaseNonce: TSetupEncryptionNonce; const ExpectedPasswordTest: Integer): Boolean;
+
+implementation
+
+uses
+  SysUtils, ChaCha20, PBKDF2;
+
+procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
+  const Iterations: Integer; out Key: TSetupEncryptionKey);
+begin
+  var SaltBytes: TBytes;
+  var SaltSize := SizeOf(Salt);
+  SetLength(SaltBytes, SaltSize);
+  Move(Salt[0], SaltBytes[0], SaltSize);
+  var KeyLength := SizeOf(Key);
+  var KeyBytes := PBKDF2SHA256(Password, SaltBytes, Iterations, KeyLength);
+  Move(KeyBytes[0], Key[0], KeyLength);
+end;
+
+{ This function assumes EncryptionKey is based on the password }
+function TestPassword(const EncryptionKey: TSetupEncryptionKey;
+  const EncryptionBaseNonce: TSetupEncryptionNonce; const ExpectedPasswordTest: Integer): Boolean;
+begin
+  { Do same as compiler did in GeneratePasswordTest and compare results }
+  var Nonce := EncryptionBaseNonce;
+  Nonce.RandomXorFirstSlice := Nonce.RandomXorFirstSlice xor -1;
+
+  var Context: TChaCha20Context;
+  XChaCha20Init(Context, EncryptionKey[0], Length(EncryptionKey), Nonce, SizeOf(Nonce), 0);
+  var ActualPasswordTest := 0;
+  XChaCha20Crypt(Context, ActualPasswordTest, ActualPasswordTest, SizeOf(ActualPasswordTest));
+
+  Result := ActualPasswordTest = ExpectedPasswordTest;
+end;
+
+
+end.

+ 1 - 15
Projects/Src/Shared.SetupTypes.pas

@@ -56,8 +56,6 @@ function StringsToCommaString(const Strings: TStrings): String;
 procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
 function StrToSetupVersionData(const S: String; var VerData: TSetupVersionData): Boolean;
 procedure HandleRenamedConstants(var Cnst: String; const RenamedConstantCallback: TRenamedConstantCallback);
-procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
-  const Iterations: Integer; out Key: TSetupEncryptionKey);
 procedure SetISSigAllowedKey(var ISSigAllowedKeys: AnsiString; const KeyIndex: Integer);
 function GetISSigAllowedKeys([ref] const ISSigAvailableKeys: TArrayOfECDSAKey;
   const ISSigAllowedKeys: AnsiString): TArrayOfECDSAKey;
@@ -66,7 +64,7 @@ function IsExcluded(Text: String; const AExcludes: TStrings): Boolean;
 implementation
 
 uses
-  PBKDF2, PathFunc, Shared.CommonFunc;
+  PathFunc, Shared.CommonFunc;
 
 function QuoteStringIfNeeded(const S: String): String;
 { Used internally by StringsToCommaString. Adds quotes around the string if
@@ -303,18 +301,6 @@ begin
   end;
 end;
 
-procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
-  const Iterations: Integer; out Key: TSetupEncryptionKey);
-begin
-  var SaltBytes: TBytes;
-  var SaltSize := SizeOf(Salt);
-  SetLength(SaltBytes, SaltSize);
-  Move(Salt[0], SaltBytes[0], SaltSize);
-  var KeyLength := SizeOf(Key);
-  var KeyBytes := PBKDF2SHA256(Password, SaltBytes, Iterations, KeyLength);
-  Move(KeyBytes[0], Key[0], KeyLength);
-end;
-
 procedure SetISSigAllowedKey(var ISSigAllowedKeys: AnsiString; const KeyIndex: Integer);
 { ISSigAllowedKeys should start out empty. If you then only use this function
   to update it, regular string comparison can be used for comparisons. }

+ 3 - 1
Projects/Src/Shared.Struct.pas

@@ -60,7 +60,7 @@ type
     shAllowUNCPath, shUserInfoPage, shUsePreviousUserInfo,
     shUninstallRestartComputer, shRestartIfNeededByRun, shShowTasksTreeLines,
     shAllowCancelDuringInstall, shWizardImageStretch, shAppendDefaultDirName,
-    shAppendDefaultGroupName, shEncryptionUsed, shSetupLogging,
+    shAppendDefaultGroupName, shSetupLogging,
     shSignedUninstaller, shUsePreviousLanguage, shDisableWelcomePage,
     shCloseApplications, shRestartApplications, shAllowNetworkDrive,
     shForceCloseApplications, shAppNameHasConsts, shUsePreviousPrivileges,
@@ -68,6 +68,7 @@ type
   TSetupLanguageDetectionMethod = (ldUILanguage, ldLocale, ldNone);
   TSetupCompressMethod = (cmStored, cmZip, cmBzip, cmLZMA, cmLZMA2);
   TSetupKDFSalt = array[0..15] of Byte;
+  TSetupEncryptionUse = (euNone, euFiles, euFull);
   TSetupEncryptionKey = array[0..31] of Byte;
   TSetupEncryptionNonce = record
     RandomXorStartOffset: Int64;
@@ -110,6 +111,7 @@ type
     WizardSizePercentX, WizardSizePercentY: Integer;
     WizardImageAlphaFormat: (afIgnored, afDefined, afPremultiplied); // Must be same as Graphics.TAlphaFormat
     PasswordTest: Integer;
+    EncryptionUse: TSetupEncryptionUse;
     EncryptionKDFSalt: TSetupKDFSalt;
     EncryptionKDFIterations: Integer;
     EncryptionBaseNonce: TSetupEncryptionNonce;