DICOM Basics using .NET and C# - Secure Communications (TLS)

Introduction

This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOM secure communications using TLS (Transport Layer Security), which provides encryption, authentication, and data integrity for DICOM network operations.

With increasing regulatory requirements like HIPAA, secure communications are essential for protecting Protected Health Information (PHI) during transmission between DICOM applications.

Prerequisites

Before you begin, ensure you have the following:

  • A .NET development environment (Visual Studio or Visual Studio Code)
  • The Fellow Oak DICOM library (fo-dicom) installed via NuGet
  • Basic understanding of DICOM network concepts
  • Understanding of TLS/SSL and X.509 certificates
  • You can find all the code demonstrated in this tutorial on GitHub here

“Life is what happens to you while you’re busy making other plans.” ~ John Lennon

The Theory Behind Secure Communications

TLS (Transport Layer Security) provides security at the transport layer, creating an encrypted tunnel through which application protocols operate unchanged. For DICOM, this means the same DIMSE operations work identically whether over plain TCP or TLS - only the transport is secured. This layered design follows good network architecture principles: security as a separate concern from application logic.

The TLS handshake establishes a secure channel through a choreographed exchange: client hello (supported cipher suites), server hello (chosen cipher suite + certificate), client verification (checks certificate chain), key exchange (establish session keys), and finished messages (verify handshake integrity). This dance, completed in milliseconds, creates mutual agreement on encryption without transmitting secrets in the clear.

Certificate trust models determine who you trust to vouch for identity. Public CAs work for the general internet but may not fit healthcare's needs. Private CAs under organizational control provide tighter trust boundaries. Some deployments use mutual TLS (mTLS) where both client and server present certificates, ensuring bilateral authentication - critical when PHI flows in both directions.

The cipher suite specifies algorithms for key exchange, bulk encryption, and integrity checking. Modern best practice (BCP 195) mandates TLS 1.2+ with AEAD ciphers like AES-GCM that combine encryption and authentication. Older cipher suites using MD5, SHA-1, or RC4 have known weaknesses and should be disabled. The tension between security and compatibility with legacy systems is a constant operational challenge.

From a HIPAA perspective, the Security Rule requires technical safeguards for PHI in transit. While HIPAA is "implementation-neutral" and doesn't mandate specific technologies, TLS-encrypted DICOM communications is the standard approach. Audit logs should record TLS connection details (cipher suite, certificate subject) for compliance evidence.

Why Use DICOM TLS?

DICOM TLS provides four essential security features:

  • Encryption: PHI is encrypted during transmission, preventing eavesdropping
  • Authentication: Verify the identity of communication partners
  • Integrity: Detect any tampering during transmission via MAC verification
  • Compliance: Meet HIPAA requirements for PHI transmission over networks

DICOM TLS Connection Profiles

DICOM PS3.15 defines several TLS profiles:

ProfileTLS VersionCipher SuitesStatus
Basic TLSTLS 1.0+RSA + 3DES/AESLegacy
AES TLSTLS 1.0+AES-128/AES-256Recommended
BCP 195 TLSTLS 1.2+AES-GCM, ECDHECurrent Best Practice

Step 1 of 4: TLS Concepts

Here are the key TLS concepts for DICOM:

using System;
using System.Diagnostics;
using FellowOakDicom;

namespace DicomSecureCommunications
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            LogToDebugConsole("=== DICOM Secure Communications (TLS) Demo ===");
            LogToDebugConsole("");

            DemonstrateTlsConcepts();
            DemonstrateCertificateRequirements();
            DemonstrateFoDicomTlsConfig();
            DemonstrateTroubleshooting();
        }

        private static void DemonstrateTlsConcepts()
        {
            LogToDebugConsole("--- TLS Concepts ---");
            LogToDebugConsole("");

            LogToDebugConsole("DICOM TLS Ports:");
            LogToDebugConsole("  Port 2761 - DICOM ISCL (Integrated Secure Communication Layer - retired)");
            LogToDebugConsole("  Port 2762 - DICOM TLS");
            LogToDebugConsole("  Custom ports are also common");
            LogToDebugConsole("");

            LogToDebugConsole("BCP 195 TLS Profile (Current Best Practice):");
            LogToDebugConsole("  - TLS 1.2 or higher (TLS 1.3 preferred)");
            LogToDebugConsole("  - Modern cipher suites");
            LogToDebugConsole("  - ECDHE key exchange preferred");
            LogToDebugConsole("  - AES-GCM encryption");
        }

        private static void LogToDebugConsole(string message)
        {
            Debug.WriteLine(message);
        }
    }
}

