瀏覽代碼

Added [Setup] section directive EncryptionKeyDerivation.

Martijn Laan 11 月之前
父節點
當前提交
a5a8448fc2

+ 15 - 1
ISHelp/isetup.xml

@@ -5439,7 +5439,21 @@ DiskSliceSize=1457664
 <body>
 <body>
 <p>If set to <tt>yes</tt>, files that are compiled into the installation (via [Files] section entries) will be encrypted using XChaCha20 encryption.</p>
 <p>If set to <tt>yes</tt>, files that are compiled into the installation (via [Files] section entries) will be encrypted using XChaCha20 encryption.</p>
 <p>If encryption is enabled and you call the <link topic="isxfunc_ExtractTemporaryFile">ExtractTemporaryFile</link> function from the [Code] section prior to the user entering the correct password, the function will fail unless the <tt>noencryption</tt> flag is used on the [Files] section entry for the file.</p>
 <p>If encryption is enabled and you call the <link topic="isxfunc_ExtractTemporaryFile">ExtractTemporaryFile</link> function from the [Code] section prior to the user entering the correct password, the function will fail unless the <tt>noencryption</tt> flag is used on the [Files] section entry for the file.</p>
-<p>The 256-bit XChaCha20 encryption key is derived from the value of <link topic="setup_password">Password</link> using PBKDF2 with 100,000 iterations and a 128-bit random salt, and the 192-bit XChaCha20 encryption nonce is a random base nonce, appending the index of the first file in the chunk for unique encryption nonces.</p>
+<p>The 256-bit XChaCha20 encryption key is derived from the value of <link topic="setup_password">Password</link> using the function specified by <link topic="setup_encryptionkeyderivation">EncryptionKeyDerivation</link>, and the 192-bit XChaCha20 encryption nonce is a random base nonce, appending the index of the first file in the chunk for unique encryption nonces.</p>
+</body>
+</setuptopic>
+
+<setuptopic directive="EncryptionKeyDerivation">
+<keyword value="pbkdf2" />
+<setupvalid><tt>pbkdf2</tt><br/>
+<tt>pbkdf2/1</tt> through <tt>pbkdf2/2147483647</tt></setupvalid>
+<setupdefault><tt>pbkdf2/100000</tt></setupdefault>
+<body>
+<p>This specifies the key derivation function to use derive the encryption key from the value of <link topic="setup_password">Password</link>, and optionally its parameters.</p>
+<p><tt>pbkdf2</tt> is the PBKDF2-HMAC-SHA256 function with a 128-bit random salt, and optionally allows to increase its numbers of iterations for extra security. If the number of iterations isn't specified, it defaults to 100000.</p>
+<p><b>See also:</b><br/>
+<link topic="setup_encryption">Encryption</link>
+</p>
 </body>
 </body>
 </setuptopic>
 </setuptopic>
 
 

+ 48 - 33
Projects/Src/Compiler.SetupCompiler.pas

@@ -111,6 +111,7 @@ type
     InternalCompressProps, CompressProps: TLZMACompressorProps;
     InternalCompressProps, CompressProps: TLZMACompressorProps;
     UseSolidCompression: Boolean;
     UseSolidCompression: Boolean;
     DontMergeDuplicateFiles: Boolean;
     DontMergeDuplicateFiles: Boolean;
+    Password: String;
     CryptKey: TSetupEncryptionKey;
     CryptKey: TSetupEncryptionKey;
     TimeStampsInUTC: Boolean;
     TimeStampsInUTC: Boolean;
     TimeStampRounding: Integer;
     TimeStampRounding: Integer;
@@ -2350,31 +2351,6 @@ var
     end;
     end;
   end;
   end;
 
 
-  procedure GenerateEncryptionKDFSalt(out Salt: TSetupKDFSalt);
-  begin
-    GenerateRandomBytes(Salt, SizeOf(Salt));
-  end;
-
-  procedure GenerateEncryptionBaseNonce(out Nonce: TSetupEncryptionNonce);
-  begin
-    GenerateRandomBytes(Nonce, SizeOf(Nonce));
-  end;
-
-  { This function assumes EncryptionKey is based on the password }
-  procedure GeneratePasswordTest(const EncryptionKey: TSetupEncryptionKey;
-    const EncryptionBaseNonce: TSetupEncryptionNonce; out PasswordTest: Integer);
-  begin
-    { Create a special nonce that cannot collide with encrypted-file nonces }
-    var Nonce := EncryptionBaseNonce;
-    Nonce.RandomXorFirstSlice := Nonce.RandomXorFirstSlice xor -1;
-
-    { Encrypt a value of 0 so Setup can do same and compare the results to test the password }
-    var Context: TChaCha20Context;
-    XChaCha20Init(Context, EncryptionKey[0], Length(EncryptionKey), Nonce, SizeOf(Nonce), 0);
-    PasswordTest := 0;
-    XChaCha20Crypt(Context, PasswordTest, PasswordTest, SizeOf(PasswordTest));
-  end;
-
   procedure StrToTouchDate(const S: String);
   procedure StrToTouchDate(const S: String);
   var
   var
     P: PChar;
     P: PChar;
