diff --git a/README.md b/README.md
index 8c4d8ece5..b3cc4ec1d 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ The main types provided by this library are:
* ECDSA 256/384/521 in OpenSSL PEM format ("BEGIN EC PRIVATE KEY")
* ECDSA 256/384/521, ED25519 and RSA in OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY")
-Private keys can be encrypted using one of the following cipher methods:
+Private keys in OpenSSL PEM and ssh.com format can be encrypted using one of the following cipher methods:
* DES-EDE3-CBC
* DES-EDE3-CFB
* DES-CBC
@@ -109,6 +109,18 @@ Private keys can be encrypted using one of the following cipher methods:
* AES-192-CBC
* AES-256-CBC
+Private keys in OpenSSH key format can be encrypted using one of the following cipher methods:
+* 3des-cbc
+* aes128-cbc
+* aes192-cbc
+* aes256-cbc
+* aes128-ctr
+* aes192-ctr
+* aes256-ctr
+* aes128-gcm@openssh.com
+* aes256-gcm@openssh.com
+* chacha20-poly1305@openssh.com
+
## Host Key Algorithms
**SSH.NET** supports the following host key algorithms:
diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs
index 51279f4a4..5ced89984 100644
--- a/src/Renci.SshNet/ConnectionInfo.cs
+++ b/src/Renci.SshNet/ConnectionInfo.cs
@@ -386,9 +386,9 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
{ "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-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv), isAead: true) },
- { "aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv), isAead: true) },
- { "chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key), isAead: true) },
+ { "aes128-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) },
+ { "aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) },
+ { "chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 4), isAead: true) },
{ "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)) },
diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs
index fbfd65408..bdc9a4c91 100644
--- a/src/Renci.SshNet/PrivateKeyFile.cs
+++ b/src/Renci.SshNet/PrivateKeyFile.cs
@@ -39,7 +39,7 @@ namespace Renci.SshNet
///
///
///
- /// The following encryption algorithms are supported:
+ /// The following encryption algorithms are supported for OpenSSL PEM and ssh.com format:
///
/// -
/// DES-EDE3-CBC
@@ -60,6 +60,39 @@ namespace Renci.SshNet
/// AES-256-CBC
///
///
+ /// The following encryption algorithms are supported for OpenSSH format:
+ ///
+ /// -
+ /// 3des-cbc
+ ///
+ /// -
+ /// aes128-cbc
+ ///
+ /// -
+ /// aes192-cbc
+ ///
+ /// -
+ /// aes256-cbc
+ ///
+ /// -
+ /// aes128-ctr
+ ///
+ /// -
+ /// aes192-ctr
+ ///
+ /// -
+ /// aes256-ctr
+ ///
+ /// -
+ /// aes128-gcm@openssh.com
+ ///
+ /// -
+ /// aes256-gcm@openssh.com
+ ///
+ /// -
+ /// chacha20-poly1305@openssh.com
+ ///
+ ///
///
///
public partial class PrivateKeyFile : IPrivateKeySource, IDisposable
@@ -450,7 +483,17 @@ private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, strin
var cipher = cipherInfo.Cipher(cipherKey.ToArray(), binarySalt);
- return cipher.Decrypt(cipherData);
+ try
+ {
+ return cipher.Decrypt(cipherData);
+ }
+ finally
+ {
+ if (cipher is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ }
}
///
@@ -474,7 +517,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
throw new SshException("This openssh key does not contain the 'openssh-key-v1' format magic header");
}
- // cipher will be "aes256-cbc" if using a passphrase, "none" otherwise
+ // cipher will be "aes256-cbc" or other cipher if using a passphrase, "none" otherwise
var cipherName = keyReader.ReadString(Encoding.UTF8);
// key derivation function (kdf): bcrypt or nothing
@@ -503,7 +546,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
// possibly encrypted private key
var privateKeyLength = (int)keyReader.ReadUInt32();
- var privateKeyBytes = keyReader.ReadBytes(privateKeyLength);
+ byte[] privateKeyBytes;
// decrypt private key if necessary
if (cipherName != "none")
@@ -518,38 +561,76 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
throw new SshException("kdf " + kdfName + " is not supported for openssh key file");
}
- // inspired by the SSHj library (https://github.com/hierynomus/sshj)
- // apply the kdf to derive a key and iv from the passphrase
- var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase);
- var keyiv = new byte[48];
- new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv);
- var key = new byte[32];
- Array.Copy(keyiv, 0, key, 0, 32);
- var iv = new byte[16];
- Array.Copy(keyiv, 32, iv, 0, 16);
-
- AesCipher cipher;
+ var ivLength = 16;
+ CipherInfo cipherInfo;
switch (cipherName)
{
+ case "3des-cbc":
+ ivLength = 8;
+ cipherInfo = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null));
+ break;
+ case "aes128-cbc":
+ cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
+ break;
+ case "aes192-cbc":
+ cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
+ break;
case "aes256-cbc":
- cipher = new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false);
+ cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false));
+ break;
+ case "aes128-ctr":
+ cipherInfo = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
+ break;
+ case "aes192-ctr":
+ cipherInfo = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
break;
case "aes256-ctr":
- cipher = new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false);
+ cipherInfo = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false));
+ break;
+ case "aes128-gcm@openssh.com":
+ cipherInfo = new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 0), isAead: true);
+ break;
+ case "aes256-gcm@openssh.com":
+ cipherInfo = new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 0), isAead: true);
+ break;
+ case "chacha20-poly1305@openssh.com":
+ ivLength = 12;
+ cipherInfo = new CipherInfo(256, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 0), isAead: true);
break;
default:
throw new SshException("Cipher '" + cipherName + "' is not supported for an OpenSSH key.");
}
+ var keyLength = cipherInfo.KeySize / 8;
+
+ // inspired by the SSHj library (https://github.com/hierynomus/sshj)
+ // apply the kdf to derive a key and iv from the passphrase
+ var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase);
+ var keyiv = new byte[keyLength + ivLength];
+ new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv);
+
+ var key = keyiv.Take(keyLength);
+ var iv = keyiv.Take(keyLength, ivLength);
+
+ var cipher = cipherInfo.Cipher(key, iv);
+ var cipherData = keyReader.ReadBytes(privateKeyLength + cipher.TagSize);
+
try
{
- privateKeyBytes = cipher.Decrypt(privateKeyBytes);
+ privateKeyBytes = cipher.Decrypt(cipherData, 0, privateKeyLength);
}
finally
{
- cipher.Dispose();
+ if (cipher is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
}
}
+ else
+ {
+ privateKeyBytes = keyReader.ReadBytes(privateKeyLength);
+ }
// validate private key length
privateKeyLength = privateKeyBytes.Length;
diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs
index fb0926c5c..b40c2343f 100644
--- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs
@@ -12,9 +12,9 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
///
internal sealed partial class AesGcmCipher : SymmetricCipher, IDisposable
{
- private const int PacketLengthFieldLength = 4;
private const int TagSizeInBytes = 16;
private readonly byte[] _iv;
+ private readonly int _aadLength;
#if NET6_0_OR_GREATER
private readonly Impl _impl;
#else
@@ -55,11 +55,13 @@ public override int TagSize
///
/// The key.
/// The IV.
- public AesGcmCipher(byte[] key, byte[] iv)
+ /// The length of additional associated data.
+ public AesGcmCipher(byte[] key, byte[] iv, int aadLength)
: base(key)
{
// SSH AES-GCM requires a 12-octet Initial IV
_iv = iv.Take(12);
+ _aadLength = aadLength;
#if NET6_0_OR_GREATER
if (System.Security.Cryptography.AesGcm.IsSupported)
{
@@ -78,8 +80,7 @@ public AesGcmCipher(byte[] key, byte[] iv)
///
/// The input data with below format:
///
- /// [outbound sequence field][packet length field][padding length field sz][payload][random paddings]
- /// [----4 bytes----(offset)][------4 bytes------][----------------Plain Text---------------(length)]
+ /// [----(offset)][----AAD----][----Plain Text----(length)]
///
///
/// The zero-based offset in at which to begin encrypting.
@@ -87,23 +88,22 @@ public AesGcmCipher(byte[] key, byte[] iv)
///
/// The encrypted data with below format:
///
- /// [packet length field][padding length field sz][payload][random paddings][Authenticated TAG]
- /// [------4 bytes------][------------------Cipher Text--------------------][-------TAG-------]
+ /// [----AAD----][----Cipher Text----][----TAG----]
///
///
public override byte[] Encrypt(byte[] input, int offset, int length)
{
var output = new byte[length + TagSize];
- Buffer.BlockCopy(input, offset, output, 0, PacketLengthFieldLength);
+ Buffer.BlockCopy(input, offset, output, 0, _aadLength);
_impl.Encrypt(
input,
- plainTextOffset: offset + PacketLengthFieldLength,
- plainTextLength: length - PacketLengthFieldLength,
+ plainTextOffset: offset + _aadLength,
+ plainTextLength: length - _aadLength,
associatedDataOffset: offset,
- associatedDataLength: PacketLengthFieldLength,
+ associatedDataLength: _aadLength,
output,
- cipherTextOffset: PacketLengthFieldLength);
+ cipherTextOffset: _aadLength);
IncrementCounter();
@@ -116,8 +116,7 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
///
/// The input data with below format:
///
- /// [inbound sequence field][packet length field][padding length field sz][payload][random paddings][Authenticated TAG]
- /// [--------4 bytes-------][--4 bytes--(offset)][--------------Cipher Text----------------(length)][-------TAG-------]
+ /// [----][----AAD----(offset)][----Cipher Text----(length)][----TAG----]
///
///
/// The zero-based offset in at which to begin decrypting and authenticating.
@@ -125,13 +124,12 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
///
/// The decrypted data with below format:
///
- /// [padding length field sz][payload][random paddings]
- /// [--------------------Plain Text-------------------]
+ /// [----Plain Text----]
///
///
public override byte[] Decrypt(byte[] input, int offset, int length)
{
- Debug.Assert(offset == 8, "The offset must be 8");
+ Debug.Assert(offset >= _aadLength, "The offset must be greater than or equals to aad length");
var output = new byte[length];
@@ -139,8 +137,8 @@ public override byte[] Decrypt(byte[] input, int offset, int length)
input,
cipherTextOffset: offset,
cipherTextLength: length,
- associatedDataOffset: offset - PacketLengthFieldLength,
- associatedDataLength: PacketLengthFieldLength,
+ associatedDataOffset: offset - _aadLength,
+ associatedDataLength: _aadLength,
output,
plainTextOffset: 0);
diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs
index e67eafc2e..5f63660c2 100644
--- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs
+++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs
@@ -18,9 +18,13 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers
///
internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher
{
- private readonly ChaCha7539Engine _aadCipher = new ChaCha7539Engine();
- private readonly ChaCha7539Engine _cipher = new ChaCha7539Engine();
- private readonly Poly1305 _mac = new Poly1305();
+ private readonly byte[] _iv;
+ private readonly int _aadLength;
+ private readonly KeyParameter _aadKeyParameter;
+ private readonly KeyParameter _keyParameter;
+ private readonly ChaCha7539Engine _aadCipher;
+ private readonly ChaCha7539Engine _cipher;
+ private readonly Poly1305 _mac;
///
/// Gets the minimun block size.
@@ -51,9 +55,23 @@ public override int TagSize
/// Initializes a new instance of the class.
///
/// The key.
- public ChaCha20Poly1305Cipher(byte[] key)
+ /// The length of additional associated data.
+ public ChaCha20Poly1305Cipher(byte[] key, int aadLength)
: base(key)
{
+ _iv = new byte[12];
+ _aadLength = aadLength;
+
+ _keyParameter = new KeyParameter(key, 0, 32);
+ _cipher = new ChaCha7539Engine();
+
+ if (aadLength > 0)
+ {
+ _aadKeyParameter = new KeyParameter(key, 32, 32);
+ _aadCipher = new ChaCha7539Engine();
+ }
+
+ _mac = new Poly1305();
}
///
@@ -62,8 +80,7 @@ public ChaCha20Poly1305Cipher(byte[] key)
///
/// The input data with below format:
///
- /// [outbound sequence field][packet length field][padding length field sz][payload][random paddings]
- /// [----4 bytes----(offset)][------4 bytes------][----------------Plain Text---------------(length)]
+ /// [----(offset)][----AAD----][----Plain Text----(length)]
///
///
/// The zero-based offset in at which to begin encrypting.
@@ -71,16 +88,22 @@ public ChaCha20Poly1305Cipher(byte[] key)
///
/// The encrypted data with below format:
///
- /// [packet length field][padding length field sz][payload][random paddings][Authenticated TAG]
- /// [------4 bytes------][------------------Cipher Text--------------------][-------TAG-------]
+ /// [----Cipher AAD----][----Cipher Text----][----TAG----]
///
///
public override byte[] Encrypt(byte[] input, int offset, int length)
{
+ _aadCipher?.Init(forEncryption: true, new ParametersWithIV(_aadKeyParameter, _iv));
+ _cipher.Init(forEncryption: true, new ParametersWithIV(_keyParameter, _iv));
+
+ var keyStream = new byte[64];
+ _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0);
+ _mac.Init(new KeyParameter(keyStream, 0, 32));
+
var output = new byte[length + TagSize];
- _aadCipher.ProcessBytes(input, offset, 4, output, 0);
- _cipher.ProcessBytes(input, offset + 4, length - 4, output, 4);
+ _aadCipher?.ProcessBytes(input, offset, _aadLength, output, 0);
+ _cipher.ProcessBytes(input, offset + _aadLength, length - _aadLength, output, _aadLength);
_mac.BlockUpdate(output, 0, length);
_ = _mac.DoFinal(output, length);
@@ -89,12 +112,16 @@ public override byte[] Encrypt(byte[] input, int offset, int length)
}
///
- /// Decrypts the first block which is packet length field.
+ /// Decrypts the AAD.
///
- /// The encrypted packet length field.
- /// The decrypted packet length field.
+ /// The encrypted AAD.
+ /// The decrypted AAD.
public override byte[] Decrypt(byte[] input)
{
+ Debug.Assert(_aadCipher != null, "The aadCipher must not be null");
+
+ _aadCipher.Init(forEncryption: false, new ParametersWithIV(_aadKeyParameter, _iv));
+
var output = new byte[input.Length];
_aadCipher.ProcessBytes(input, 0, input.Length, output, 0);
@@ -107,8 +134,7 @@ public override byte[] Decrypt(byte[] input)
///
/// The input data with below format:
///
- /// [inbound sequence field][packet length field][padding length field sz][payload][random paddings][Authenticated TAG]
- /// [--------4 bytes-------][--4 bytes--(offset)][--------------Cipher Text----------------(length)][-------TAG-------]
+ /// [----][----Cipher AAD----(offset)][----Cipher Text----(length)][----TAG----]
///
///
/// The zero-based offset in at which to begin decrypting and authenticating.
@@ -116,16 +142,21 @@ public override byte[] Decrypt(byte[] input)
///
/// The decrypted data with below format:
///
- /// [padding length field sz][payload][random paddings]
- /// [--------------------Plain Text-------------------]
+ /// [----Plain Text----]
///
///
public override byte[] Decrypt(byte[] input, int offset, int length)
{
- Debug.Assert(offset == 8, "The offset must be 8");
+ Debug.Assert(offset >= _aadLength, "The offset must be greater than or equals to aad length");
+
+ _cipher.Init(forEncryption: false, new ParametersWithIV(_keyParameter, _iv));
+
+ var keyStream = new byte[64];
+ _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0);
+ _mac.Init(new KeyParameter(keyStream, 0, 32));
var tag = new byte[TagSize];
- _mac.BlockUpdate(input, offset - 4, length + 4);
+ _mac.BlockUpdate(input, offset - _aadLength, length + _aadLength);
_ = _mac.DoFinal(tag, 0);
if (!Arrays.FixedTimeEquals(TagSize, tag, 0, input, offset + length))
{
@@ -140,18 +171,7 @@ public override byte[] Decrypt(byte[] input, int offset, int length)
internal override void SetSequenceNumber(uint sequenceNumber)
{
- var iv = new byte[12];
- BinaryPrimitives.WriteUInt64BigEndian(iv.AsSpan(4), sequenceNumber);
-
- // ChaCha20 encryption and decryption is completely
- // symmetrical, so the 'forEncryption' is
- // irrelevant. (Like 90% of stream ciphers)
- _aadCipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(Key, 32, 32), iv));
- _cipher.Init(forEncryption: true, new ParametersWithIV(new KeyParameter(Key, 0, 32), iv));
-
- var keyStream = new byte[64];
- _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0);
- _mac.Init(new KeyParameter(keyStream, 0, 32));
+ BinaryPrimitives.WriteUInt64BigEndian(_iv.AsSpan(4), sequenceNumber);
}
}
}
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.pub
new file mode 100644
index 000000000..a719d918d
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC5fr8EY9Gb65zsvrCsGDF7+AXJgDJrAkvBAFDiYBBiX SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt
new file mode 100644
index 000000000..442d55ec9
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACDNkZXMtY2JjAAAABmJjcnlwdAAAABgAAAAQEMaDlDt8i7
+NL7keWLs6LGAAAABAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAuX6/BGPRm+uc7L6wr
+Bgxe/gFyYAyawJLwQBQ4mAQYlwAAAJC/OWi6TtpChMbEYxOK4TuUencG4ULol0k3hJ4905
+LI0+etT6s5fqr8W3D93A6ElxGLtxtkhgLfhTC4DIo4fPxD7mHNKtjbbyJn2oqzxqUlvETS
+d9gg7Ph+zw4a+/GMhhIlPc79D6QxISSNrdtFccvLwTVukhkm04OGIyNJzE5qOz4g78lejZ
+pKjFzVLY+WjSg=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.pub
new file mode 100644
index 000000000..9afd37c24
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMYdAxNcki2uVihPLcIxl6Zk27MBZdI2ef7NBMIYFHtW SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt
new file mode 100644
index 000000000..305ea8c1b
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABApmviQDZ
+dmF4eaa1glB0maAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMYdAxNcki2uVihP
+LcIxl6Zk27MBZdI2ef7NBMIYFHtWAAAAkHLroaoIoLkV3nmNck418+ndWFFEsvbk3AURyG
+3fZboIGmjTCFGWDigRmB/w75jKwurLdfXvORWMxqaxZ0sjnHFwt0i/pumOv61nFC0timVV
+VSoCWcAgyBsl2+lnRhnORlY5THVvgU6zF6p8Mf8ONMT59HXRxc9VMRc+eJhY3wb3q0BIN+
+vSQ7PXvnKrq7/mbA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.pub
new file mode 100644
index 000000000..856fe28fe
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP44SYIMQiq6RfzllvHztr7ATNkXbAFqFZtukvHa0VaU SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt
new file mode 100644
index 000000000..8c49a39fb
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jdHIAAAAGYmNyeXB0AAAAGAAAABAGS7ibrm
+I3evObqzYMOyFsAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIP44SYIMQiq6Rfzl
+lvHztr7ATNkXbAFqFZtukvHa0VaUAAAAkHezd7R3B/U5jmRG9ayxcB5v9eIjjM2eZ/7O6z
+7waNtAIO36Ve+BY5qIduP4t8qdZ1JDFHaPxx/WLqezfV0hqRLzR4Pm/bcAHN60610x0BSF
+xHDC7nFej/X4Sr0SEplqsCfqfk5B4wsmdLUGwvpxIqoUCsCLx4YVaAlG/OezZUJ6b6lViZ
+zcaaG2MGRlm28f9Q==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.pub
new file mode 100644
index 000000000..31a491628
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJrCQwF23e9NNyfNKyYFhmbAme9DahSl8S7IVdjHFUZk SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt
new file mode 100644
index 000000000..067372172
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAAFmFlczEyOC1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
+AAGAAAABAVgfqUe6lBEiQrnz6KqwwkAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
+IJrCQwF23e9NNyfNKyYFhmbAme9DahSl8S7IVdjHFUZkAAAAkJn29lzPneR2zVKGlR1+mw
+qDiwgKDEHdGVC/LgNQwxsaOryYEwA8fK0pBx4Ai+oRJZIAL430ZvwXSNrbkRo6oLkOouxH
+0Cx9Uq4ETgumAAMAuyPecU8POBTUQUJsbA35EJZofxceVOa8iKleGSEKzVlQeSESY78AFH
+U3bsmAJ21q3daVGXKrH5RIi3vSDE5IdRWMqonvhXSSLk8HADgR4Kw=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.pub
new file mode 100644
index 000000000..e64cb6532
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPjSxcsIj5ycJxIhhcMQuJAYTgANIXUa4Y1WEiOrblXA SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt
new file mode 100644
index 000000000..591f28ca2
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jYmMAAAAGYmNyeXB0AAAAGAAAABCO+DfG2m
+YhNIGKmiDRH4cTAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIPjSxcsIj5ycJxIh
+hcMQuJAYTgANIXUa4Y1WEiOrblXAAAAAkC9fsItDX1HaNpw9PBxBX/Eedlu72MGGO7osjJ
+QtuvNb5VhcR07iYhxyw97F7MeraRYNvLrWQyxURB1BkZaSCemBrQJ/ljgEOhU5IgqmlooI
+T4x5BuChnet8HfPJ5Ws9fd5WMOtfMpdO8ZHkJM5VPiSUUhfgWSV0YGKY4Q7luDcpLHzBZk
+ZpossNHwNKsBMC9g==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.pub
new file mode 100644
index 000000000..dc77b8e8c
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIERKx068MaW5RqzljZBz1nZw6OIzp4zyUjTKlJQ45wAg SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt
new file mode 100644
index 000000000..05a06f06f
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jdHIAAAAGYmNyeXB0AAAAGAAAABCV/tolDk
+gwraQcGNlFOu+GAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIERKx068MaW5Rqzl
+jZBz1nZw6OIzp4zyUjTKlJQ45wAgAAAAkGnQfFCyY4tOQO+dNs+1Zzt6l5UbRHMjECnvuF
+M3P5oFi0FXRSNSODXvZzZWCn9EVtaICV0bP+UKx9SfVAAkS64ZHl8n0IlRI9PmmthP6yZr
+N7RzXaejppY/ZqQm+yH7S1cNb9KsAEIGMUlws34KlPCitc1HKJu8r9UmQGXaXXur/l47f5
+AVP+RmKjdDZy7FvA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.pub
new file mode 100644
index 000000000..6096d4437
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC+D1UJTH4n4ipIrHFeBBVjDJkhnjFzhZfewDxNAg5hb SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt
new file mode 100644
index 000000000..c8f3072c6
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABAwPJUhck
+LArH2ovPv8EjcMAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIC+D1UJTH4n4ipIr
+HFeBBVjDJkhnjFzhZfewDxNAg5hbAAAAkIrxGvN1uUkltnk7X6fktv3KWQVL1oSuJ10kK8
+HY854Dp8qbGi0sFPcBrX/0y7kTyN58L72UWzjWwUZXrG/n6mb+PHXQbfr4MO5R/+BMnIA0
+0NmEj4nzY/9WCtU7En30zv5IF1MiLU2x7qPBqwdkOIKkTz+cTOXo0PCqT1s/Y2cbMUarS/
+cPdZ1FL5JwOcbqvQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.pub
new file mode 100644
index 000000000..069798587
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJiK1qYA1G7CJwZrDhleCnSXM0YgZtVA4lEqDBlrV6LE SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt
new file mode 100644
index 000000000..e2990c982
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCLdsObaY
+r3GiQxDA6fA1AlAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJiK1qYA1G7CJwZr
+DhleCnSXM0YgZtVA4lEqDBlrV6LEAAAAkClmUCCE7AqpnGJCaI44L/mCRBVMCEXi7O9FNG
+VLklY6VJV//S77A696OKqeEZxv17l9VnkL+KIbn5dgf05JzMAXFGgaUO9xsz++21A/3bfF
+lQ1oghMQPN6iXVg2eVTTzLrlxW8+9wQRr+RZ+38rL55fnU187f/evLGajjJdVhtzw/PvKg
+sCJ4xZHaGOUR6OxQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.pub
new file mode 100644
index 000000000..9aceccee7
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKs32WTgxZkPlc1Xg4q6m9H2MRsMYDOuRXXXKNa8sBIM scott@SCOTTLAPTOP
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt
new file mode 100644
index 000000000..fd65439c0
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAAFmFlczI1Ni1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
+AAGAAAABDlF34NcdHSHJFFHrK8PK/FAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
+IGgS0NrTKMQcchUDkewJOtOC5J+nmrdny2rxCN0koPHdAAAAkHCy69z0kHw/kCs4a+LCBn
+2R37rprUEqHSiykVEPKxRDqpjqitaVNGzvPo6uhUclW9xxsAufMYv+Mn/Rz5ZLqHSV1Jio
+zdBZrAkM13DJpW6xKVjbjGTr7zXpnjr1dgMs1tmq/T9F503Dky84u9qA+jUVczFitWuwvn
+JMHIJ7zgAI2fp5z+aq51lH9rLAp6Vmjoal5MRsZhIHBrrwheBEWPE=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.pub b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.pub
new file mode 100644
index 000000000..438c80f0d
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBquS79wnun0ksZv6JJMgHzoZhzqH6Lkft+1sHTHFOjY SSH.NET
diff --git a/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt
new file mode 100644
index 000000000..4d1974258
--- /dev/null
+++ b/test/Data/Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm
+JjcnlwdAAAABgAAAAQoCDdnsgOWkWp01akN/vJjwAAABAAAAABAAAAMwAAAAtzc2gtZWQy
+NTUxOQAAACAarku/cJ7p9JLGb+iSTIB86GYc6h+i5H7ftbB0xxTo2AAAAJDUnN2iwK9ZgA
+fpExo5jBaxI90HMxcUmgUCc4H7fTOqKousn4oyK9SfXH66IS9S1id7D+6KfVHBnmF+aH5q
+g75pAGaHUGncm5PY3Lfun4KD5mDbN5cnloFDKb+z4pJq6FhpCeg9GyiYbFeIz8HrDeEugK
+RKeq5tKWibhDLX6Ywp0+gQH1Rb14atw2Yy1zV9KX900we18Jjhl7R+FvkrFkUC
+-----END OPENSSH PRIVATE KEY-----
diff --git a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs
index d07c3867c..009c9d915 100644
--- a/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs
+++ b/test/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs
@@ -562,11 +562,110 @@ public void Test_PrivateKey_OPENSSH_ED25519()
}
[TestMethod()]
- [Owner("bhalbright")]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_3DES_CBC()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.3Des.CBC.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_128_CBC()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.128.CBC.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_192_CBC()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.192.CBC.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_256_CBC()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.256.CBC.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_128_CTR()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.128.CTR.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_192_CTR()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.192.CTR.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_256_CTR()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.256.CTR.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_128_GCM()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.128.GCM.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
+ [TestCategory("PrivateKey")]
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_AES_256_GCM()
+ {
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.Aes.256.GCM.txt"))
+ {
+ _ = new PrivateKeyFile(stream, "12345");
+ }
+ }
+
+ [TestMethod()]
+ [Owner("scott-xu")]
[TestCategory("PrivateKey")]
- public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED()
+ public void Test_PrivateKey_OPENSSH_ED25519_ENCRYPTED_ChaCha20_Poly1305()
{
- using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.txt"))
+ using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt"))
{
_ = new PrivateKeyFile(stream, "12345");
}