Step 2 of 4: Certificate Requirements

TLS requires X.509 certificates for authentication:

private static void DemonstrateCertificateRequirements()
{
    LogToDebugConsole("--- Certificate Requirements ---");
    LogToDebugConsole("");

    LogToDebugConsole("Server Certificate:");
    LogToDebugConsole("  - X.509 v3 certificate for the DICOM server");
    LogToDebugConsole("  - Should be signed by trusted CA");
    LogToDebugConsole("  - Common Name (CN) should match hostname");
    LogToDebugConsole("  - Subject Alternative Names (SAN) recommended");
    LogToDebugConsole("");

    LogToDebugConsole("Client Certificate (for mutual authentication):");
    LogToDebugConsole("  - X.509 v3 certificate for the client");
    LogToDebugConsole("  - Required if server demands client auth");
    LogToDebugConsole("  - CN often set to AE Title");
    LogToDebugConsole("");

    LogToDebugConsole("Trust Store:");
    LogToDebugConsole("  - Contains CA certificates to trust");
    LogToDebugConsole("  - May include root and intermediate CAs");
    LogToDebugConsole("  - Both server and client need trust stores");
    LogToDebugConsole("");

    LogToDebugConsole("Creating Self-Signed Certificates (PowerShell):");
    LogToDebugConsole("  New-SelfSignedCertificate -DnsName \"localhost\" \\");
    LogToDebugConsole("    -CertStoreLocation \"cert:\\LocalMachine\\My\" \\");
    LogToDebugConsole("    -KeyAlgorithm RSA -KeyLength 2048 \\");
    LogToDebugConsole("    -NotAfter (Get-Date).AddYears(1)");
}
Certificate ComponentRequirement
Key SizeRSA 2048+ or ECDSA 256+
Signature AlgorithmSHA-256 or stronger
ValidityNot expired
UsageKey Usage: Digital Signature
TrustSigned by trusted CA (or self-signed for testing)

Step 3 of 4: fo-dicom TLS Configuration

Here's how to configure TLS in fo-dicom:

private static void DemonstrateFoDicomTlsConfig()
{
    LogToDebugConsole("--- fo-dicom TLS Configuration ---");
    LogToDebugConsole("");

    LogToDebugConsole("TLS Client Example:");
    LogToDebugConsole("");
    LogToDebugConsole("  // Load certificate");
    LogToDebugConsole("  var cert = new X509Certificate2(\"client.pfx\", password);");
    LogToDebugConsole("");
    LogToDebugConsole("  // Create client with TLS enabled");
    LogToDebugConsole("  var client = DicomClientFactory.Create(");
    LogToDebugConsole("      host, port, useTls: true,");
    LogToDebugConsole("      callingAe, calledAe);");
    LogToDebugConsole("");
    LogToDebugConsole("  // Configure TLS options");
    LogToDebugConsole("  client.ServiceOptions.TlsInitiator =");
    LogToDebugConsole("      DicomTlsInitiator.Create(");
    LogToDebugConsole("          () => new SslClientAuthenticationOptions {");
    LogToDebugConsole("              ClientCertificates = new X509Certificate2Collection(cert),");
    LogToDebugConsole("              TargetHost = host");
    LogToDebugConsole("          });");
    LogToDebugConsole("");
    LogToDebugConsole("  // Add request and send");
    LogToDebugConsole("  await client.AddRequestAsync(new DicomCEchoRequest());");
    LogToDebugConsole("  await client.SendAsync();");
    LogToDebugConsole("");

    LogToDebugConsole("TLS Server Example:");
    LogToDebugConsole("");
    LogToDebugConsole("  // Load server certificate");
    LogToDebugConsole("  var cert = new X509Certificate2(\"server.pfx\", password);");
    LogToDebugConsole("");
    LogToDebugConsole("  // Create server with TLS");
    LogToDebugConsole("  var server = DicomServerFactory.Create<DicomCEchoProvider>(port);");
    LogToDebugConsole("  server.Options.TlsAcceptor = DicomTlsAcceptor.Create(");
    LogToDebugConsole("      () => new SslServerAuthenticationOptions {");
    LogToDebugConsole("          ServerCertificate = cert");
    LogToDebugConsole("      });");
}