@@ -2818,6 +2794,17 @@ begin
     ssEncryption: begin
     ssEncryption: begin
         SetSetupHeaderOption(shEncryptionUsed);
         SetSetupHeaderOption(shEncryptionUsed);
       end;
       end;
+    ssEncryptionKeyDerivation: begin
+        if Value = 'pbkdf2' then
+          SetupHeader.EncryptionKDFIterations := 100000
+        else if Copy(Value, 1, 7) = 'pbkdf2/' then begin
+          I := StrToIntDef(Copy(Value, 8, Maxint), -1);
+          if I < 1 then
+            Invalid;
+          SetupHeader.EncryptionKDFIterations := I;
+        end else
+          Invalid;
+      end;
     ssExtraDiskSpaceRequired: begin
     ssExtraDiskSpaceRequired: begin
         if not StrToInteger64(Value, SetupHeader.ExtraDiskSpaceRequired) then
         if not StrToInteger64(Value, SetupHeader.ExtraDiskSpaceRequired) then
           Invalid;
           Invalid;
@@ -2927,13 +2914,7 @@ begin
         OutputManifestFile := Value;
         OutputManifestFile := Value;
       end;
       end;
     ssPassword: begin
     ssPassword: begin
-        if Value <> '' then begin
-          GenerateEncryptionKDFSalt(SetupHeader.EncryptionKDFSalt);
-          GenerateEncryptionKey(Value,  SetupHeader.EncryptionKDFSalt, CryptKey);
-          GenerateEncryptionBaseNonce(SetupHeader.EncryptionBaseNonce);
-          GeneratePasswordTest(CryptKey, SetupHeader.EncryptionBaseNonce, SetupHeader.PasswordTest);
-          Include(SetupHeader.Options, shPassword);
-        end;
+        Password := Value;
       end;
       end;
     ssPrivilegesRequired: begin
     ssPrivilegesRequired: begin
         if CompareText(Value, 'none') = 0 then
         if CompareText(Value, 'none') = 0 then
@@ -7320,6 +7301,31 @@ var
     SetFileTime(H, nil, nil, @FT);
     SetFileTime(H, nil, nil, @FT);
   end;
   end;
 
 
+  procedure GenerateEncryptionKDFSalt(out Salt: TSetupKDFSalt);
+  begin
+    GenerateRandomBytes(Salt, SizeOf(Salt));
+  end;
+
+  procedure GenerateEncryptionBaseNonce(out Nonce: TSetupEncryptionNonce);
+  begin
+    GenerateRandomBytes(Nonce, SizeOf(Nonce));
+  end;
+
+  { This function assumes EncryptionKey is based on the password }
+  procedure GeneratePasswordTest(const EncryptionKey: TSetupEncryptionKey;
+    const EncryptionBaseNonce: TSetupEncryptionNonce; out PasswordTest: Integer);
+  begin
+    { Create a special nonce that cannot collide with encrypted-file nonces }
+    var Nonce := EncryptionBaseNonce;
+    Nonce.RandomXorFirstSlice := Nonce.RandomXorFirstSlice xor -1;
+
+    { Encrypt a value of 0 so Setup can do same and compare the results to test the password }
+    var Context: TChaCha20Context;
+    XChaCha20Init(Context, EncryptionKey[0], Length(EncryptionKey), Nonce, SizeOf(Nonce), 0);
+    PasswordTest := 0;
+    XChaCha20Crypt(Context, PasswordTest, PasswordTest, SizeOf(PasswordTest));
+  end;
+
 const
 const
   BadFilePathChars = '/*?"<>|';
   BadFilePathChars = '/*?"<>|';
   BadFileNameChars = BadFilePathChars + ':';
   BadFileNameChars = BadFilePathChars + ':';
@@ -7411,6 +7417,7 @@ begin
     NotRecognizedMessagesWarning := True;
     NotRecognizedMessagesWarning := True;
     UsedUserAreasWarning := True;
     UsedUserAreasWarning := True;
     SetupHeader.WizardStyle := wsClassic;
     SetupHeader.WizardStyle := wsClassic;
+    SetupHeader.EncryptionKDFIterations := 100000;
 
 
     { Read [Setup] section }
     { Read [Setup] section }
     EnumIniSection(EnumSetupProc, 'Setup', 0, True, True, '', False, False);
     EnumIniSection(EnumSetupProc, 'Setup', 0, True, True, '', False, False);
