Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AEAD AES 128/256 GCM Ciphers (.NET 6.0 onward only) #1369

Merged
merged 31 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8b5c819
Init AeadCipher
scott-xu Mar 27, 2024
4d160ca
Move AeadCipher to parent folder. Move EncryptBlock/DecryptBlock from…
scott-xu Mar 27, 2024
7546a5a
simplify parameter name
scott-xu Mar 27, 2024
270fb96
Implement AesGcmCipher
scott-xu Mar 27, 2024
7a6b334
Merge branch 'develop' into aes-gcm-and-chacha20-poly1305
scott-xu Mar 27, 2024
1059b68
Update README
scott-xu Mar 28, 2024
fe26b65
Remove protected IV from AeadCipher; Set offset to outbound sequence …
scott-xu Mar 31, 2024
1b92a6a
Rename associatedData to packetLengthField
scott-xu Mar 31, 2024
7377cbe
Use Span<byte> to avoid unnecessary allocations
scott-xu Mar 31, 2024
cdbe822
Use `Span` to improve performance when `IncrementCounter()`
scott-xu Mar 31, 2024
65ad16a
Add `IsAead` property to `CipherInfo`. Include packet length field an…
scott-xu Apr 2, 2024
3336d3d
Fix build
scott-xu Apr 2, 2024
37a16bf
Fix UT
scott-xu Apr 2, 2024
7debca4
Merge branch 'develop' into aesgcm
scott-xu Apr 4, 2024
e727959
Check `AesGcm.IsSupported` before add to the `Encryptions` collection.
scott-xu Apr 4, 2024
b0ccb0c
Merge branch 'aesgcm' of https://github.com/scott-xu/SSH.NET into aesgcm
scott-xu Apr 4, 2024
6bdbfc0
Merge branch 'develop' into aesgcm
scott-xu Apr 4, 2024
e8ed4a2
Suppress CA1859 "Use concrete types when possible for improved perfor…
scott-xu Apr 4, 2024
4228014
Merge branch 'develop' into aesgcm
scott-xu Apr 4, 2024
7a24090
Merge branch 'develop' into aesgcm
scott-xu Apr 7, 2024
c532157
Update xml doc comments. Do not treat AesGcmCipher separately in Sess…
scott-xu Apr 8, 2024
a831f3d
Merge branch 'aesgcm' of https://github.com/scott-xu/SSH.NET into aesgcm
scott-xu Apr 8, 2024
299a3f6
Fix build
scott-xu Apr 8, 2024
ebeb582
Fix build
scott-xu Apr 8, 2024
46f9a6d
Update the comment as ChaCha20Poly1305 uses a separated key to encypt…
scott-xu Apr 9, 2024
8aaabc4
Update src/Renci.SshNet/Security/Cryptography/Cipher.cs
scott-xu Apr 9, 2024
0922306
Make `AesGcmCipher` internal.
scott-xu Apr 9, 2024
71d5635
Merge branch 'aesgcm' of https://github.com/scott-xu/SSH.NET into aesgcm
scott-xu Apr 9, 2024
fb7884b
Fix nullable error in build
Rob-Hague Apr 17, 2024
12b7c38
Merge branch 'fixbuild' into aesgcm
Rob-Hague Apr 18, 2024
ec34f71
typos
Rob-Hague Apr 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ the missing test once you figure things out. 🤓
* aes128-cbc
* aes192-cbc
* aes256-cbc
* aes128-gcm<span></span>@openssh.com (.NET 6 and higher)
* aes256-gcm<span></span>@openssh.com (.NET 6 and higher)
* blowfish-cbc
* twofish-cbc
* twofish192-cbc
Expand Down
12 changes: 11 additions & 1 deletion src/Renci.SshNet/CipherInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public class CipherInfo
/// </value>
public int KeySize { get; private set; }

/// <summary>
/// Gets a value indicating whether the cipher is AEAD (Authenticated Encryption with Associated data).
/// </summary>
/// <value>
/// <see langword="true"/> to indicate the cipher is AEAD, <see langword="false"/> to incidicate the cipher is not AEAD.
/// </value>
public bool IsAead { get; private set; }