Step 4 of 4: Troubleshooting TLS Issues

Common TLS issues and solutions:

private static void DemonstrateTroubleshooting()
{
    LogToDebugConsole("--- TLS Troubleshooting ---");
    LogToDebugConsole("");

    LogToDebugConsole("1. Certificate not trusted");
    LogToDebugConsole("   - Solution: Add CA to trust store");
    LogToDebugConsole("   - Or: Custom validation callback (not for production)");
    LogToDebugConsole("");

    LogToDebugConsole("2. Hostname mismatch");
    LogToDebugConsole("   - Solution: CN or SAN must match server hostname");
    LogToDebugConsole("   - Ensure TargetHost is set correctly");
    LogToDebugConsole("");

    LogToDebugConsole("3. Cipher suite mismatch");
    LogToDebugConsole("   - Solution: Enable compatible cipher suites");
    LogToDebugConsole("   - Check server and client supported ciphers");
    LogToDebugConsole("");

    LogToDebugConsole("4. Certificate expired");
    LogToDebugConsole("   - Solution: Renew certificate");
    LogToDebugConsole("   - Set up certificate rotation process");
    LogToDebugConsole("");

    LogToDebugConsole("5. Protocol version mismatch");
    LogToDebugConsole("   - Solution: Ensure TLS versions match");
    LogToDebugConsole("   - Prefer TLS 1.2 or higher");
    LogToDebugConsole("");

    LogToDebugConsole("Debugging Tips:");
    LogToDebugConsole("  - Use Wireshark to capture TLS handshake");
    LogToDebugConsole("  - Check Windows Event Log for SChannel errors");
    LogToDebugConsole("  - Verify certificate chain with OpenSSL:");
    LogToDebugConsole("    openssl s_client -connect host:port");
}

Implementation Example

Complete TLS client example:

using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using FellowOakDicom;
using FellowOakDicom.Network;
using FellowOakDicom.Network.Tls;

public class DicomTlsClient
{
    public async Task SendWithTlsAsync(string host, int port,
        string callingAe, string calledAe, string certPath, string password)
    {
        // Load client certificate
        var cert = new X509Certificate2(certPath, password);

        // Create client with TLS
        var client = DicomClientFactory.Create(host, port, true, callingAe, calledAe);

        // Configure TLS
        client.ServiceOptions.TlsInitiator = DicomTlsInitiator.Create(
            () => new SslClientAuthenticationOptions
            {
                ClientCertificates = new X509Certificate2Collection(cert),
                TargetHost = host,
                EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12 |
                                      System.Security.Authentication.SslProtocols.Tls13,
                // For testing only - don't use in production
                RemoteCertificateValidationCallback = ValidateServerCertificate
            });

        // Add C-ECHO request
        await client.AddRequestAsync(new DicomCEchoRequest());

        // Send with TLS
        await client.SendAsync();
    }

    private bool ValidateServerCertificate(object sender,
        X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
    {
        // In production, properly validate the certificate
        if (errors == SslPolicyErrors.None)
            return true;

        // Log errors for debugging
        Console.WriteLine($"Certificate errors: {errors}");

        // For testing only - accept all certificates
        // return true;

        // For production - reject invalid certificates
        return false;
    }
}

Security Best Practices

  • Use TLS 1.2+: Disable older protocols (SSL 3.0, TLS 1.0, TLS 1.1)
  • Strong cipher suites: Prefer AES-GCM with ECDHE key exchange
  • Certificate management: Use CA-signed certificates in production
  • Key protection: Store private keys securely (HSM for high-security)
  • Mutual authentication: Consider client certificates for sensitive environments
  • Regular renewal: Implement certificate rotation before expiry

Conclusion

DICOM TLS is essential for protecting patient data during network transmission. While the configuration can be complex, fo-dicom provides the necessary APIs to implement secure DICOM communications in your .NET applications.

For production deployments, always use properly signed certificates from a trusted CA, enable only modern TLS versions and cipher suites, and implement proper certificate validation. Testing with self-signed certificates is acceptable during development, but never skip certificate validation in production systems.

Please check out the next tutorial in this series where we cover DICOM encapsulated documents.