[Private Key] Add support for PuTTY private key file format (V3 and V2) (#1543)

* [Private Key] Add support for PuTTY private key

* add negative test for mac

---------

Co-authored-by: Rob Hague <rob.hague00@gmail.com>
This commit is contained in:
Scott Xu
2024-12-03 06:44:44 +08:00
committed by GitHub
parent 70c12467dd
commit 3b4f2cfc1c
20 changed files with 678 additions and 15 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.4.0" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.5.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1">
+8 -1
View File
@@ -102,17 +102,21 @@ The main types provided by this library are:
* OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY")
* ssh.com format ("BEGIN SSH2 ENCRYPTED PRIVATE KEY")
* OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY")
* PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3")
* DSA in
* OpenSSL traditional PEM format ("BEGIN DSA PRIVATE KEY")
* OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY")
* ssh.com format ("BEGIN SSH2 ENCRYPTED PRIVATE KEY")
* PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3")
* ECDSA 256/384/521 in
* OpenSSL traditional PEM format ("BEGIN EC PRIVATE KEY")
* OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY")
* OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY")
* PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3")
* ED25519 in
* OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY")
* OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY")
* PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3")
Private keys in OpenSSL traditional PEM format can be encrypted using one of the following cipher methods:
* DES-EDE3-CBC
@@ -124,7 +128,7 @@ Private keys in OpenSSL traditional PEM format can be encrypted using one of the
Private keys in OpenSSL PKCS#8 PEM format can be encrypted using any cipher method BouncyCastle supports.
Private keys in ssh.com format can be encrypted using one of the following cipher methods:
Private keys in ssh.com format can be encrypted using the following cipher method:
* 3des-cbc
Private keys in OpenSSH key format can be encrypted using one of the following cipher methods:
@@ -139,6 +143,9 @@ Private keys in OpenSSH key format can be encrypted using one of the following c
* aes256-gcm<span></span>@openssh.com
* chacha20-poly1305<span></span>@openssh.com
Private keys in PuTTY private key format can be encrypted using the following cipher method:
* aes256-cbc
## Host Key Algorithms
**SSH.NET** supports the following host key algorithms:
+5 -7
View File
@@ -42,13 +42,11 @@ namespace Renci.SshNet
{
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
}
var binarySalt = new byte[_salt.Length / 2];
for (var i = 0; i < binarySalt.Length; i++)
{
binarySalt[i] = Convert.ToByte(_salt.Substring(i * 2, 2), 16);
}
#if NET
var binarySalt = Convert.FromHexString(_salt);
#else
var binarySalt = Org.BouncyCastle.Utilities.Encoders.Hex.Decode(_salt);
#endif
CipherInfo cipher;
switch (_cipherName)
{
+273
View File
@@ -0,0 +1,273 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography.Ciphers;
namespace Renci.SshNet
{
public partial class PrivateKeyFile
{
private sealed class PuTTY : IPrivateKeyParser
{
private readonly string _version;
private readonly string _algorithmName;
private readonly string _encryptionType;
private readonly string _comment;
private readonly byte[] _publicKey;
private readonly string? _argon2Type;
private readonly string? _argon2Salt;
private readonly string? _argon2Iterations;
private readonly string? _argon2Memory;
private readonly string? _argon2Parallelism;
private readonly byte[] _data;
private readonly string _mac;
private readonly string? _passPhrase;
public PuTTY(string version, string algorithmName, string encryptionType, string comment, byte[] publicKey, string? argon2Type, string? argon2Salt, string? argon2Iterations, string? argon2Memory, string? argon2Parallelism, byte[] data, string mac, string? passPhrase)
{
_version = version;
_algorithmName = algorithmName;
_encryptionType = encryptionType;
_comment = comment;
_publicKey = publicKey;
_argon2Type = argon2Type;
_argon2Salt = argon2Salt;
_argon2Iterations = argon2Iterations;
_argon2Memory = argon2Memory;
_argon2Parallelism = argon2Parallelism;
_data = data;
_mac = mac;
_passPhrase = passPhrase;
}
/// <summary>
/// Parses an PuTTY PPK key file.
/// <see href="https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html"/>.
/// </summary>
public Key Parse()
{
byte[] privateKey;
HMAC hmac;
switch (_encryptionType)
{
case "aes256-cbc":
if (string.IsNullOrEmpty(_passPhrase))
{
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
}
byte[] cipherKey;
byte[] cipherIV;
switch (_version)
{
case "3":
ThrowHelper.ThrowIfNullOrEmpty(_argon2Type);
ThrowHelper.ThrowIfNullOrEmpty(_argon2Iterations);
ThrowHelper.ThrowIfNullOrEmpty(_argon2Memory);
ThrowHelper.ThrowIfNullOrEmpty(_argon2Parallelism);
ThrowHelper.ThrowIfNullOrEmpty(_argon2Salt);
var keyData = Argon2(
_argon2Type,
Convert.ToInt32(_argon2Iterations),
Convert.ToInt32(_argon2Memory),
Convert.ToInt32(_argon2Parallelism),
#if NET
Convert.FromHexString(_argon2Salt),
#else
Org.BouncyCastle.Utilities.Encoders.Hex.Decode(_argon2Salt),
#endif
_passPhrase);
cipherKey = keyData.Take(32);
cipherIV = keyData.Take(32, 16);
var macKey = keyData.Take(48, 32);
hmac = new HMACSHA256(macKey);
break;
case "2":
keyData = V2KDF(_passPhrase);
cipherKey = keyData.Take(32);
cipherIV = new byte[16];
macKey = CryptoAbstraction.HashSHA1(Encoding.UTF8.GetBytes("putty-private-key-file-mac-key" + _passPhrase)).Take(20);
hmac = new HMACSHA1(macKey);
break;
default:
throw new SshException("PuTTY key file version " + _version + " is not supported");
}
using (var cipher = new AesCipher(cipherKey, cipherIV, AesCipherMode.CBC, pkcs7Padding: false))
{
privateKey = cipher.Decrypt(_data);
}
break;
case "none":
switch (_version)
{
case "3":
hmac = new HMACSHA256(Array.Empty<byte>());
break;
case "2":
var macKey = CryptoAbstraction.HashSHA1(Encoding.UTF8.GetBytes("putty-private-key-file-mac-key"));
hmac = new HMACSHA1(macKey);
break;
default:
throw new SshException("PuTTY key file version " + _version + " is not supported");
}
privateKey = _data;
break;
default:
throw new SshException("Encryption " + _encryptionType + " is not supported for PuTTY key file");
}
byte[] macData;
using (var macStream = new SshDataStream(256))
{
macStream.Write(_algorithmName, Encoding.UTF8);
macStream.Write(_encryptionType, Encoding.UTF8);
macStream.Write(_comment, Encoding.UTF8);
macStream.WriteBinary(_publicKey);
macStream.WriteBinary(privateKey);
macData = macStream.ToArray();
}
byte[] macValue;
using (hmac)
{
macValue = hmac.ComputeHash(macData);
}
#if NET
var reference = Convert.FromHexString(_mac);
#else
var reference = Org.BouncyCastle.Utilities.Encoders.Hex.Decode(_mac);
#endif
if (!macValue.SequenceEqual(reference))
{
throw new SshException("MAC verification failed for PuTTY key file");
}
var publicKeyReader = new SshDataReader(_publicKey);
var keyType = publicKeyReader.ReadString(Encoding.UTF8);
Debug.Assert(keyType == _algorithmName, $"{nameof(keyType)} is not the same as {nameof(_algorithmName)}");
var privateKeyReader = new SshDataReader(privateKey);
Key parsedKey;
switch (keyType)
{
case "ssh-ed25519":
parsedKey = new ED25519Key(privateKeyReader.ReadBignum2());
break;
case "ecdsa-sha2-nistp256":
case "ecdsa-sha2-nistp384":
case "ecdsa-sha2-nistp521":
var curve = publicKeyReader.ReadString(Encoding.ASCII);
var pub = publicKeyReader.ReadBignum2();
var prv = privateKeyReader.ReadBignum2();
parsedKey = new EcdsaKey(curve, pub, prv);
break;
case "ssh-dss":
var p = publicKeyReader.ReadBignum();
var q = publicKeyReader.ReadBignum();
var g = publicKeyReader.ReadBignum();
var y = publicKeyReader.ReadBignum();
var x = privateKeyReader.ReadBignum();
parsedKey = new DsaKey(p, q, g, y, x);
break;
case "ssh-rsa":
var exponent = publicKeyReader.ReadBignum(); // e
var modulus = publicKeyReader.ReadBignum(); // n
var d = privateKeyReader.ReadBignum(); // d
p = privateKeyReader.ReadBignum(); // p
q = privateKeyReader.ReadBignum(); // q
var inverseQ = privateKeyReader.ReadBignum(); // iqmp
parsedKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);
break;
default:
throw new SshException("Key type " + keyType + " is not supported for PuTTY key file");
}
parsedKey.Comment = _comment;
return parsedKey;
}
private static byte[] Argon2(string type, int iterations, int memory, int parallelism, byte[] salt, string passPhrase)
{
int param;
switch (type)
{
case "Argon2i":
param = Argon2Parameters.Argon2i;
break;
case "Argon2d":
param = Argon2Parameters.Argon2d;
break;
case "Argon2id":
param = Argon2Parameters.Argon2id;
break;
default:
throw new SshException("KDF " + type + " is not supported for PuTTY key file");
}
var a2p = new Argon2Parameters.Builder(param)
.WithVersion(Argon2Parameters.Version13)
.WithIterations(iterations)
.WithMemoryAsKB(memory)
.WithParallelism(parallelism)
.WithSalt(salt).Build();
var generator = new Argon2BytesGenerator();
generator.Init(a2p);
var output = new byte[80];
var bytes = generator.GenerateBytes(passPhrase.ToCharArray(), output);
if (bytes != output.Length)
{
throw new SshException("Failed to generate key via Argon2");
}
return output;
}
private static byte[] V2KDF(string passPhrase)
{
var cipherKey = new List<byte>();
var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase);
for (var sequenceNumber = 0; sequenceNumber < 2; sequenceNumber++)
{
using (var sha1 = SHA1.Create())
{
var sequence = new byte[] { 0, 0, 0, (byte)sequenceNumber };
_ = sha1.TransformBlock(sequence, 0, 4, outputBuffer: null, 0);
_ = sha1.TransformFinalBlock(passPhraseBytes, 0, passPhraseBytes.Length);
Debug.Assert(sha1.Hash != null, "Hash is null");
cipherKey.AddRange(sha1.Hash);
}
}
return cipherKey.ToArray();
}
}
}
}
+55 -6
View File
@@ -25,16 +25,16 @@ namespace Renci.SshNet
/// The following private keys are supported:
/// <list type="bullet">
/// <item>
/// <description>RSA in OpenSSL PEM, ssh.com and OpenSSH key format</description>
/// <description>RSA in OpenSSL PEM, ssh.com, OpenSSH and PuTTY key format</description>
/// </item>
/// <item>
/// <description>DSA in OpenSSL PEM and ssh.com format</description>
/// <description>DSA in OpenSSL PEM, ssh.com and PuTTY key format</description>
/// </item>
/// <item>
/// <description>ECDSA 256/384/521 in OpenSSL PEM and OpenSSH key format</description>
/// <description>ECDSA 256/384/521 in OpenSSL PEM, OpenSSH and PuTTY key format</description>
/// </item>
/// <item>
/// <description>ED25519 in OpenSSL PEM and OpenSSH key format</description>
/// <description>ED25519 in OpenSSL PEM, OpenSSH and PuTTY key format</description>
/// </item>
/// </list>
/// </para>
@@ -73,7 +73,7 @@ namespace Renci.SshNet
/// </list>
/// </para>
/// <para>
/// The following encryption algorithms are supported for OpenSSH format:
/// The following encryption algorithms are supported for OpenSSH key format:
/// <list type="bullet">
/// <item>
/// <description>3des-cbc</description>
@@ -107,23 +107,37 @@ namespace Renci.SshNet
/// </item>
/// </list>
/// </para>
/// <para>
/// The following encryption algorithms are supported for PuTTY key format:
/// <list type="bullet">
/// <item>
/// <description>aes256-cbc</description>
/// </item>
/// </list>
/// </para>
/// </remarks>
public partial class PrivateKeyFile : IPrivateKeySource, IDisposable
{
private const string PrivateKeyPattern = @"^-+ *BEGIN (?<keyName>\w+( \w+)*) *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[a-fA-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k<keyName> *-+";
private const string PuTTYPrivateKeyPattern = @"^(?<keyName>PuTTY-User-Key-File)-(?<version>\d+): (?<algorithmName>[\w-]+)\r?\nEncryption: (?<encryptionType>[\w-]+)\r?\nComment: (?<comment>.*)\r?\nPublic-Lines: \d+\r?\n(?<publicKey>(([a-zA-Z0-9/+=]{1,64})\r?\n)+)(Key-Derivation: (?<argon2Type>\w+)\r?\nArgon2-Memory: (?<argon2Memory>\d+)\r?\nArgon2-Passes: (?<argon2Passes>\d+)\r?\nArgon2-Parallelism: (?<argon2Parallelism>\d+)\r?\nArgon2-Salt: (?<argon2Salt>[a-fA-F0-9]+)\r?\n)?Private-Lines: \d+\r?\n(?<data>(([a-zA-Z0-9/+=]{1,64})\r?\n)+)+Private-MAC: (?<mac>[a-fA-F0-9]+)";
private const string CertificatePattern = @"(?<type>[-\w]+@openssh\.com)\s(?<data>[a-zA-Z0-9\/+=]*)(\s+(?<comment>.*))?";
#if NET7_0_OR_GREATER
private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex();
private static readonly Regex PuTTYPrivateKeyRegex = GetPrivateKeyPuTTYRegex();
private static readonly Regex CertificateRegex = GetCertificateRegex();
[GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
private static partial Regex GetPrivateKeyRegex();
[GeneratedRegex(PuTTYPrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
private static partial Regex GetPrivateKeyPuTTYRegex();
[GeneratedRegex(CertificatePattern, RegexOptions.ExplicitCapture)]
private static partial Regex GetCertificateRegex();
#else
private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
private static readonly Regex PuTTYPrivateKeyRegex = new Regex(PuTTYPrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
private static readonly Regex CertificateRegex = new Regex(CertificatePattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
#endif
@@ -287,7 +301,14 @@ namespace Renci.SshNet
using (var sr = new StreamReader(privateKey))
{
var text = sr.ReadToEnd();
privateKeyMatch = PrivateKeyRegex.Match(text);
if (text.StartsWith("PuTTY-User-Key-File", StringComparison.Ordinal))
{
privateKeyMatch = PuTTYPrivateKeyRegex.Match(text);
}
else
{
privateKeyMatch = PrivateKeyRegex.Match(text);
}
}
if (!privateKeyMatch.Success)
@@ -321,6 +342,34 @@ namespace Renci.SshNet
case "SSH2 ENCRYPTED PRIVATE KEY":
parser = new SSHCOM(binaryData, passPhrase);
break;
case "PuTTY-User-Key-File":
var version = privateKeyMatch.Result("${version}");
var algorithmName = privateKeyMatch.Result("${algorithmName}");
var encryptionType = privateKeyMatch.Result("${encryptionType}");
var comment = privateKeyMatch.Result("${comment}");
var publicKey = privateKeyMatch.Result("${publicKey}");
var argon2Type = privateKeyMatch.Result("${argon2Type}");
var argon2Memory = privateKeyMatch.Result("${argon2Memory}");
var argon2Passes = privateKeyMatch.Result("${argon2Passes}");
var argon2Parallelism = privateKeyMatch.Result("${argon2Parallelism}");
var argon2Salt = privateKeyMatch.Result("${argon2Salt}");
var mac = privateKeyMatch.Result("${mac}");
parser = new PuTTY(
version,
algorithmName,
encryptionType,
comment,
Convert.FromBase64String(publicKey),
argon2Type,
argon2Salt,
argon2Passes,
argon2Memory,
argon2Parallelism,
binaryData,
mac,
passPhrase);
break;
default:
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName));
}
@@ -0,0 +1,9 @@
PuTTY-User-Key-File-2: ssh-ed25519
Encryption: aes256-cbc
Comment: Key.OPENSSH.ED25519
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol
MGMi
Private-Lines: 1
f3N2/AkwbgVeXdK155h+JCbWgBXyEk3qEyx+ChUqm4tOUQGiJ95/mTo4RbIjWn+2
Private-MAC: 823817b8364ce7f52e278e252fd10e2f51ac8554
+9
View File
@@ -0,0 +1,9 @@
PuTTY-User-Key-File-2: ssh-ed25519
Encryption: none
Comment: Key.OPENSSH.ED25519
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol
MGMi
Private-Lines: 1
AAAAIADMEXUw9TGuz7JykmHbzPOj8XebpZwo76iuxJtHkvAp
Private-MAC: 6a1329739932b5caaaf48d7fcd61392d498b8f5f
@@ -0,0 +1,26 @@
PuTTY-User-Key-File-2: ssh-rsa
Encryption: aes256-cbc
Comment: Key.OPENSSH.RSA
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w
oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m
JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/
G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi
QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0
4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09
Private-Lines: 14
i38C+M7U/qOvzvOdtEC60v3cn354TMdDpAVk22UuNgQY7u6avW907mUuXjqU7Tjy
G66uQ+6vOnaQR6xTYWn3uI1YMWGsRylLiNZ/O/dIpITv+9xuU5U8qB+nFIH3iD6U
cBuumRKH2k2IxYVBO1nKTt3T3HejIIy3JlFifs9ylahgPD1m8jIfNARnYB09fOQr
g+nV4YNRntOqpf+cUpc+TGnz35oa9O+6fzJY/2hdkOJ+fUBdlYmcKEZZ9PgYHAlM
+aoQhL1vRoqZqzfHZYcMUBlmbRMgWiPfFB59nU0QbZ2uClbFxGGehBnCAsnd8Nni
TafVYckp3kCNLmctrmorsRgM/IXWcdqmrGJO5sHEsutQqs9Dp3KM9xhRP23xoGnZ
ssNw1qEAOP/k9JTCID/oeRE7RvAEMEdCs3l6FHKDvInR5xDuNrGehHdc3vGqklkC
1kBER2vWRX+LANNXcfISRDNajC6XKsDo6aGJRPTMj/tbVJNbjP4Oi6fwcFizV9Z0
yJiw1yV5AjUc8nG3cuTb1wNZ8DTPccq+W6ir8U+Vmdc67Mhxas6e2UKa5nXC+HqC
jtM1oCPpxNyRoJSuxcWrwcs/yEhFfSOBU9PFsbJcX5i+ilZ2ZSipFc2fOcSx/6RC
U36mELhmHPINoTDOc6qvL/fXRkqtoQGZm2UHoINVLkeMu3ERkxxDk9kFqRcSdFd+
nSMJt9d3YRSsjhzvro65ugKiYBubugon24frB3nARI9zoe0BZB9H/RW3RPlMT8d9
H+gElqyiYWZKKJs+Qht6NY0Hv1gp9erMIcTTts/w7ZRc5oS8pLCKOuB9wKYepJm7
5I0x8qVotiHglDJ7PPbP+3UzrJvGp2nwPzj4qokCZKFhPYNYiCY16V0LaBRoE3mQ
Private-MAC: 191817999553b67a7516223647e90e0b17db8918
+26
View File
@@ -0,0 +1,26 @@
PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: Key.OPENSSH.RSA
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w
oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m
JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/
G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi
QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0
4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09
Private-Lines: 14
AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv
Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N
BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u
FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL
ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ
zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje
DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd
8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF
7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J
8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5
WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA
AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1
vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP
fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM
Private-MAC: 7f487d19cb5d03257c9b9a2e1c7192a5d2396af5
@@ -0,0 +1,22 @@
PuTTY-User-Key-File-3: ssh-dss
Encryption: aes256-cbc
Comment: imported-openssh-key
Public-Lines: 10
AAAAB3NzaC1kc3MAAACBALVl3fae2O4qwsAK95SUShX0KMUNP+yl/uT3lGH9T/Zp
tnHSlrTxnTWXCl0g91KEeCaEnDDhLxm4aCv1Ag4B/yvcM4u34qkmaNLy2LiAxiqd
obZcNG61Pqwqd5IDkp38LBsn8tmb12xu9NalpUfOiSEB1cyCr4zFZMrm0wtdyJQV
AAAAFQCu+iNkqf/YOAYjYrHSCHFmWAfEYQAAAIAOVJ434UAR3Hn6lA5nWNfFOuUV
H3W7nJaP0FQJiIPx7GUbdxO9qtDNTbWkWL3c9qx5+B7Ole4xM7cvyXPrNQUYDHCF
lS+Ue2x3IeJrkdfZkH9ePP25y5A0J4/c+8XXvQaj4zA5nfw13oy5Ptyd7d3Kq5tE
DM8KiVdIhwkXjUA3PQAAAIEAm8IGZQatS7M6AfNITNWG4TI7Z2aRQjLb9/MWJIID
7c/VQ4zdTZdG3kpk0Gj9n4xreopK5NmYAdj8rtFfPBgmXltsLqt+bBcXkpxW//7W
C29WOXW3t90ySTh+cWuWfr9fV7mf4Ql/6u/ZIgpQNvnNYezazt3fK8EXjI1dAXEu
QxE=
Key-Derivation: Argon2id
Argon2-Memory: 8192
Argon2-Passes: 8
Argon2-Parallelism: 1
Argon2-Salt: 310d916da49faba22ba8d2745777e5c5
Private-Lines: 1
xMm0Tg+o7Yq6lAs6L33y2fy3fiDPl6p71iKxm8OAgj4=
Private-MAC: fc2aef48bf90b80b97d06c32c37491db614331c2551ab37865d8719ee6cb5f4f
+17
View File
@@ -0,0 +1,17 @@
PuTTY-User-Key-File-3: ssh-dss
Encryption: none
Comment: imported-openssh-key
Public-Lines: 10
AAAAB3NzaC1kc3MAAACBALVl3fae2O4qwsAK95SUShX0KMUNP+yl/uT3lGH9T/Zp
tnHSlrTxnTWXCl0g91KEeCaEnDDhLxm4aCv1Ag4B/yvcM4u34qkmaNLy2LiAxiqd
obZcNG61Pqwqd5IDkp38LBsn8tmb12xu9NalpUfOiSEB1cyCr4zFZMrm0wtdyJQV
AAAAFQCu+iNkqf/YOAYjYrHSCHFmWAfEYQAAAIAOVJ434UAR3Hn6lA5nWNfFOuUV
H3W7nJaP0FQJiIPx7GUbdxO9qtDNTbWkWL3c9qx5+B7Ole4xM7cvyXPrNQUYDHCF
lS+Ue2x3IeJrkdfZkH9ePP25y5A0J4/c+8XXvQaj4zA5nfw13oy5Ptyd7d3Kq5tE
DM8KiVdIhwkXjUA3PQAAAIEAm8IGZQatS7M6AfNITNWG4TI7Z2aRQjLb9/MWJIID
7c/VQ4zdTZdG3kpk0Gj9n4xreopK5NmYAdj8rtFfPBgmXltsLqt+bBcXkpxW//7W
C29WOXW3t90ySTh+cWuWfr9fV7mf4Ql/6u/ZIgpQNvnNYezazt3fK8EXjI1dAXEu
QxE=
Private-Lines: 1
AAAAFBhGOzk+Aimeob964E8+HsQNlyde
Private-MAC: 6c517ac5ede72c006b0115dd9d0830c8e699a1f4d72c708d41f68b6263d974ae
@@ -0,0 +1,15 @@
PuTTY-User-Key-File-3: ecdsa-sha2-nistp256
Encryption: aes256-cbc
Comment: imported-openssh-key
Public-Lines: 3
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEA+TDv5/cqk
g07M8M1aQKS8eUkBXnBOWXw5IMalXR0HnJtQQD6M2eHihjYSp+9oU+/Zi5afR11/
qDRHLlU/Nx8=
Key-Derivation: Argon2id
Argon2-Memory: 8192
Argon2-Passes: 8
Argon2-Parallelism: 1
Argon2-Salt: d496ed94c393d7c5df6ecd10440210ba
Private-Lines: 1
7gcJBC98J9hOJVkp0rUJrx8vlWIklCf+/7iQZT+1pCyhaWs+5jm/dIw8aCGMhM0a
Private-MAC: 916979ee4696a5ee7674816259b2ac32c5258eaec94c554a9bc8d090dbc45b50
+10
View File
@@ -0,0 +1,10 @@
PuTTY-User-Key-File-3: ecdsa-sha2-nistp256
Encryption: none
Comment: imported-openssh-key
Public-Lines: 3
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEA+TDv5/cqk
g07M8M1aQKS8eUkBXnBOWXw5IMalXR0HnJtQQD6M2eHihjYSp+9oU+/Zi5afR11/
qDRHLlU/Nx8=
Private-Lines: 1
AAAAIEdqaFKgJBIibVjyUh1v7Y35LwIQJrocdTaYFLwl7iB0
Private-MAC: 3df7a232c8e3021a8f9809968b748c9db46ad3c668fa7d3f4d87ec8cbd838522
@@ -0,0 +1,14 @@
PuTTY-User-Key-File-3: ssh-ed25519
Encryption: aes256-cbc
Comment: Key.OPENSSH.ED25519
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol
MGMi
Key-Derivation: Argon2d
Argon2-Memory: 8192
Argon2-Passes: 8
Argon2-Parallelism: 1
Argon2-Salt: a937df822b7304499c58f1795e90376a
Private-Lines: 1
EjcQkrXqEA3uJb/TfLt8MM+Gt1pM80S5fn5AlUWiUdhvrCG7pTTSVpaYYP8svW89
Private-MAC: 673bd514209afa19d14506300f236a6049a915e7c7ec1a67d8df464e948f2654
@@ -0,0 +1,14 @@
PuTTY-User-Key-File-3: ssh-ed25519
Encryption: aes256-cbc
Comment: Key.OPENSSH.ED25519
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol
MGMi
Key-Derivation: Argon2i
Argon2-Memory: 8192
Argon2-Passes: 8
Argon2-Parallelism: 1
Argon2-Salt: bf99b3e5c7d28d566c8c759d8a1a41d2
Private-Lines: 1
tbSkuSPLzdnnQuGYnC/L1fv9UnFzCBkwtk5aLDZYJFiD08h5EmpGCfJJHe0lJx9/
Private-MAC: eee47ba955ba32fc85e3eb7a70d7e5f0359eacbf9f1abd5f02904fa98db513b7
@@ -0,0 +1,14 @@
PuTTY-User-Key-File-3: ssh-ed25519
Encryption: aes256-cbc
Comment: Key.OPENSSH.ED25519
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol
MGMi
Key-Derivation: Argon2id
Argon2-Memory: 8192
Argon2-Passes: 21
Argon2-Parallelism: 1
Argon2-Salt: a8bb359d93fc58732e595da9c80b2e88
Private-Lines: 1
GHfMNhx9V3k/OKJxNb6QJ93Gwg9/slksETZ0Ns0edelE6hJfUc+XhXnQBLkUHZ6H
Private-MAC: b07d3ddb544a7396637bc607bedd3d14ed28db929f2bdb5af4a94b410c3d9feb
+9
View File
@@ -0,0 +1,9 @@
PuTTY-User-Key-File-3: ssh-ed25519
Encryption: none
Comment: Key.OPENSSH.ED25519
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7Ol
MGMi
Private-Lines: 1
AAAAIADMEXUw9TGuz7JykmHbzPOj8XebpZwo76iuxJtHkvAp
Private-MAC: 273204de25dcb0fc8835d6d08b8045be34358bf64d009a7566d97851449fb9ab
@@ -0,0 +1,31 @@
PuTTY-User-Key-File-3: ssh-rsa
Encryption: aes256-cbc
Comment: Key.OPENSSH.RSA
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w
oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m
JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/
G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi
QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0
4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09
Key-Derivation: Argon2id
Argon2-Memory: 8192
Argon2-Passes: 13
Argon2-Parallelism: 1
Argon2-Salt: 433d83be99e63cd358ef5df037448734
Private-Lines: 14
q3I+FoI3y6nzGIi1lIXMIx4J/yOlIYIqoHQbpQxFiUwoDxYYm8zy4BYexEw/Ox6n
LaKo4LKyvh36+5lrjqMluWsJ+PXrA+GBxSXsanzWG42RPRNbNF7RQCqTrxDRgTSv
GztaXcS5URSIutOl5vePib/9lFC5FlYRp+dCM0CVRpkUezgbZdnOQ5901DhylceT
fUXzpUcEHI4i1kp9awG+4ZpfJf057HuHFhmIvNECirt//LKBH9vrr7NQ7t1Sv8+r
NdzkcUHpCyvPLqII/zaQurgFlrYTCIyqzKHphoVZGa2XrB6VHgt/pvh+a0fOw/lB
c3t377cOr1PTht7AhYoeUPheFaYE9bPyZ5pV0Ai5IlOD94blp94BWU56e8GNx8Gp
SB0zCqrW80Q39OXb3hweyIcVEbb8dfY6sJf3PhzEL1pehUzOFSRlwjGl7BsYZCe3
uIUyNzczpbvk6OTReGRHM7DX5xz3t/esC7VXP7CpOnjhdr9dZ6dpqGz/vDciyxV9
Jw/gaipPAQXUf6ls3atooxN1IpLnwtPE1FHAzofV9ixW4TqHmv1H9++94VJKFyA0
nPUVBtKhM8gT/4ZxnPEDc/sl/EYb9DmuaUC8cTV2O10Wpnj3HhS1Qh43qAguyOu/
qF7QGETSweXTYSgresMxeLRkX/tia6tz2Rfk1HnP8sRbMyIOyOvU4pNMDuTelGy9
7OfsS54bcBIM1DTpMZ7HiKctZ6DvjL4VtgIJPlXmc+s4CD6cjbycOHo5iQfjsoS4
Wh2dHGK3bYVJ+LTA/Z63nasR9CLW26oL76CI/+TXvheyFotzIjtn3iKpJUqS7Dc0
vpZT3x5OWxmYlu1xZYbzMkLIedE8WaKYjeb3qE3Ox+rN0xIYXoNiCi6jxU+viuF4
Private-MAC: 3bf7e6fe566fab36eba63d3129929b197f6b07e234ebcf22549d10b1d97e3e2f
+26
View File
@@ -0,0 +1,26 @@
PuTTY-User-Key-File-3: ssh-rsa
Encryption: none
Comment: Key.OPENSSH.RSA
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w
oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m
JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/
G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi
QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0
4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09
Private-Lines: 14
AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv
Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N
BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u
FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL
ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ
zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje
DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd
8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF
7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J
8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5
WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA
AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1
vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP
fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM
Private-MAC: ef76b1cf66a4a28d6fe08c70012c4bfa61771502e496d227dd77580650d20bfd
@@ -344,6 +344,20 @@ namespace Renci.SshNet.Tests.Classes
[DataRow("Key.OPENSSH.RSA.Encrypted.Aes.192.CTR.txt", "12345", typeof(RsaKey))]
[DataRow("Key.OPENSSH.RSA.Encrypted.txt", "12345", typeof(RsaKey))]
[DataRow("Key.OPENSSH.RSA.txt", null, typeof(RsaKey))]
[DataRow("Key.PuTTY2.Ed25519.Encrypted.12345.ppk", "12345", typeof(ED25519Key))]
[DataRow("Key.PuTTY2.Ed25519.ppk", null, typeof(ED25519Key))]
[DataRow("Key.PuTTY2.RSA.Encrypted.12345.ppk", "12345", typeof(RsaKey))]
[DataRow("Key.PuTTY2.RSA.ppk", null, typeof(RsaKey))]
[DataRow("Key.PuTTY3.DSA.Encrypted.Argon2id.12345.ppk", "12345", typeof(DsaKey))]
[DataRow("Key.PuTTY3.DSA.ppk", null, typeof(DsaKey))]
[DataRow("Key.PuTTY3.ECDSA.Encrypted.Argon2id.12345.ppk", "12345", typeof(EcdsaKey))]
[DataRow("Key.PuTTY3.ECDSA.ppk", null, typeof(EcdsaKey))]
[DataRow("Key.PuTTY3.Ed25519.Encrypted.Argon2i.12345.ppk", "12345", typeof(ED25519Key))]
[DataRow("Key.PuTTY3.Ed25519.Encrypted.Argon2d.12345.ppk", "12345", typeof(ED25519Key))]
[DataRow("Key.PuTTY3.Ed25519.Encrypted.Argon2id.12345.ppk", "12345", typeof(ED25519Key))]
[DataRow("Key.PuTTY3.Ed25519.ppk", null, typeof(ED25519Key))]
[DataRow("Key.PuTTY3.RSA.Encrypted.Argon2id.12345.ppk", "12345", typeof(RsaKey))]
[DataRow("Key.PuTTY3.RSA.ppk", null, typeof(RsaKey))]
[DataRow("Key.RSA.Encrypted.Aes.128.CBC.12345.txt", "12345", typeof(RsaKey))]
[DataRow("Key.RSA.Encrypted.Aes.192.CBC.12345.txt", "12345", typeof(RsaKey))]
[DataRow("Key.RSA.Encrypted.Aes.256.CBC.12345.txt", "12345", typeof(RsaKey))]
@@ -523,6 +537,86 @@ namespace Renci.SshNet.Tests.Classes
}
}
[TestMethod]
public void PuTTYv2_InvalidMac_ThrowsSshException()
{
string pk = """
PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: Key.OPENSSH.RSA
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w
oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m
JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/
G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi
QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0
4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09
Private-Lines: 14
AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv
Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N
BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u
FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL
ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ
zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje
DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd
8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF
7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J
8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5
WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA
AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1
vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP
fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM
Private-MAC: 7f487d19cb5d03257c9b9a2aaaaaaaaaaaaaaaaa
""";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(pk)))
{
var ex = Assert.ThrowsException<SshException>(() => new PrivateKeyFile(stream));
Assert.AreEqual("MAC verification failed for PuTTY key file", ex.Message);
}
}
[TestMethod]
public void PuTTYv3_InvalidMac_ThrowsSshException()
{
string pk = """
PuTTY-User-Key-File-3: ssh-rsa
Encryption: none
Comment: Key.OPENSSH.RSA
Public-Lines: 6
AAAAB3NzaC1yc2EAAAADAQABAAABAQDtbs6KCLsePWaxraXweKYs/NqBWYT8Kx4w
oJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkxFx0m
JMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/
G34QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdi
QMDifn4nJBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q0
4if3HaERo3q/EMu1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09
Private-Lines: 14
AAABAQDpeCr6CmnM632eu2zPkCN/W0eVJ6yftdpi4JFWA9veY5lK4RbcFR1NrRKv
Z+TWfNIGlSt+qc3eJ3IraDdsPWxsFEOBQpH4Bo1wI3dOnF/GDJV4mFAu8SQR2i/N
BFR/CtdF/GYTeOREZ9Vu/HKWsbynfnFyZfJ16XjqvaLx2PyAhje0qnREy9nhmU1u
FYc93k7HIdYv17eBs5LIjKNCBMpl7OHMStL9f8on9dirPIECo2pnZGDWQqIdGUdL
ooQja3IXBh+H5Fvov3FyHVKo61CFNaKubFLbl2kYPaOBqVd7KLDw+a6pOJYKpSZQ
zHox0Xe0WyKuvngrhAD2Sox5pEu1AAAAgQD+dPDqesFjwMJ9SXwWbqkLY3H5yXje
DZGEAXcm59L1buVHcqkkC2vIZQM0ToQPqib65bGYDPYfAsi08ropvJYpGR6HMDtd
8wU3VWkPHNpSb39rl0yFzWR7HkuyE5HwYjtYUgeM/EQ5Dq9+Zhn3W8iSBQMBWReF
7PFp0BfrxxGnawAAAIEA7t9vXgsFRX/YNMzR32bt9adFrRK3LEb+e36vlKD7aL/J
8VBe9aDlnuSkhpxrTCAiN9ZAbT4VG73zprqja4CQY4I2z0JotMUgBOS90LhCkTY5
WhN/1mnSgcM4SQ7WrrmJNYn5K3QFaeu18kOabsrhoFWkATT268QPYNSG8ni+P/cA
AACBALFEE9FIau5dLoE3eGPfPWx+nltH6Jdtf5uwec5CUHqTWnVD07NfPLr7+Ip1
vJ9jt0Qmp11h2XwidQLEfzBBFtgukA7b6ilx2831kJQmElcQdewo1ESmvHzWiAJP
fM4JjTcDudzQZXsq1IT4L5t8bewAoKc12OUcDSS/P2tFjpoM
Private-MAC: ef76b1cf66a4a28d6fe08c70012c4bfa61771502e496d227dddddddddddddddd
""";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(pk)))
{
var ex = Assert.ThrowsException<SshException>(() => new PrivateKeyFile(stream));
Assert.AreEqual("MAC verification failed for PuTTY key file", ex.Message);
}
}
private void SaveStreamToFile(Stream stream, string fileName)
{
var buffer = new byte[4000];