/// <summary>
/// Gets the cipher.
/// </summary>
Expand All @@ -27,10 +35,12 @@ public class CipherInfo
/// </summary>
/// <param name="keySize">Size of the key.</param>
/// <param name="cipher">The cipher.</param>
public CipherInfo(int keySize, Func<byte[], byte[], Cipher> cipher)
/// <param name="isAead"><see langword="true"/> to indicate the cipher is AEAD, <see langword="false"/> to incidicate the cipher is not AEAD.</param>
public CipherInfo(int keySize, Func<byte[], byte[], Cipher> cipher, bool isAead = false)
{
KeySize = keySize;
Cipher = (key, iv) => cipher(key.Take(KeySize / 8), iv);
IsAead = isAead;
}
}
}
45 changes: 26 additions & 19 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ public class ConnectionInfo : IConnectionInfoInternal
/// <summary>
/// Gets supported encryptions for this connection.
/// </summary>
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
Rob-Hague marked this conversation as resolved.
Show resolved Hide resolved
public IDictionary<string, CipherInfo> Encryptions { get; private set; }
#pragma warning restore CA1859 // Use concrete types when possible for improved performance

/// <summary>
/// Gets supported hash algorithms for this connection.
Expand Down Expand Up @@ -380,25 +382,30 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
{ "diffie-hellman-group1-sha1", () => new KeyExchangeDiffieHellmanGroup1Sha1() },
};