@@ -7559,7 +7566,7 @@ begin
       else
       else
         VersionInfoProductTextVersion := VersionInfoProductVersionOriginalValue;
         VersionInfoProductTextVersion := VersionInfoProductVersionOriginalValue;
     end;
     end;
-    if (shEncryptionUsed in SetupHeader.Options) and not (shPassword in SetupHeader.Options) then begin
+    if (shEncryptionUsed in SetupHeader.Options) and (Password = '') then begin
       LineNumber := SetupDirectiveLines[ssEncryption];
       LineNumber := SetupDirectiveLines[ssEncryption];
       AbortCompileFmt(SCompilerEntryMissing2, ['Setup', 'Password']);
       AbortCompileFmt(SCompilerEntryMissing2, ['Setup', 'Password']);
     end;
     end;
@@ -7629,6 +7636,14 @@ begin
       SignedUninstallerDir := AddBackslash(SignedUninstallerDir);
       SignedUninstallerDir := AddBackslash(SignedUninstallerDir);
     end;
     end;
 
 
+    if Password <> '' then begin
+      GenerateEncryptionKDFSalt(SetupHeader.EncryptionKDFSalt);
+      GenerateEncryptionKey(Password,  SetupHeader.EncryptionKDFSalt, SetupHeader.EncryptionKDFIterations, CryptKey);
+      GenerateEncryptionBaseNonce(SetupHeader.EncryptionBaseNonce);
+      GeneratePasswordTest(CryptKey, SetupHeader.EncryptionBaseNonce, SetupHeader.PasswordTest);
+      Include(SetupHeader.Options, shPassword);
+    end;
+
     { Read text files }
     { Read text files }
     if LicenseFile <> '' then begin
     if LicenseFile <> '' then begin
       LineNumber := SetupDirectiveLines[ssLicenseFile];
       LineNumber := SetupDirectiveLines[ssLicenseFile];

+ 1 - 1
Projects/Src/Setup.MainFunc.pas

@@ -2691,7 +2691,7 @@ var
       var PasswordOk := False;
       var PasswordOk := False;
       var S := InitPassword;
       var S := InitPassword;
       var CryptKey: TSetupEncryptionKey;
       var CryptKey: TSetupEncryptionKey;
-      GenerateEncryptionKey(S, SetupHeader.EncryptionKDFSalt, CryptKey);
+      GenerateEncryptionKey(S, SetupHeader.EncryptionKDFSalt, SetupHeader.EncryptionKDFIterations, CryptKey);
       if shPassword in SetupHeader.Options then
       if shPassword in SetupHeader.Options then
         PasswordOk := TestPassword(CryptKey);
         PasswordOk := TestPassword(CryptKey);
       if not PasswordOk and (CodeRunner <> nil) then
       if not PasswordOk and (CodeRunner <> nil) then

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

@@ -2294,7 +2294,7 @@ procedure TWizardForm.NextButtonClick(Sender: TObject);
     var SaveCursor := GetCursor;
     var SaveCursor := GetCursor;
     SetCursor(LoadCursor(0, IDC_WAIT));
     SetCursor(LoadCursor(0, IDC_WAIT));
     try
     try
-      GenerateEncryptionKey(S, SetupHeader.EncryptionKDFSalt, CryptKey);
+      GenerateEncryptionKey(S, SetupHeader.EncryptionKDFSalt, SetupHeader.EncryptionKDFIterations, CryptKey);
     finally
     finally
       SetCursor(SaveCursor);
       SetCursor(SaveCursor);
     end;
     end;

+ 1 - 0
Projects/Src/Shared.SetupSectionDirectives.pas

@@ -76,6 +76,7 @@ type
     ssDontMergeDuplicateFiles,
     ssDontMergeDuplicateFiles,
     ssEnableDirDoesntExistWarning,
     ssEnableDirDoesntExistWarning,
     ssEncryption,
     ssEncryption,
+    ssEncryptionKeyDerivation,
     ssExtraDiskSpaceRequired,
     ssExtraDiskSpaceRequired,
     ssFlatComponentsList,
     ssFlatComponentsList,
     ssInfoAfterFile,
     ssInfoAfterFile,

+ 4 - 3
Projects/Src/Shared.SetupTypes.pas

@@ -51,7 +51,8 @@ function StringsToCommaString(const Strings: TStrings): String;
 procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
 procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
 function StrToSetupVersionData(const S: String; var VerData: TSetupVersionData): Boolean;
 function StrToSetupVersionData(const S: String; var VerData: TSetupVersionData): Boolean;
 procedure HandleRenamedConstants(var Cnst: String; const RenamedConstantCallback: TRenamedConstantCallback);
 procedure HandleRenamedConstants(var Cnst: String; const RenamedConstantCallback: TRenamedConstantCallback);
-procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt; out Key: TSetupEncryptionKey);
+procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
+  const Iterations: Integer; out Key: TSetupEncryptionKey);
 
 
 implementation
 implementation
 
 
@@ -294,14 +295,14 @@ begin
 end;
 end;
 
 
 procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
 procedure GenerateEncryptionKey(const Password: String; const Salt: TSetupKDFSalt;
-  out Key: TSetupEncryptionKey);
+  const Iterations: Integer; out Key: TSetupEncryptionKey);
 begin
 begin
   var SaltBytes: TBytes;
   var SaltBytes: TBytes;
   var SaltSize := SizeOf(Salt);
   var SaltSize := SizeOf(Salt);
   SetLength(SaltBytes, SaltSize);
   SetLength(SaltBytes, SaltSize);
   Move(Salt[0], SaltBytes[0], SaltSize);
   Move(Salt[0], SaltBytes[0], SaltSize);
   var KeyLength := SizeOf(Key);
   var KeyLength := SizeOf(Key);
-  var KeyBytes := PBKDF2SHA256(Password, SaltBytes, 100000, KeyLength);
+  var KeyBytes := PBKDF2SHA256(Password, SaltBytes, Iterations, KeyLength);
   Move(KeyBytes[0], Key[0], KeyLength);
   Move(KeyBytes[0], Key[0], KeyLength);
 end;
 end;
 
 

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

@@ -112,6 +112,7 @@ type
     WizardImageAlphaFormat: (afIgnored, afDefined, afPremultiplied); // Must be same as Graphics.TAlphaFormat
     WizardImageAlphaFormat: (afIgnored, afDefined, afPremultiplied); // Must be same as Graphics.TAlphaFormat
     PasswordTest: Integer;
     PasswordTest: Integer;
     EncryptionKDFSalt: TSetupKDFSalt;
     EncryptionKDFSalt: TSetupKDFSalt;
+    EncryptionKDFIterations: Integer;
     EncryptionBaseNonce: TSetupEncryptionNonce;
     EncryptionBaseNonce: TSetupEncryptionNonce;
     ExtraDiskSpaceRequired: Integer64;
     ExtraDiskSpaceRequired: Integer64;
     SlicesPerDisk: Integer;
     SlicesPerDisk: Integer;

+ 2 - 1
whatsnew.htm

@@ -82,7 +82,8 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
 <p><span class="head2">Other changes</span></p>
 <p><span class="head2">Other changes</span></p>
 <ul>
 <ul>
   <li>Updated the LZMA SDK used by Inno Setup to the latest version, increasing the speed of LZMA and LZMA2 compression and decompression (respectively by 21% and 11% in a test with default settings) without changing the compression ratio. Compression memory requirements have increased by about 4%.</li>
   <li>Updated the LZMA SDK used by Inno Setup to the latest version, increasing the speed of LZMA and LZMA2 compression and decompression (respectively by 21% and 11% in a test with default settings) without changing the compression ratio. Compression memory requirements have increased by about 4%.</li>
-  <li>Updated the key derivation function and encryption algorithm used by Inno Setup to PBKDF2 and XChaCha20 respectively, increasing security. This code is built-in: the separate ISCrypt.dll "encryption module" is no longer used and will be automatically deleted when you update.</li>
+  <li>Updated the key derivation function and encryption algorithm used by Inno Setup to PBKDF2-HMAC-SHA256 and XChaCha20 respectively, increasing security. This code is built-in: the separate ISCrypt.dll "encryption module" is no longer used and will be automatically deleted when you update.</li>
+  <li>Added <tt>[Setup]</tt> section directive <tt>EncryptionKeyDerivation</tt> to change the number of PBKDF2-HMAC-SHA256 iterations to use from the default of 100000 to another value.</li>
   <li>Replaced all remaining use of MD5 and SHA-1 hashes with SHA-256 hashes, without removing the MD5 and SHA-1 Pascal Scripting and ISPP support functions.</li>
   <li>Replaced all remaining use of MD5 and SHA-1 hashes with SHA-256 hashes, without removing the MD5 and SHA-1 Pascal Scripting and ISPP support functions.</li>
   <li>Merged the Inno Setup Preprocessor documentation into the main documentation instead of being separate.</li>
   <li>Merged the Inno Setup Preprocessor documentation into the main documentation instead of being separate.</li>
   <li>Added a dark mode version of the documentation, automatically used by the Compiler IDE if a dark theme is chosen.</li>
   <li>Added a dark mode version of the documentation, automatically used by the Compiler IDE if a dark theme is chosen.</li>