-
-
Notifications
You must be signed in to change notification settings - Fork 969
Support AesGcm cipher #1364
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
Closed
Closed
Support AesGcm cipher #1364
Changes from 10 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
8b5c819
Init AeadCipher
scott-xu 4d160ca
Move AeadCipher to parent folder. Move EncryptBlock/DecryptBlock from…
scott-xu 7546a5a
simplify parameter name
scott-xu 270fb96
Implement AesGcmCipher
scott-xu 7a6b334
Merge branch 'develop' into aes-gcm-and-chacha20-poly1305
scott-xu 1059b68
Update README
scott-xu fe26b65
Remove protected IV from AeadCipher; Set offset to outbound sequence …
scott-xu 1b92a6a
Rename associatedData to packetLengthField
scott-xu 7377cbe
Use Span<byte> to avoid unnecessary allocations
scott-xu cdbe822
Use `Span` to improve performance when `IncrementCounter()`
scott-xu 65ad16a
Add `IsAead` property to `CipherInfo`. Include packet length field an…
scott-xu 3336d3d
Fix build
scott-xu 37a16bf
Fix UT
scott-xu fdc8f49
Merge branch 'develop' into aes-gcm-and-chacha20-poly1305
scott-xu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -388,6 +388,10 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy | |
| { "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)) }, | ||
| #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER | ||
| { "[email protected]", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv)) }, | ||
| { "[email protected]", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv)) }, | ||
| #endif | ||
| { "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)) }, | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| namespace Renci.SshNet.Security.Cryptography | ||
| { | ||
| /// <summary> | ||
| /// Represents algorithm for Authenticated Encryption with Associated data. | ||
| /// </summary> | ||
| public abstract class AeadCipher : SymmetricCipher | ||
| { | ||
| /// <summary> | ||
| /// Gets the size of the block in bytes. | ||
| /// </summary> | ||
| /// <value> | ||
| /// The size of the block in bytes. | ||
| /// </value> | ||
| private readonly byte _blockSize; | ||
|
|
||
| /// <summary> | ||
| /// Gets the tag size in bytes. | ||
| /// </summary> | ||
| public int TagSize { get; } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override byte MinimumSize | ||
| { | ||
| get { return _blockSize; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="AeadCipher"/> class. | ||
| /// </summary> | ||
| /// <param name="key">The key.</param> | ||
| /// <param name="tagSize">The tag size in bytes.</param> | ||
| protected AeadCipher(byte[] key, int tagSize) | ||
| : base(key) | ||
| { | ||
| _blockSize = 16; | ||
| TagSize = tagSize; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER | ||
| using System; | ||
| using System.Buffers.Binary; | ||
| using System.Security.Cryptography; | ||
|
|
||
| using Renci.SshNet.Common; | ||
|
|
||
| namespace Renci.SshNet.Security.Cryptography.Ciphers | ||
| { | ||
| /// <summary> | ||
| /// AES GCM cipher implementation. | ||
| /// <see href="https://datatracker.ietf.org/doc/html/rfc5647"/>. | ||
| /// </summary> | ||
| public sealed class AesGcmCipher : AeadCipher, IDisposable | ||
| { | ||
| private readonly byte[] _nonce; | ||
| private readonly AesGcm _aesGcm; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="AesGcmCipher"/> class. | ||
| /// </summary> | ||
| /// <param name="key">The key.</param> | ||
| /// <param name="iv">The IV.</param> | ||
| public AesGcmCipher(byte[] key, byte[] iv) | ||
| : base(key, tagSize: 16) | ||
| { | ||
| _nonce = iv.Take(12); | ||
| #if NET8_0_OR_GREATER | ||
| _aesGcm = new AesGcm(key, TagSize); | ||
| #else | ||
| _aesGcm = new AesGcm(key); | ||
| #endif | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override byte[] Encrypt(byte[] input, int offset, int length) | ||
| { | ||
| // [outbound sequence][packet length field][padding length field sz][payload][random paddings] | ||
| // [--4 bytes(offset)][------4 bytes------][-------------------Plain Text--------------------] | ||
| var packetLengthField = new ReadOnlySpan<byte>(input, offset, 4); | ||
| var plainText = new ReadOnlySpan<byte>(input, offset + 4, length - 4); | ||
|
|
||
| var result = new byte[length + TagSize]; | ||
| packetLengthField.CopyTo(result); | ||
| var cipherText = new Span<byte>(result, 4, length - 4); | ||
| var tag = new Span<byte>(result, length, TagSize); | ||
|
|
||
| _aesGcm.Encrypt(_nonce, plainText, cipherText, tag, packetLengthField); | ||
|
|
||
| IncrementCounter(); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override byte[] Decrypt(byte[] input, int offset, int length) | ||
| { | ||
| // [inbound sequence][packet length field][padding length field sz][payload][random paddings][Authenticated TAG] | ||
| // [-----4 bytes----][----4 bytes(offset)][------------------Cipher Text--------------------][-------TAG-------] | ||
| var packetLengthField = new ReadOnlySpan<byte>(input, offset - 4, 4); | ||
| var cipherText = new ReadOnlySpan<byte>(input, offset, length); | ||
| var tag = new ReadOnlySpan<byte>(input, offset + length, TagSize); | ||
|
||
|
|
||
| var plainText = new byte[length]; | ||
|
|
||
| _aesGcm.Decrypt(_nonce, cipherText, tag, plainText, packetLengthField); | ||
|
|
||
| IncrementCounter(); | ||
|
|
||
| return plainText; | ||
| } | ||
|
|
||
| private void IncrementCounter() | ||
| { | ||
| var invocationCounter = new Span<byte>(_nonce, 4, 8); | ||
| var count = BinaryPrimitives.ReadUInt64BigEndian(invocationCounter); | ||
| BinaryPrimitives.WriteUInt64BigEndian(invocationCounter, count + 1); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Dispose the instance. | ||
| /// </summary> | ||
| /// <param name="disposing">Set to True to dispose of resouces.</param> | ||
| public void Dispose(bool disposing) | ||
| { | ||
| if (disposing) | ||
| { | ||
| _aesGcm.Dispose(); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public void Dispose() | ||
| { | ||
| // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||
| Dispose(disposing: true); | ||
| GC.SuppressFinalize(this); | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite unpleasant/unintuitive to assume there is data to process in
inputbeforeoffset. Is there a nicer way to do it?Could packet_length (i.e. the associated data) start at
offset?At the very least, there should be some explicit
///xmldoc here explaining the usage.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your concern. Typically, the cipher only decrypts "cipher text" which starts from offset and ends before MAC (tag). However,
AesGcmrequires the packet_length (aad) and MAC (tag) to do the decryption and authentication which is before and after the cipher text.The
Decrypt(...)method ofAesGcmis called (and only called) from https://github.com/sshnet/SSH.NET/blob/develop/src/Renci.SshNet/Session.cs#L1302 which guarantees the data layout.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, adding a bounds check could be a good thing for future proofing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some options here:
Sessionclass, treat aead cipher as special and set offset before packet_length and include MAC(tag) in lengthSessionclass, treat aead cipher as special and set offset to 0 and include all in length (consider that ChaCha20Poly1305 requires sequence block if I read the spec correctly)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checked the source code of the constructor about
ReadOnlySpan<T>. It does check the bounds:startto ulong/uint to make sure it is positive.lengthto no larger than array length - offset.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I prefer option1, because it is consistent for all kinds of ciphers. No special handing in
Sessionclass. The current implementation in #1369 is option2. It defers the issue when addChaCha20Poly1305in the future.Life will be much easier if there's no such many options 🙃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed back to option1 with some tiny enhancements in the latest commit.