π A Windows Service for Secure SSH Tunneling & Multi-Port Forwarding
β
Automated Reverse Port Forwarding β Supports 22, 3389, 5800, 5900
β
Windows Service Mode β Prevents accidental termination
β
Auto-Reconnect β If disconnected, reconnects automatically
β
Encrypted Passphrase β Loads AES-encrypted passphrase from file
β
Embedded Private Key Security β Uses Renci.SshNet for seamless authentication
dotnet add package Renci.SshNet
- Replace
remote.server.com
anduser
in the source code. - Embed
id_rsa
as a resource (Build Action: Embedded Resource
). - Use the encryption tool (see below) to generate
encrypted_passphrase.bin
.
sc create SshTunnelService binPath= "C:\path\to\program.exe" start= auto
Use this small utility to encrypt your private key's passphrase securely:
A simple Windows Forms application in C# that allows you to encrypt a passphrase using AES encryption and save it to a .enc
file.
- Input a passphrase
- Encrypt using AES (symmetric encryption)
- Save encrypted data to file
- .NET Framework 4.7.2 or higher
- Visual Studio or any C# IDE that supports Windows Forms
PassphraseEncryptor/
β
βββ Program.cs
βββ Form1.cs
βββ Form1.Designer.cs
Program.cs
using System;
using System.Windows.Forms;
namespace PassphraseEncryptor
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new PassphraseEncryptorForm());
}
}
}
Form1.cs
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
namespace PassphraseEncryptor
{
public partial class PassphraseEncryptorForm : Form
{
public PassphraseEncryptorForm()
{
InitializeComponent();
}
private void BtnEncrypt_Click(object sender, EventArgs e)
{
// Retrieve the passphrase from the text box
string passphrase = txtPassphrase.Text;
// Validate input
if (string.IsNullOrWhiteSpace(passphrase))
{
MessageBox.Show("Please enter a passphrase.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
using (Aes aes = Aes.Create())
{
// Generate a random AES key and IV
aes.GenerateKey();
aes.GenerateIV();
// Create an encryptor with the generated key and IV
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
using (StreamWriter sw = new StreamWriter(cs))
{
// Write the plaintext passphrase to the crypto stream
sw.Write(passphrase);
}
// Convert the encrypted stream to a byte array
byte[] encrypted = ms.ToArray();
// Prompt the user to choose where to save the encrypted file
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Encrypted File (*.enc)|*.enc",
Title = "Save Encrypted Passphrase"
};
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
// Save the encrypted data to the chosen file
File.WriteAllBytes(saveFileDialog.FileName, encrypted);
// Derive file base name (without extension) for key and IV
string baseFilePath = Path.Combine(
Path.GetDirectoryName(saveFileDialog.FileName),
Path.GetFileNameWithoutExtension(saveFileDialog.FileName)
);
// Encrypt AES key and IV using Windows DPAPI (user-specific scope)
byte[] protectedKey = ProtectedData.Protect(aes.Key, null, DataProtectionScope.CurrentUser);
byte[] protectedIV = ProtectedData.Protect(aes.IV, null, DataProtectionScope.CurrentUser);
// Save the protected key and IV to files
File.WriteAllBytes(baseFilePath + ".key", protectedKey);
File.WriteAllBytes(baseFilePath + ".iv", protectedIV);
MessageBox.Show("Passphrase encrypted and saved successfully!\nAES key and IV securely protected and saved.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
/*
// ORIGINAL (commented) - Did not store or protect the key and IV
//aes.GenerateKey();
//aes.GenerateIV();
//File.WriteAllBytes("aes.key", aes.Key);
//File.WriteAllBytes("aes.iv", aes.IV);
*/
}
}
}
}
Form1.Designer.cs
namespace PassphraseEncryptor
{
partial class PassphraseEncryptorForm
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.TextBox txtPassphrase;
private System.Windows.Forms.Button btnEncrypt;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.txtPassphrase = new System.Windows.Forms.TextBox();
this.btnEncrypt = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// txtPassphrase
//
this.txtPassphrase.Location = new System.Drawing.Point(12, 12);
this.txtPassphrase.Name = "txtPassphrase";
this.txtPassphrase.Size = new System.Drawing.Size(260, 20);
this.txtPassphrase.TabIndex = 0;
//
// btnEncrypt
//
this.btnEncrypt.Location = new System.Drawing.Point(197, 38);
this.btnEncrypt.Name = "btnEncrypt";
this.btnEncrypt.Size = new System.Drawing.Size(75, 23);
this.btnEncrypt.TabIndex = 1;
this.btnEncrypt.Text = "Encrypt";
this.btnEncrypt.UseVisualStyleBackColor = true;
this.btnEncrypt.Click += new System.EventHandler(this.BtnEncrypt_Click);
//
// PassphraseEncryptorForm
//
this.ClientSize = new System.Drawing.Size(284, 71);
this.Controls.Add(this.btnEncrypt);
this.Controls.Add(this.txtPassphrase);
this.Name = "PassphraseEncryptorForm";
this.Text = "Passphrase Encryptor";
this.ResumeLayout(false);
this.PerformLayout();
}
}
}
SshTunnelService is a Windows Service built in C# using the SSH.NET library. It creates an SSH tunnel using a private key with an encrypted passphrase.
- Windows Service architecture
- SSH tunnel with encrypted private key passphrase
- Secure storage of AES-encrypted passphrase
- Robust and clean service lifecycle management
SshTunnelService/
β
βββ SshTunnelService.cs # Main service class
βββ Program.cs # Service entry point
βββ Encryptor.cs # AES passphrase encrypt/decrypt logic
βββ app.config # Configuration file for service settings
βββ README.md # Documentation
Update app.config
with your settings:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- SSH Server Parameters -->
<add key="Host" value="Your_HOST" />
<add key="Port" value="PORT" />
<add key="Username" value="YourUsername" />
<!-- AES Key and IV used to decrypt the embedded passphrase -->
<add key="AesKey" value="BASE64_ENCODED_AES_KEY_HERE" />
<add key="AesIV" value="BASE64_ENCODED_AES_IV_HERE" />
<!-- β οΈ Legacy settings (no longer used but left for reference) -->
<!--
<add key="PrivateKeyPath" value="C:\path\to\privateKey.pem" />
<add key="EncryptedPassphrasePath" value="C:\path\to\EncryptedPassphrase.bin" />
-->
</appSettings>
</configuration>
Use the Encryptor.EncryptStringToFile
method to encrypt your passphrase:
Encryptor.EncryptStringToFile("my_passphrase", "C:\path\to\passphrase.enc", aesKey, aesIV);
using System;
using System.Configuration;
using System.Diagnostics;
using System.ServiceProcess;
using System.Timers;
using Renci.SshNet;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
namespace SshTunnelServiceApp
{
public class SshTunnelService : ServiceBase
{
private SshClient sshClient;
private Timer reconnectTimer;
// Added for multiple remote port forwards
private readonly List<ForwardedPortRemote> remotePorts = new List<ForwardedPortRemote>();
public SshTunnelService()
{
this.ServiceName = "SshTunnelService";
}
protected override void OnStart(string[] args)
{
reconnectTimer = new Timer(60000); // Reconnect every 60 seconds
reconnectTimer.Elapsed += (s, e) => EnsureConnected();
reconnectTimer.Start();
EnsureConnected(); // Initial connection attempt
}
protected override void OnStop()
{
reconnectTimer?.Stop();
// Stop all remote forwarded ports
foreach (var port in remotePorts)
{
port.Stop();
}
sshClient?.Disconnect();
sshClient?.Dispose();
}
private void EnsureConnected()
{
try
{
if (sshClient != null && sshClient.IsConnected)
return;
// β
Read connection info from AppSettings
string host = ConfigurationManager.AppSettings["Host"];
int port = int.Parse(ConfigurationManager.AppSettings["Port"]);
string username = ConfigurationManager.AppSettings["Username"];
// β OLD: Aes key and IV from AppSettings (now replaced with embedded resources)
/*
string OLD_privateKeyPath = ConfigurationManager.AppSettings["PrivateKeyPath"];
byte[] aesKey = Convert.FromBase64String(ConfigurationManager.AppSettings["AesKey"]);
byte[] aesIV = Convert.FromBase64String(ConfigurationManager.AppSettings["AesIV"]);
*/
// β
Embedded resource names
string keyResource = "SshTunnelServiceApp.Resources.aes.key"; // Adjust namespace + folder if needed
string ivResource = "SshTunnelServiceApp.Resources.aes.iv"; // Adjust namespace + folder if needed
string passphraseResource = "SshTunnelServiceApp.Resources.EncryptedPassphrase.bin";
string privateKeyResource = "SshTunnelServiceApp.Resources.privateKeyResource.ppk";
// β
Load and decrypt AES key from embedded resource
byte[] aesKey;
using (Stream keyStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(keyResource))
{
if (keyStream == null)
throw new Exception("Unable to find embedded AES key resource: " + keyResource);
using (MemoryStream msKey = new MemoryStream())
{
keyStream.CopyTo(msKey);
aesKey = System.Security.Cryptography.ProtectedData.Unprotect(msKey.ToArray(), null, DataProtectionScope.CurrentUser);
}
}
// β
Load and decrypt AES IV from embedded resource
byte[] aesIV;
using (Stream ivStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ivResource))
{
if (ivStream == null)
throw new Exception("Unable to find embedded AES IV resource: " + ivResource);
using (MemoryStream msIV = new MemoryStream())
{
ivStream.CopyTo(msIV);
aesIV = System.Security.Cryptography.ProtectedData.Unprotect(msIV.ToArray(), null, DataProtectionScope.CurrentUser);
}
}
// β
Load and decrypt passphrase using AES key and IV
using (Stream passphraseStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(passphraseResource))
{
if (passphraseStream == null)
throw new Exception("Unable to find embedded passphrase resource: " + passphraseResource);
using (MemoryStream passphraseMemory = new MemoryStream())
{
passphraseStream.CopyTo(passphraseMemory);
passphraseMemory.Position = 0;
string passphrase = Encryptor.DecryptStream(passphraseMemory, aesKey, aesIV);
// β
Load private key resource and initialize SSH client
using (Stream privateKeyStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(privateKeyResource))
{
if (privateKeyStream == null)
throw new Exception("Unable to find embedded private key resource: " + privateKeyResource);
using (MemoryStream privateKeyMemory = new MemoryStream())
{
privateKeyStream.CopyTo(privateKeyMemory);
privateKeyMemory.Position = 0;
var keyFile = new PrivateKeyFile(privateKeyMemory, passphrase);
sshClient = new SshClient(host, port, username, keyFile);
sshClient.Connect();
// β Old Local Forwarding (commented out)
/*
int localPort = int.Parse(ConfigurationManager.AppSettings["LocalPort"]);
string remoteHost = ConfigurationManager.AppSettings["RemoteHost"];
int remotePort = int.Parse(ConfigurationManager.AppSettings["RemotePort"]);
portForwarder = new ForwardedPortLocal("127.0.0.1", (uint)localPort, remoteHost, (uint)remotePort);
sshClient.AddForwardedPort(portForwarder);
portForwarder.Start();
*/
// β
Reverse Port Forwarding for ports 22, 3389, 5800, 5900
uint[] portsToForward = { 22, 3389, 5800, 5900 };
foreach (var remotePort in portsToForward)
{
var forwardedPort = new ForwardedPortRemote("0.0.0.0", remotePort, "127.0.0.1", remotePort);
sshClient.AddForwardedPort(forwardedPort);
forwardedPort.Start();
remotePorts.Add(forwardedPort);
}
}
}
}
}
}
catch (Exception ex)
{
EventLog.WriteEntry("SshTunnelService", "SSH Tunnel error: " + ex.Message, EventLogEntryType.Error);
}
}
}
}
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class Encryptor
{
public static void EncryptStringToFile(string plainText, string filePath, byte[] Key, byte[] IV)
{
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
ICryptoTransform encryptor = aes.CreateEncryptor();
using (FileStream fs = new FileStream(filePath, FileMode.Create))
using (CryptoStream cs = new CryptoStream(fs, encryptor, CryptoStreamMode.Write))
using (StreamWriter sw = new StreamWriter(cs))
{
sw.Write(plainText);
}
}
}
public static string DecryptStringFromFile(string filePath, byte[] Key, byte[] IV)
{
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
ICryptoTransform decryptor = aes.CreateDecryptor();
using (FileStream fs = new FileStream(filePath, FileMode.Open))
using (CryptoStream cs = new CryptoStream(fs, decryptor, CryptoStreamMode.Read))
using (StreamReader sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
//************
public static string DecryptStream(Stream encryptedStream, byte[] Key, byte[] IV)
{
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
ICryptoTransform decryptor = aes.CreateDecryptor();
using (CryptoStream cs = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
using (StreamReader sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
//************
}
/*using System.ServiceProcess;
static class Program
{
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new SshTunnelService() };
ServiceBase.Run(ServicesToRun);
}
}*/
using System.ServiceProcess;
static class Program
{
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new SshTunnelServiceApp.SshTunnelService() }; // Fully qualified
ServiceBase.Run(ServicesToRun);
}
}
- Compile the project in Release mode.
- Use
sc create
orinstallutil
to register the service. - Start it from the Services snap-in or via
sc start SshTunnelService
.
+-----------------------+
| SSH Client (C#) |
| using Renci.SshNet |
+----------+------------+
|
v
+-------------------------------+
| Reverse Port Forwarding |
| Ports: 22, 3389, 5800, |
| 5900 |
+----------+--------------------+
|
v
+------------------------+
| Windows Service |
| Auto-Restart Logic |
| AES Decryption Support |
+------------------------+
β
Encrypted Passphrase β Uses AES encryption for secure passphrase loading
β
Embedded Key β Prevents tampering
β
Auto Recovery β Handles disconnections automatically
This project is MIT Licensed. Feel free to contribute and customize!.