5.1 P/Invoke - Integrate DLLs

This page shows how to use OpenSSL via P/Invoke in .NET.


What is P/Invoke?

P/Invoke (Platform Invoke) enables calling native DLL functions from .NET:

.NET Code  →  P/Invoke  →  libcrypto-3-x64.dll  →  OpenSSL

Prepare DLLs

1. Copy DLLs to Project

cd MyProject
copy "D:\Projects\openssl-3.6.0\bin\bin\libcrypto-3-x64.dll" .\
copy "D:\Projects\openssl-3.6.0\bin\bin\libssl-3-x64.dll" .\

2. Modify .csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
 
  <ItemGroup>
    <!-- OpenSSL Native Libraries -->
    <None Update="libcrypto-3-x64.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="libssl-3-x64.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Simple P/Invoke Example

using System;
using System.Runtime.InteropServices;
 
namespace MyProject;
 
/// <summary>
/// OpenSSL P/Invoke Bindings
/// </summary>
public static class OpenSslInterop
{
    private const string LIBCRYPTO = "libcrypto-3-x64.dll";
 
    // Initialization
    [DllImport(LIBCRYPTO, CallingConvention = CallingConvention.Cdecl)]
    public static extern int OPENSSL_init_crypto(ulong opts, IntPtr settings);
 
    // Get version
    [DllImport(LIBCRYPTO, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr OpenSSL_version(int type);
 
    // Constants
    public const int OPENSSL_VERSION_STRING = 6;
    public const ulong OPENSSL_INIT_LOAD_CRYPTO_STRINGS = 0x00000002;
    public const ulong OPENSSL_INIT_ADD_ALL_CIPHERS = 0x00000004;
    public const ulong OPENSSL_INIT_ADD_ALL_DIGESTS = 0x00000008;
 
    /// <summary>
    /// Initialize OpenSSL
    /// </summary>
    public static void Initialize()
    {
        OPENSSL_init_crypto(
            OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
            OPENSSL_INIT_ADD_ALL_CIPHERS |
            OPENSSL_INIT_ADD_ALL_DIGESTS,
            IntPtr.Zero);
    }
 
    /// <summary>
    /// Returns the OpenSSL version
    /// </summary>
    public static string GetVersion()
    {
        var ptr = OpenSSL_version(OPENSSL_VERSION_STRING);
        return Marshal.PtrToStringAnsi(ptr) ?? "Unknown";
    }
}
 
// Usage:
class Program
{
    static void Main()
    {
        OpenSslInterop.Initialize();
        Console.WriteLine($"OpenSSL Version: {OpenSslInterop.GetVersion()}");
    }
}

ML-DSA Key Generation

public static class MlDsaInterop
{
    private const string LIBCRYPTO = "libcrypto-3-x64.dll";
 
    [DllImport(LIBCRYPTO)] private static extern IntPtr EVP_PKEY_CTX_new_from_name(IntPtr libctx, string name, IntPtr propq);
    [DllImport(LIBCRYPTO)] private static extern int EVP_PKEY_keygen_init(IntPtr ctx);
    [DllImport(LIBCRYPTO)] private static extern int EVP_PKEY_keygen(IntPtr ctx, out IntPtr pkey);
    [DllImport(LIBCRYPTO)] private static extern void EVP_PKEY_CTX_free(IntPtr ctx);
    [DllImport(LIBCRYPTO)] private static extern void EVP_PKEY_free(IntPtr pkey);
    [DllImport(LIBCRYPTO)] private static extern int i2d_PrivateKey(IntPtr pkey, ref IntPtr pp);
    [DllImport(LIBCRYPTO)] private static extern int i2d_PUBKEY(IntPtr pkey, ref IntPtr pp);
    [DllImport(LIBCRYPTO)] private static extern void OPENSSL_free(IntPtr ptr);
 
    /// <summary>
    /// Generates an ML-DSA-65 key pair
    /// </summary>
    public static (byte[] privateKey, byte[] publicKey) GenerateMlDsa65KeyPair()
    {
        IntPtr ctx = EVP_PKEY_CTX_new_from_name(IntPtr.Zero, "mldsa65", IntPtr.Zero);
        if (ctx == IntPtr.Zero)
            throw new Exception("Failed to create context");
 
        try
        {
            if (EVP_PKEY_keygen_init(ctx) <= 0)
                throw new Exception("Failed to init keygen");
 
            IntPtr pkey;
            if (EVP_PKEY_keygen(ctx, out pkey) <= 0)
                throw new Exception("Failed to generate key");
 
            try
            {
                // Export private key
                IntPtr privPtr = IntPtr.Zero;
                int privLen = i2d_PrivateKey(pkey, ref privPtr);
                byte[] privateKey = new byte[privLen];
                Marshal.Copy(privPtr, privateKey, 0, privLen);
                OPENSSL_free(privPtr);
 
                // Export public key
                IntPtr pubPtr = IntPtr.Zero;
                int pubLen = i2d_PUBKEY(pkey, ref pubPtr);
                byte[] publicKey = new byte[pubLen];
                Marshal.Copy(pubPtr, publicKey, 0, pubLen);
                OPENSSL_free(pubPtr);
 
                return (privateKey, publicKey);
            }
            finally
            {
                EVP_PKEY_free(pkey);
            }
        }
        finally
        {
            EVP_PKEY_CTX_free(ctx);
        }
    }
}

Complete Interop Class

For a complete P/Invoke implementation see:

WvdS.System.Security.Cryptography API

This library includes:

  • ML-DSA Sign/Verify
  • ML-KEM Encapsulation/Decapsulation
  • X.509 certificates with PQ algorithms
  • Hybrid signatures

Common Errors

"DLL not found"

System.DllNotFoundException: Unable to load DLL 'libcrypto-3-x64.dll'

Solution:

  1. DLLs present in output directory (bin/Debug/)?
  2. .csproj CopyToOutputDirectory correct?
  3. Platform correct? (x64 vs x86)

"Entry point not found"

System.EntryPointNotFoundException: Unable to find entry point 'XYZ'

Solution:

  1. Function name spelled correctly?
  2. CallingConvention.Cdecl set?
  3. OpenSSL version supports the function?

Memory Leaks

Always release resources!

  • EVP_PKEY_free(pkey)
  • EVP_PKEY_CTX_free(ctx)
  • OPENSSL_free(ptr)

Tips

LibraryImport instead of DllImport (from .NET 7)

// Modern approach
[LibraryImport("libcrypto-3-x64.dll")]
public static partial int OPENSSL_init_crypto(ulong opts, IntPtr settings);

Continue to


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: on 2026/01/30 at 08:47 AM