diff --git a/Duplicati/Library/Encryption/AESEncryption.cs b/Duplicati/Library/Encryption/AESEncryption.cs
index 8d5c12bf2..804b6609b 100644
--- a/Duplicati/Library/Encryption/AESEncryption.cs
+++ b/Duplicati/Library/Encryption/AESEncryption.cs
@@ -1,24 +1,26 @@
-// Copyright (C) 2025, The Duplicati Team
-// https://duplicati.com, hello@duplicati.com
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// Copyright (C) 2025, The Duplicati Team
+// https://duplicati.com, hello@duplicati.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -31,6 +33,23 @@ namespace Duplicati.Library.Encryption
///
public class AESEncryption : EncryptionBase
{
+ ///
+ /// Toggles ignoring AES padding bytes; NOTE: this is not exposed as an option, but can be set using the environment variable DUPLICATI__AES_IGNORE_PADDING_BYTES
+ ///
+ private const string KEY_AES_IGNORE_PADDING_BYTES = "aes-ignore-padding-bytes";
+ ///
+ /// The key used to define the AES stream format
+ ///
+ private const string KEY_AES_VERSION = "aes-version";
+ ///
+ /// The key used to define the number of iterations used for the AES stream format v3
+ ///
+ private const string KEY_AES_V3_ITERATIONS = "aes-v3-iterations";
+ ///
+ /// The key used to define if the AES output should use a minimal header
+ ///
+ private const string KEY_AES_MINIMAL_HEADER = "aes-minimal-header";
+
///
/// The key used to encrypt the data
///
@@ -41,15 +60,10 @@ namespace Duplicati.Library.Encryption
///
private static long m_cachedsizeoverhead = -1;
- ///
- /// Cached set of options for minimal header
- ///
- private static readonly SharpAESCrypt.EncryptionOptions m_minimalHeaderOptions = new(InsertCreatedByIdentifier: false, InsertTimeStamp: false, InsertPlaceholder: false);
-
///
/// Cached set of options for decryption
///
- private static readonly SharpAESCrypt.DecryptionOptions m_decryptionOptions = new(IgnorePaddingBytes: Environment.GetEnvironmentVariable("AES_IGNORE_PADDING_BYTES") == "1");
+ private readonly SharpAESCrypt.DecryptionOptions m_decryptionOptions;
///
/// Options to use for encryption
@@ -61,6 +75,9 @@ namespace Duplicati.Library.Encryption
///
public AESEncryption()
{
+ m_key = null!;
+ m_encryptionOptions = default!;
+ m_decryptionOptions = default!;
}
///
@@ -69,22 +86,79 @@ namespace Duplicati.Library.Encryption
/// The passphrase to use
/// Flag controlling if the encryption is done with a minimal header
public AESEncryption(string passphrase, bool minimalheader)
+ : this(passphrase, new Dictionary() { { KEY_AES_MINIMAL_HEADER, minimalheader.ToString() } })
{
- if (string.IsNullOrEmpty(passphrase))
- throw new ArgumentException(Strings.AESEncryption.EmptyKeyError, nameof(passphrase));
-
- m_key = passphrase;
- m_encryptionOptions = minimalheader ? m_minimalHeaderOptions : default;
}
///
/// Constructs a new AES encryption/decyption instance
///
- public AESEncryption(string passphrase, Dictionary options)
- : this(passphrase, false)
+ public AESEncryption(string passphrase, IReadOnlyDictionary options)
{
+ if (string.IsNullOrEmpty(passphrase))
+ throw new ArgumentException(Strings.AESEncryption.EmptyKeyError, nameof(passphrase));
+
+ m_key = passphrase;
+ var encOpts = SharpAESCrypt.EncryptionOptions.Default;
+ var decOpts = SharpAESCrypt.DecryptionOptions.Default;
+
+ int? version = null;
+ int? iterations = null;
+
+ var minimalHeader = Utility.Utility.ParseBool(
+ options.GetValueOrDefault(KEY_AES_MINIMAL_HEADER),
+ Utility.Utility.ParseBool(
+ GetEnvValue(KEY_AES_MINIMAL_HEADER),
+ false
+ )
+ );
+
+ var ignorePaddingBytes = Utility.Utility.ParseBool(
+ options.GetValueOrDefault(KEY_AES_IGNORE_PADDING_BYTES),
+ Utility.Utility.ParseBool(
+ GetEnvValue(KEY_AES_IGNORE_PADDING_BYTES),
+ false
+ )
+ );
+
+ if (int.TryParse(GetEnvValue(KEY_AES_VERSION), out var tempInt))
+ version = tempInt;
+ if (int.TryParse(options.GetValueOrDefault(KEY_AES_VERSION), out tempInt))
+ version = tempInt;
+
+ if (int.TryParse(GetEnvValue(KEY_AES_V3_ITERATIONS), out tempInt))
+ iterations = tempInt;
+ if (int.TryParse(options.GetValueOrDefault(KEY_AES_V3_ITERATIONS), out tempInt))
+ iterations = tempInt;
+
+
+ if (minimalHeader)
+ encOpts = encOpts with
+ {
+ InsertCreatedByIdentifier = false,
+ InsertTimeStamp = false,
+ InsertPlaceholder = false
+ };
+
+ if (version.HasValue)
+ encOpts = encOpts with { FileVersion = (byte)version.Value };
+ if (iterations.HasValue)
+ encOpts = encOpts with { KdfIterations = iterations.Value };
+ if (ignorePaddingBytes)
+ decOpts = decOpts with { IgnorePaddingBytes = true };
+
+ m_encryptionOptions = encOpts;
+ m_decryptionOptions = decOpts;
}
+ ///
+ /// Gets the environment variable value for an option
+ ///
+ /// The key to get the value for
+ /// The value
+ private static string? GetEnvValue(string key)
+ => Environment.GetEnvironmentVariable("DUPLICATI__" + key.ToUpperInvariant().Replace('-', '_'));
+
#region IEncryption Members
///
@@ -145,19 +219,31 @@ namespace Duplicati.Library.Encryption
/// Gets a list of supported commandline arguments
///
/// The supported commands.
- public override IList SupportedCommands
- => new List([
- new CommandLineArgument(
- "aes-set-threadlevel",
- CommandLineArgument.ArgumentType.String,
- Strings.AESEncryption.AessetthreadlevelShort,
- Strings.AESEncryption.AessetthreadlevelLong,
- "",
- null,
- null,
- Strings.AESEncryption.AessetthreadlevelDeprecated
- ),
- ]);
+ public override IList SupportedCommands => [
+ new CommandLineArgument(
+ KEY_AES_VERSION,
+ CommandLineArgument.ArgumentType.Enumeration,
+ Strings.AESEncryption.AesversionShort,
+ Strings.AESEncryption.AesversionLong,
+ SharpAESCrypt.EncryptionOptions.Default.FileVersion.ToString(),
+ null,
+ ["2", "3"]
+ ),
+ new CommandLineArgument(
+ KEY_AES_V3_ITERATIONS,
+ CommandLineArgument.ArgumentType.Integer,
+ Strings.AESEncryption.Aesv3iterationsShort,
+ Strings.AESEncryption.Aesv3iterationsLong,
+ SharpAESCrypt.EncryptionOptions.Default.KdfIterations.ToString()
+ ),
+ new CommandLineArgument(
+ KEY_AES_MINIMAL_HEADER,
+ CommandLineArgument.ArgumentType.Boolean,
+ Strings.AESEncryption.AesminimalheaderShort,
+ Strings.AESEncryption.AesminimalheaderLong,
+ "false"
+ )
+ ];
#endregion
}
diff --git a/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj b/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj
index 768e514ab..08f9db26b 100644
--- a/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj
+++ b/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/Duplicati/Library/Encryption/Strings.cs b/Duplicati/Library/Encryption/Strings.cs
index e5677f4af..f035693a8 100644
--- a/Duplicati/Library/Encryption/Strings.cs
+++ b/Duplicati/Library/Encryption/Strings.cs
@@ -31,6 +31,12 @@ namespace Duplicati.Library.Encryption.Strings
public static string AessetthreadlevelLong { get { return LC.L(@"Use this option to set the thread level allowed for AES crypt operations."); } }
public static string AessetthreadlevelShort { get { return LC.L(@"Set thread level utilized for crypting"); } }
public static string AessetthreadlevelDeprecated { get { return LC.L(@"The option --{0} is no longer used and has been deprecated.", "aes-set-threadlevel"); } }
+ public static string AesversionShort { get { return LC.L(@"Set the AES file format version"); } }
+ public static string AesversionLong { get { return LC.L(@"Use this option to set the AES encryption file format version."); } }
+ public static string Aesv3iterationsShort { get { return LC.L(@"Set the number of iterations used for AES v3 key derivation"); } }
+ public static string Aesv3iterationsLong { get { return LC.L(@"Use this option to set the number of iterations used for AES v3 encryption. The default is using the library default."); } }
+ public static string AesminimalheaderShort { get { return LC.L(@"Use minimal header for AES encryption"); } }
+ public static string AesminimalheaderLong { get { return LC.L(@"Use this option to use a minimal header for AES encryption. This will reduce the size of the encrypted file, but will remove some additional metadata that is normally stored in the header."); } }
}
internal static class EncryptionBase
{