Encryptions = new Dictionary<string, CipherInfo>
{
{ "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) },
{ "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
{ "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
{ "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) },
{ "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "blowfish-cbc", new CipherInfo(128, (key, iv) => new BlowfishCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "twofish-cbc", new CipherInfo(256, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "twofish192-cbc", new CipherInfo(192, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "twofish128-cbc", new CipherInfo(128, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "twofish256-cbc", new CipherInfo(256, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)) },
{ "arcfour", new CipherInfo(128, (key, iv) => new Arc4Cipher(key, dischargeFirstBytes: false)) },
{ "arcfour128", new CipherInfo(128, (key, iv) => new Arc4Cipher(key, dischargeFirstBytes: true)) },
{ "arcfour256", new CipherInfo(256, (key, iv) => new Arc4Cipher(key, dischargeFirstBytes: true)) },
{ "cast128-cbc", new CipherInfo(128, (key, iv) => new CastCipher(key, new CbcCipherMode(iv), padding: null)) },
};
Encryptions = new Dictionary<string, CipherInfo>();
Encryptions.Add("aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)));
Encryptions.Add("aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)));
Encryptions.Add("aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)));
#if NET6_0_OR_GREATER
if (AesGcm.IsSupported)
{
Encryptions.Add("aes128-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv), isAead: true));
Encryptions.Add("aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv), isAead: true));
}
#endif
Encryptions.Add("aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)));
Encryptions.Add("aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)));
Encryptions.Add("aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)));
Encryptions.Add("3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null)));
Encryptions.Add("blowfish-cbc", new CipherInfo(128, (key, iv) => new BlowfishCipher(key, new CbcCipherMode(iv), padding: null)));
Encryptions.Add("twofish-cbc", new CipherInfo(256, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)));
Encryptions.Add("twofish192-cbc", new CipherInfo(192, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)));
Encryptions.Add("twofish128-cbc", new CipherInfo(128, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)));
Encryptions.Add("twofish256-cbc", new CipherInfo(256, (key, iv) => new TwofishCipher(key, new CbcCipherMode(iv), padding: null)));
Encryptions.Add("arcfour", new CipherInfo(128, (key, iv) => new Arc4Cipher(key, dischargeFirstBytes: false)));
Encryptions.Add("arcfour128", new CipherInfo(128, (key, iv) => new Arc4Cipher(key, dischargeFirstBytes: true)));
Encryptions.Add("arcfour256", new CipherInfo(256, (key, iv) => new Arc4Cipher(key, dischargeFirstBytes: true)));
Encryptions.Add("cast128-cbc", new CipherInfo(128, (key, iv) => new CastCipher(key, new CbcCipherMode(iv), padding: null)));

#pragma warning disable IDE0200 // Remove unnecessary lambda expression; We want to prevent instantiating the HashAlgorithm objects.
HmacAlgorithms = new Dictionary<string, HashInfo>
Expand Down
10 changes: 5 additions & 5 deletions src/Renci.SshNet/Messages/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected override void WriteBytes(SshDataStream stream)
base.WriteBytes(stream);
}

internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool isEncryptThenMAC = false)
internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool excludePacketLengthFieldWhenPadding = false)
{
const int outboundPacketSequenceSize = 4;

Expand Down Expand Up @@ -78,9 +78,9 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool is
var packetLength = messageLength + 4 + 1;

// determine the padding length
// in Encrypt-then-MAC mode, the length field is not encrypted, so we should keep it out of the
// in Encrypt-then-MAC mode or AEAD, the length field is not encrypted, so we should keep it out of the
// padding length calculation
var paddingLength = GetPaddingLength(paddingMultiplier, isEncryptThenMAC ? packetLength - 4 : packetLength);
var paddingLength = GetPaddingLength(paddingMultiplier, excludePacketLengthFieldWhenPadding ? packetLength - 4 : packetLength);

// add padding bytes
var paddingBytes = new byte[paddingLength];
Expand All @@ -106,9 +106,9 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool is
var packetLength = messageLength + 4 + 1;

// determine the padding length
// in Encrypt-then-MAC mode, the length field is not encrypted, so we should keep it out of the
// in Encrypt-then-MAC mode or AEAD, the length field is not encrypted, so we should keep it out of the
// padding length calculation
var paddingLength = GetPaddingLength(paddingMultiplier, isEncryptThenMAC ? packetLength - 4 : packetLength);
var paddingLength = GetPaddingLength(paddingMultiplier, excludePacketLengthFieldWhenPadding ? packetLength - 4 : packetLength);

var packetDataLength = GetPacketDataLength(messageLength, paddingLength);

Expand Down
38 changes: 26 additions & 12 deletions src/Renci.SshNet/Security/Cryptography/BlockCipher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,6 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
return output;
}

/// <summary>
/// Decrypts the specified data.
/// </summary>
/// <param name="input">The data.</param>
/// <returns>
/// The decrypted data.
/// </returns>
public override byte[] Decrypt(byte[] input)
{
return Decrypt(input, 0, input.Length);
}

/// <summary>
/// Decrypts the specified input.
/// </summary>
Expand Down Expand Up @@ -167,5 +155,31 @@ public override byte[] Decrypt(byte[] input, int offset, int length)

return output;
}

/// <summary>
/// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array.
/// </summary>
/// <param name="inputBuffer">The input data to encrypt.</param>
/// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
/// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
/// <param name="outputBuffer">The output to which to write encrypted data.</param>
/// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
/// <returns>
/// The number of bytes encrypted.
/// </returns>
public abstract int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);

/// <summary>
/// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array.
/// </summary>
/// <param name="inputBuffer">The input data to decrypt.</param>
/// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
/// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
/// <param name="outputBuffer">The output to which to write decrypted data.</param>
/// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
/// <returns>
/// The number of bytes decrypted.
/// </returns>
public abstract int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
}
}
14 changes: 13 additions & 1 deletion src/Renci.SshNet/Security/Cryptography/Cipher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ public abstract class Cipher
/// </value>
public abstract byte MinimumSize { get; }

/// <summary>
/// Gets the size of the authentication tag for ciphers which implement Authenticated Encryption (AE).
/// </summary>
/// <returns>
/// When this <see cref="Cipher"/> implements Authenticated Encryption, the size, in bytes,
/// of the authentication tag included in the encrypted message.
/// </returns>
public virtual int TagSize { get; }
scott-xu marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Encrypts the specified input.
/// </summary>
Expand Down Expand Up @@ -41,7 +50,10 @@ public byte[] Encrypt(byte[] input)
/// <returns>
/// The decrypted data.
/// </returns>
public abstract byte[] Decrypt(byte[] input);
public byte[] Decrypt(byte[] input)
{
return Decrypt(input, 0, input.Length);
}

/// <summary>
/// Decrypts the specified input.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
/// </summary>
public enum AesCipherMode
{
/// <summary>CBC Mode.</summary>
/// <summary>Cipher Block Chain Mode.</summary>
CBC = 1,

/// <summary>ECB Mode.</summary>
/// <summary>Electronic Codebook Mode.</summary>
ECB = 2,

/// <summary>OFB Mode.</summary>
/// <summary>Output Feedback Mode.</summary>
OFB = 3,

/// <summary>CFB Mode.</summary>
/// <summary>Cipher Feedback Mode.</summary>
CFB = 4,

/// <summary>CTS Mode.</summary>
/// <summary>Cipher Text Stealing Mode.</summary>
CTS = 5,

/// <summary>CTR Mode.</summary>
/// <summary>Counter Mode.</summary>
CTR = 6
}
}
Loading