DICOM Basics using .NET and C# - Digital Signatures
Introduction
This is part of my series of articles on the DICOM standard. In this tutorial, we'll explore DICOM digital signatures, which provide mechanisms for ensuring data integrity, authentication, and non-repudiation of medical imaging data.
Digital signatures are critical for medico-legal requirements, audit trails, and ensuring that diagnostic data hasn't been tampered with. DICOM defines specific signature profiles and structures for signing medical images and reports.
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 concepts from previous tutorials
- Understanding of public key cryptography concepts
- You can find all the code demonstrated in this tutorial on GitHub here
“The only truly secure system is one that is powered off, cast in a block of concrete, and sealed in a lead-lined room with armed guards.” ~ Gene Spafford
The Theory Behind Digital Signatures
Digital signatures apply public key cryptography to provide mathematical proof of data integrity and authenticity. Unlike simple checksums (which anyone can recalculate), a digital signature requires a private key that only the signer possesses. To sign data, you hash it and encrypt the hash with your private key. To verify, someone decrypts with your public key and compares hashes. If they match, the data is unchanged and came from the private key holder.
In medical imaging, the stakes are high. A maliciously altered CT image could lead to wrong diagnosis; a modified radiology report could affect treatment decisions. Digital signatures provide tamper evidence: any modification, even a single bit, produces a different hash that won't match the signature. This mathematical guarantee is far stronger than administrative controls or access logs.
The DICOM signature structure allows signing specific attributes rather than the entire file. This is important because some attributes legitimately change after signing (like Storage Location) while others must remain inviolate (Patient ID, Pixel Data). The Data Elements Signed sequence explicitly lists what's covered, creating a precise scope for the integrity guarantee.
X.509 certificates bind identity to public keys. Without certificates, you could verify that data is unchanged since signing, but not who signed it. A Certificate Authority vouches for the identity of certificate holders, creating a chain of trust. In healthcare, this might link to a specific radiologist, establishing non-repudiation for medico-legal purposes.
The timestamp problem matters for signatures: a signature proves data existed in its current form when signed, but when was that? Trusted timestamp authorities can countersign the signature with their time, providing third-party evidence of when the signature was created. This is important for audit trails and legal proceedings.
Why Digital Signatures in DICOM?
Digital signatures serve three essential purposes in medical imaging:
- Data Integrity: Detect any modification to signed data - critical for diagnostic accuracy
- Authentication: Verify the identity of the signer and confirm the source of data
- Non-repudiation: Prevent the signer from denying having signed - important for legal evidence
Digital Signature Structure in DICOM
DICOM digital signatures are stored in specific sequences within the dataset:
| Sequence/Attribute | Tag | Description |
|---|---|---|
| Digital Signatures Sequence | (FFFA,FFFA) | Contains one or more signatures |
| MAC Parameters Sequence | (4FFE,0001) | Hash algorithm parameters |
| MAC ID Number | (0400,0005) | Links signature to MAC parameters |
| Digital Signature UID | (0400,0100) | Unique identifier for signature |
| Digital Signature DateTime | (0400,0105) | When signature was created |
| Certificate of Signer | (0400,0115) | X.509 certificate (encoded) |
| Signature | (0400,0120) | Actual signature bytes |
Step 1 of 4: Understanding Signature Structure
Here's the conceptual structure of DICOM digital signatures:
using System;
using System.Diagnostics;
using FellowOakDicom;
namespace DicomDigitalSignatures
{
public class Program
{
public static void Main(string[] args)
{
try
{
LogToDebugConsole("=== DICOM Digital Signatures Demo ===");
LogToDebugConsole("");
// Overview of digital signatures
DemonstrateSignatureOverview();
// Signature structure
DemonstrateSignatureStructure();
// Signature profiles
DemonstrateSignatureProfiles();
// Verification process
DemonstrateVerificationProcess();
}
catch (Exception e)
{
LogToDebugConsole($"Error: {e.Message}");
}
}
private static void DemonstrateSignatureOverview()
{
LogToDebugConsole("--- Why Digital Signatures? ---");
LogToDebugConsole("");
LogToDebugConsole("1. Data Integrity");
LogToDebugConsole(" - Detect any modification to the signed data");
LogToDebugConsole(" - Critical for diagnostic accuracy");
LogToDebugConsole(" - Supports medico-legal requirements");
LogToDebugConsole("");
LogToDebugConsole("2. Authentication");
LogToDebugConsole(" - Verify identity of the signer");
LogToDebugConsole(" - Confirm source of the data");
LogToDebugConsole(" - Support audit trails");
LogToDebugConsole("");
LogToDebugConsole("3. Non-repudiation");
LogToDebugConsole(" - Signer cannot deny having signed");
LogToDebugConsole(" - Important for legal evidence");
LogToDebugConsole(" - Supports accountability");
}
private static void LogToDebugConsole(string message)
{
Debug.WriteLine(message);
}
}
}
Step 2 of 4: Digital Signature Attributes
Each signature item in the Digital Signatures Sequence contains these key attributes:
private static void DemonstrateSignatureStructure()
{
LogToDebugConsole("--- Digital Signature Structure ---");
LogToDebugConsole("");
LogToDebugConsole("Digital Signatures Sequence (FFFA,FFFA):");
LogToDebugConsole(" This sequence contains one or more signatures.");
LogToDebugConsole("");
LogToDebugConsole("Each Digital Signature Item contains:");
LogToDebugConsole("");
LogToDebugConsole(" MAC ID Number (0400,0005)");
LogToDebugConsole(" Identifies the MAC (Message Auth Code) algorithm");
LogToDebugConsole("");
LogToDebugConsole(" Digital Signature UID (0400,0100)");
LogToDebugConsole(" Unique identifier for this signature");
LogToDebugConsole("");
LogToDebugConsole(" Digital Signature DateTime (0400,0105)");
LogToDebugConsole(" When the signature was created");
LogToDebugConsole("");
LogToDebugConsole(" Certificate Type (0400,0110)");
LogToDebugConsole(" X509_1993_SIG (X.509 certificate)");
LogToDebugConsole("");
LogToDebugConsole(" Certificate of Signer (0400,0115)");
LogToDebugConsole(" The X.509 certificate (encoded)");
LogToDebugConsole("");
LogToDebugConsole(" Signature (0400,0120)");
LogToDebugConsole(" The actual digital signature bytes");
LogToDebugConsole("");
LogToDebugConsole("--- MAC Parameters Sequence (4FFE,0001) ---");
LogToDebugConsole("");
LogToDebugConsole(" MAC Calculation Transfer Syntax UID (0400,0010)");
LogToDebugConsole(" How data was encoded for MAC calculation");
LogToDebugConsole("");
LogToDebugConsole(" MAC Algorithm (0400,0015)");
LogToDebugConsole(" RIPEMD160, MD5, SHA1, SHA256, SHA384, SHA512");
LogToDebugConsole("");
LogToDebugConsole(" Data Elements Signed (0400,0020)");
LogToDebugConsole(" List of attribute tags that were signed");
}
Step 3 of 4: DICOM Signature Profiles
DICOM defines several signature profiles for different use cases:
private static void DemonstrateSignatureProfiles()
{
LogToDebugConsole("--- DICOM Signature Profiles ---");
LogToDebugConsole("");
LogToDebugConsole("1. Base RSA Digital Signature Profile");
LogToDebugConsole(" - Minimum requirements for any DICOM signature");
LogToDebugConsole(" - RSA with SHA-256 or stronger");
LogToDebugConsole(" - 2048-bit minimum key size");
LogToDebugConsole("");
LogToDebugConsole("2. Creator RSA Digital Signature Profile");
LogToDebugConsole(" - For equipment creating DICOM objects");
LogToDebugConsole(" - Signs entire dataset (except signatures)");
LogToDebugConsole(" - Ensures data hasn't changed since creation");
LogToDebugConsole("");
LogToDebugConsole("3. Authorization Digital Signature Profile");
LogToDebugConsole(" - For physicians authorizing reports/results");
LogToDebugConsole(" - Signs clinically relevant attributes");
LogToDebugConsole(" - Includes signer identification");
LogToDebugConsole("");
LogToDebugConsole("4. SR RSA Digital Signature Profile");
LogToDebugConsole(" - Specifically for Structured Reports");
LogToDebugConsole(" - Signs content items");
LogToDebugConsole(" - Supports report verification");
LogToDebugConsole("");
LogToDebugConsole("--- Attributes Commonly Signed ---");
LogToDebugConsole("");
LogToDebugConsole("Always signed:");
LogToDebugConsole(" - SOP Class UID, SOP Instance UID");
LogToDebugConsole(" - Patient Name, Patient ID");
LogToDebugConsole(" - Study/Series Instance UIDs");
LogToDebugConsole(" - Pixel Data (for images)");
LogToDebugConsole(" - Content Sequence (for SR)");
LogToDebugConsole("");
LogToDebugConsole("Usually excluded from signing:");
LogToDebugConsole(" - File Meta Information");
LogToDebugConsole(" - Digital Signatures Sequence itself");
LogToDebugConsole(" - Group Length elements");
}
Step 4 of 4: Signature Verification Process
Verifying a DICOM digital signature involves these steps:
private static void DemonstrateVerificationProcess()
{
LogToDebugConsole("--- Signature Verification Process ---");
LogToDebugConsole("");
LogToDebugConsole("1. Extract Signature Information");
LogToDebugConsole(" - Read Digital Signatures Sequence");
LogToDebugConsole(" - Get certificate and signature bytes");
LogToDebugConsole(" - Get list of signed attributes");
LogToDebugConsole("");
LogToDebugConsole("2. Validate Certificate");
LogToDebugConsole(" - Check certificate chain to trusted CA");
LogToDebugConsole(" - Verify certificate is not expired");
LogToDebugConsole(" - Check certificate is not revoked (CRL/OCSP)");
LogToDebugConsole("");
LogToDebugConsole("3. Calculate MAC");
LogToDebugConsole(" - Extract signed attributes");
LogToDebugConsole(" - Encode using specified transfer syntax");
LogToDebugConsole(" - Calculate hash using specified algorithm");
LogToDebugConsole("");
LogToDebugConsole("4. Verify Signature");
LogToDebugConsole(" - Use public key from certificate");
LogToDebugConsole(" - Decrypt signature to get original hash");
LogToDebugConsole(" - Compare with calculated hash");
LogToDebugConsole("");
LogToDebugConsole("5. Report Result");
LogToDebugConsole(" - VALID: Hashes match, certificate valid");
LogToDebugConsole(" - INVALID: Hashes don't match (data modified)");
LogToDebugConsole(" - UNKNOWN: Cannot verify certificate chain");
}
Implementation Example
Here's how you would work with digital signatures in fo-dicom:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using FellowOakDicom;
// Creating a signature (conceptual - requires full implementation)
public void SignDicomFile(string dicomPath, string certificatePath, string password)
{
// Load the certificate with private key
var cert = new X509Certificate2(certificatePath, password,
X509KeyStorageFlags.Exportable);
var privateKey = cert.GetRSAPrivateKey();
// Open the DICOM file
var file = DicomFile.Open(dicomPath);
var dataset = file.Dataset;
// Note: fo-dicom doesn't have built-in signing
// You would need to implement:
// 1. Collect attributes to sign
// 2. Encode them per MAC transfer syntax
// 3. Calculate hash (SHA-256)
// 4. Sign hash with private key
// 5. Create MAC Parameters Sequence
// 6. Create Digital Signatures Sequence
// 7. Add sequences to dataset
}
// Checking for existing signatures
public bool HasDigitalSignatures(DicomDataset dataset)
{
return dataset.Contains(DicomTag.DigitalSignaturesSequence);
}
// Reading signature information
public void ReadSignatureInfo(DicomDataset dataset)
{
var sigSeq = dataset.GetSequence(DicomTag.DigitalSignaturesSequence);
if (sigSeq != null)
{
foreach (var item in sigSeq.Items)
{
var sigUid = item.GetSingleValueOrDefault(
DicomTag.DigitalSignatureUID, "");
var sigDateTime = item.GetSingleValueOrDefault(
DicomTag.DigitalSignatureDateTime, "");
Console.WriteLine($"Signature UID: {sigUid}");
Console.WriteLine($"Signed at: {sigDateTime}");
}
}
}
Algorithm Requirements
DICOM specifies these cryptographic requirements:
| Component | Requirement |
|---|---|
| Hash Algorithm | SHA-256 or stronger (SHA-384, SHA-512) |
| Signature Algorithm | RSA with minimum 2048-bit keys |
| Certificate | X.509 v3 with Digital Signature key usage |
| Alternative | ECDSA with 256+ bit keys |
Best Practices
- Use SHA-256 or stronger: MD5 and SHA-1 are deprecated
- Use 2048+ bit RSA keys: Shorter keys are vulnerable
- Include timestamps: Use trusted timestamp authority
- Protect private keys: Use HSMs for high-security environments
- Certificate management: Have revocation strategy (CRL/OCSP)
- Document policy: Define what gets signed and by whom
Conclusion
Digital signatures in DICOM provide essential security guarantees for medical imaging data. While implementing full signature support requires significant cryptographic work, understanding the structure and profiles helps when integrating with systems that use digital signatures.
Many PACS and enterprise imaging systems support digital signatures out of the box. When working with such systems, ensure your applications can properly read and validate signatures, even if they don't create them.
Please check out the next tutorial in this series where we cover DICOM secure communications using TLS.