[C#] How to Verify Digital Signatures Using RSA Public Keys

目次

Overview

This implementation verifies that data has not been tampered with (integrity) and that it was sent by the correct sender (authenticity) by using the received data, the digital signature, and the sender’s public key. In modern .NET implementations, you use the VerifyData method of the RSA class for a simple and secure process instead of the older RSAPKCS1SignatureDeformatter class.

Specifications (Input/Output)

  • Input: – Text data to be verified (UTF-8).
    • Digital signature (Base64 string).
    • Sender’s RSA public key (XML string).
  • Output: Verification result (bool: true if valid, false if tampered with or inconsistent).
  • Prerequisites: Use the same hash algorithm (e.g., SHA256) and padding mode (e.g., PKCS#1) as when the signature was created.

Basic Usage

Call the validation method as follows:

using System.Security.Cryptography;
using System.Text;

// Call the signature verification method
bool isValid = ValidateSignature(originalData, signatureBase64, publicKeyXml);

if (isValid)
{
    Console.WriteLine("Signature is valid. The data is trusted.");
}
else
{
    Console.WriteLine("Warning: The signature is invalid or the data has been tampered with.");
}

Full Code Example

The following is a console application that includes the signature verification logic and test code.

using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
    static void Main()
    {
        // 1. Prepare test data
        // In reality, you use data and a signature received via a network, etc.
        string receivedMessage = "OrderConfirmed: ID-998877";
        
        // For testing, we generate a temporary key pair and signature (normally not needed)
        GenerateTestEnvironment(receivedMessage, out string publicKeyXml, out string validSignature);

        Console.WriteLine($"[Received Message] {receivedMessage}");
        Console.WriteLine($"[Public Key] Found XML key info.");
        Console.WriteLine($"[Signature] {validSignature.Substring(0, 20)}...");

        // 2. Verify a valid signature
        Console.WriteLine("\n--- Test 1: Valid Signature ---");
        bool result1 = VerifyDigitalSignature(receivedMessage, validSignature, publicKeyXml);
        Console.WriteLine($"Verification Result: {(result1 ? "OK (Trusted)" : "NG (Tampered)")}");

        // 3. Verify tampered data
        Console.WriteLine("\n--- Test 2: Tampered Data ---");
        string tamperedMessage = "OrderConfirmed: ID-000000"; // Modifying the ID
        bool result2 = VerifyDigitalSignature(tamperedMessage, validSignature, publicKeyXml);
        Console.WriteLine($"Verification Result: {(result2 ? "OK (Trusted)" : "NG (Tampered)")}");
    }

    /// <summary>
    /// Receives a message, signature, and public key to verify validity.
    /// </summary>
    /// <param name="data">The original data to verify.</param>
    /// <param name="signatureBase64">Base64 formatted signature.</param>
    /// <param name="publicKeyXml">RSA public key in XML format.</param>
    /// <returns>True if the signature is valid.</returns>
    static bool VerifyDigitalSignature(string data, string signatureBase64, string publicKeyXml)
    {
        try
        {
            // Convert data to byte array
            byte[] dataBytes = Encoding.UTF8.GetBytes(data);
            
            // Convert signature to byte array
            byte[] signatureBytes = Convert.FromBase64String(signatureBase64);

            // Create RSA instance
            using var rsa = RSA.Create();
            
            // Load public key
            // Use ImportFromPem for non-XML formats like PEM
            rsa.FromXmlString(publicKeyXml);

            // Execute verification
            // Important: Use the same "hash algorithm" and "padding" as the signing process
            return rsa.VerifyData(
                dataBytes,
                signatureBytes,
                HashAlgorithmName.SHA256,
                RSASignaturePadding.Pkcs1);
        }
        catch (FormatException)
        {
            Console.WriteLine("Error: Invalid Base64 signature format.");
            return false;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
            return false;
        }
    }

    // Helper to generate signature for testing (signing logic)
    static void GenerateTestEnvironment(string msg, out string pubKey, out string sig)
    {
        using var rsa = RSA.Create(2048);
        pubKey = rsa.ToXmlString(false);
        byte[] data = Encoding.UTF8.GetBytes(msg);
        byte[] sigBytes = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        sig = Convert.ToBase64String(sigBytes);
    }
}

Customization Points

  • Hash Algorithm Consistency: The HashAlgorithmName.SHA256 must perfectly match the one used by the signer. If the signer used SHA1 or SHA512, you must change this setting.
  • Padding Mode: While RSASignaturePadding.Pkcs1 is common, newer systems may use the more robust Pss. Check your technical specifications.
  • Public Key Format: This example uses FromXmlString. If you are using keys generated by OpenSSL, switch the implementation to use ImportFromPem or ImportSubjectPublicKeyInfo.

Important Notes

  • Reasons for Verification Failure: If VerifyData returns false, it may not just mean the data was tampered with. It could also be due to a mismatched public key, incorrect hash algorithm, or different text encoding (UTF-8 vs. Shift-JIS).
  • Avoiding Old APIs: Using the RSAPKCS1SignatureDeformatter class is redundant in modern .NET environments and is not recommended. Methods integrated into the RSA class provide cleaner, cross-platform code.
  • Exception Handling: Exceptions will occur if the Base64 string is corrupted or the XML key format is invalid. You should catch these within your verification method to prevent application crashes.

Advanced Usage

Verifying Hash Values (Digests) Directly

Use this implementation if you are verifying a pre-calculated hash value instead of the entire data.

static bool VerifyHashDirectly(byte[] hashDigest, byte[] signature, string publicKeyXml)
{
    using var rsa = RSA.Create();
    rsa.FromXmlString(publicKeyXml);

    // Use VerifyHash instead of VerifyData
    return rsa.VerifyHash(
        hashDigest, 
        signature, 
        HashAlgorithmName.SHA256, 
        RSASignaturePadding.Pkcs1);
}

Conclusion

Digital signature verification can be handled entirely by the RSA.VerifyData method. The most important requirement is to match the “hash algorithm” and “padding method” exactly with the ones used during signature creation. If these do not match, verification will always fail even if the data is correct. Since XML-formatted keys are specific to .NET, designing your system to support PEM format will increase flexibility when integrating with external systems.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次