2026-04-17 12:34:49 +02:00
// Copyright (C) 2026, The Duplicati Team
2026-03-13 17:20:33 +01:00
// 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
2024-06-04 17:27:18 +02:00
// DEALINGS IN THE SOFTWARE.
2024-02-28 15:45:30 +01:00
2026-03-13 17:20:33 +01:00
#nullable enable
2009-01-14 17:29:30 +00:00
using System ;
using System.Collections.Generic ;
2010-06-19 21:08:21 +00:00
using System.IO ;
2019-07-26 09:18:16 -04:00
using Duplicati.Library.Interface ;
2009-01-14 17:29:30 +00:00
namespace Duplicati.Library.Encryption
{
/// <summary>
/// Implements AES encryption
/// </summary>
2013-05-05 17:07:24 +02:00
public class AESEncryption : EncryptionBase
2009-01-14 17:29:30 +00:00
{
2026-03-13 17:20:33 +01:00
/// <summary>
/// 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
/// </summary>
private const string KEY_AES_IGNORE_PADDING_BYTES = "aes-ignore-padding-bytes" ;
/// <summary>
/// The key used to define the AES stream format
/// </summary>
private const string KEY_AES_VERSION = "aes-version" ;
/// <summary>
/// The key used to define the number of iterations used for the AES stream format v3
/// </summary>
private const string KEY_AES_V3_ITERATIONS = "aes-v3-iterations" ;
/// <summary>
/// The key used to define if the AES output should use a minimal header
/// </summary>
private const string KEY_AES_MINIMAL_HEADER = "aes-minimal-header" ;
2010-06-19 21:08:21 +00:00
/// <summary>
/// The key used to encrypt the data
/// </summary>
2024-08-20 16:52:26 +02:00
private readonly string m_key ;
2010-06-19 21:08:21 +00:00
2016-02-25 19:17:23 +01:00
/// <summary>
/// The cached value for size overhead
/// </summary>
private static long m_cachedsizeoverhead = - 1 ;
2024-08-20 16:52:26 +02:00
/// <summary>
/// Cached set of options for decryption
/// </summary>
2026-03-13 17:20:33 +01:00
private readonly SharpAESCrypt . DecryptionOptions m_decryptionOptions ;
2024-08-20 16:52:26 +02:00
/// <summary>
/// Options to use for encryption
/// </summary>
private readonly SharpAESCrypt . EncryptionOptions m_encryptionOptions ;
2010-06-19 21:08:21 +00:00
/// <summary>
/// Default constructor, used to read file extension and supported commands
/// </summary>
public AESEncryption ()
{
2026-03-13 17:20:33 +01:00
m_key = null !;
m_encryptionOptions = default !;
m_decryptionOptions = default !;
2010-06-19 21:08:21 +00:00
}
2009-01-14 17:29:30 +00:00
/// <summary>
/// Constructs a new AES encryption/decyption instance
/// </summary>
2024-08-20 16:52:26 +02:00
/// <param name="passphrase">The passphrase to use</param>
/// <param name="minimalheader">Flag controlling if the encryption is done with a minimal header</param>
public AESEncryption ( string passphrase , bool minimalheader )
2026-03-13 17:20:33 +01:00
: this ( passphrase , new Dictionary < string , string >() { { KEY_AES_MINIMAL_HEADER , minimalheader . ToString () } })
{
}
/// <summary>
/// Constructs a new AES encryption/decyption instance
/// </summary>
public AESEncryption ( string passphrase , IReadOnlyDictionary < string , string > options )
2009-01-14 17:29:30 +00:00
{
2019-07-26 09:18:16 -04:00
if ( string . IsNullOrEmpty ( passphrase ))
2017-09-25 19:43:26 -07:00
throw new ArgumentException ( Strings . AESEncryption . EmptyKeyError , nameof ( passphrase ));
2016-03-03 09:24:29 +01:00
2010-06-19 21:08:21 +00:00
m_key = passphrase ;
2026-03-13 17:20:33 +01:00
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
};
2026-05-04 16:19:56 +02:00
// Until the next stable release, use version 2 by default
if ( version == null )
version = 2 ;
2026-03-13 17:20:33 +01:00
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 ;
2024-08-20 16:52:26 +02:00
}
/// <summary>
2026-03-13 17:20:33 +01:00
/// Gets the environment variable value for an option
2024-08-20 16:52:26 +02:00
/// </summary>
2026-03-13 17:20:33 +01:00
/// <param name="key">The key to get the value for</param>
/// <returns>The value</returns>
private static string? GetEnvValue ( string key )
=> Environment . GetEnvironmentVariable ( "DUPLICATI__" + key . ToUpperInvariant (). Replace ( '-' , '_' ));
2010-06-19 21:08:21 +00:00
2009-01-14 17:29:30 +00:00
#region IEncryption Members
2016-02-25 19:17:23 +01:00
/// <summary>
/// The extension that the encryption implementation adds to the filename
/// </summary>
/// <value>The filename extension.</value>
2009-01-25 19:33:36 +00:00
public override string FilenameExtension { get { return "aes" ; } }
2016-02-25 19:17:23 +01:00
/// <summary>
/// A localized description of the encryption module
/// </summary>
/// <value>The description.</value>
2011-01-12 18:59:01 +00:00
public override string Description { get { return string . Format ( Strings . AESEncryption . Description_v2 ); } }
2016-02-25 19:17:23 +01:00
/// <summary>
/// A localized string describing the encryption module with a friendly name
/// </summary>
/// <value>The display name.</value>
2010-06-19 21:08:21 +00:00
public override string DisplayName { get { return Strings . AESEncryption . DisplayName ; } }
2016-02-25 19:17:23 +01:00
/// <summary>
/// Dispose the specified disposing.
/// </summary>
/// <param name="disposing">If set to <c>true</c> disposing.</param>
2024-08-20 16:52:26 +02:00
protected override void Dispose ( bool disposing ) { }
2010-06-19 21:08:21 +00:00
2016-02-25 19:17:23 +01:00
/// <summary>
/// Returns the size in bytes of the overhead that will be added to a file of the given size when encrypted
/// </summary>
/// <param name="filesize">The size of the file to encrypt</param>
/// <returns>The size of the overhead in bytes</returns>
2010-06-19 21:08:21 +00:00
public override long SizeOverhead ( long filesize )
{
2016-02-25 19:17:23 +01:00
if ( m_cachedsizeoverhead != - 1 )
return m_cachedsizeoverhead ;
2019-07-26 09:18:16 -04:00
2010-06-19 21:08:21 +00:00
//If we use 1, we trigger the blocksize.
//As the AES algorithm does not alter the size,
// the results are the same as for the real size,
// but a single byte encryption is much faster.
2016-02-25 19:17:23 +01:00
return m_cachedsizeoverhead = base . SizeOverhead ( 1 );
2010-06-19 21:08:21 +00:00
}
2016-02-25 19:17:23 +01:00
/// <summary>
/// Encrypts the stream
/// </summary>
/// <param name="input">The target stream</param>
/// <returns>An encrypted stream that can be written to</returns>
2010-06-19 21:08:21 +00:00
public override Stream Encrypt ( Stream input )
2024-08-20 16:52:26 +02:00
=> new SharpAESCrypt . EncryptingStream ( m_key , input , m_encryptionOptions );
2009-01-25 19:33:36 +00:00
2016-02-25 19:17:23 +01:00
/// <summary>
/// Decrypts the stream to the output stream
/// </summary>
/// <param name="input">The encrypted stream</param>
/// <returns>The unencrypted stream</returns>
2010-06-19 21:08:21 +00:00
public override Stream Decrypt ( Stream input )
2024-08-20 16:52:26 +02:00
=> new SharpAESCrypt . DecryptingStream ( m_key , input , m_decryptionOptions );
2009-01-14 17:29:30 +00:00
2016-02-25 19:17:23 +01:00
/// <summary>
/// Gets a list of supported commandline arguments
/// </summary>
/// <value>The supported commands.</value>
2026-03-13 17:20:33 +01:00
public override IList < ICommandLineArgument > 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"
)
];
2009-01-14 17:29:30 +00:00
2010-06-19 21:08:21 +00:00
#endregion
2009-01-14 17:29:30 +00:00
}
}