WasmCryptoProvider

Namespace: WvdS.System.Security.Cryptography.Providers

JavaScript Interop-based crypto provider for Blazor WebAssembly. Communicates via IJSRuntime with openssl.wasm.

Overview

The WasmCryptoProvider enables post-quantum cryptography in Blazor WebAssembly applications through:

  • JavaScript Interop to WebAssembly-compiled OpenSSL
  • Fully asynchronous API (required for JS Interop)
  • Identical functionality to NativeCryptoProvider

Architecture

Blazor WebAssembly
      |
      v
+-----------------+
| WasmCrypto-     |
| Provider        |
| (C#)            |
+--------+--------+
         | IJSRuntime.InvokeAsync
         v
+-----------------+
| wvds-crypto.js  |
| (JavaScript)    |
+--------+--------+
         |
         v
+-----------------+
| openssl.wasm    |
| (WebAssembly)   |
+-----------------+

Properties

Property Type Description
Name string "WASM (JS Interop)"
IsAvailable bool true if initialized

Dependency Injection

// Program.cs (Blazor WebAssembly)
builder.Services.AddScoped<ICryptoProvider>(sp =>
    new WasmCryptoProvider(sp.GetRequiredService<IJSRuntime>()));

Initialization

@inject ICryptoProvider CryptoProvider
 
@code {
    protected override async Task OnInitializedAsync()
    {
        await CryptoProvider.InitializeAsync();
 
        if (CryptoProvider.IsAvailable)
        {
            var version = CryptoProvider.GetOpenSslVersion();
            Console.WriteLine($"OpenSSL WASM: {version}");
        }
    }
}

Required JS/WASM Files

In wwwroot/index.html:

<head>
    <!-- OpenSSL WASM Module -->
    <script src="_content/WvdS.Crypto/openssl.js"></script>
 
    <!-- WvdS Crypto Wrapper -->
    <script src="_content/WvdS.Crypto/wvds-crypto.js"></script>
</head>

ML-DSA Operations

GenerateMlDsaKeyPairAsync

var (publicKey, privateKey) = await provider.GenerateMlDsaKeyPairAsync("ML-DSA-65");

SignMlDsaAsync

byte[] data = Encoding.UTF8.GetBytes("Browser-signed data");
byte[] signature = await provider.SignMlDsaAsync(data, privateKey);

VerifyMlDsaAsync

bool isValid = await provider.VerifyMlDsaAsync(data, signature, publicKey);

ML-KEM Operations

GenerateMlKemKeyPairAsync

var (publicKey, privateKey) = await provider.GenerateMlKemKeyPairAsync("ML-KEM-768");

EncapsulateAsync

var (sharedSecret, ciphertext) = await provider.EncapsulateAsync(recipientPublicKey);

DecapsulateAsync

byte[] sharedSecret = await provider.DecapsulateAsync(ciphertext, privateKey);

Key Derivation Operations

Pbkdf2Async

PBKDF2 via Web Crypto API.

byte[] salt = await provider.RandomBytesAsync(32);
 
byte[] derivedKey = await provider.Pbkdf2Async(
    password: "UserPassword",
    salt: salt,
    iterations: 100000,
    outputLength: 32,
    hash: "SHA-256");

Pbkdf2WithPqSaltAsync

PBKDF2 with PQ-enhanced salt.

byte[] key = await provider.Pbkdf2WithPqSaltAsync(
    password: "UserPassword",
    baseSalt: baseSalt,
    pqPublicKey: recipientPqPublicKey,
    iterations: 100000,
    outputLength: 32);

Argon2idAsync

Memory-hard KDF via OpenSSL WASM.

byte[] key = await provider.Argon2idAsync(
    password: passwordBytes,
    salt: salt,
    outputLength: 32,
    iterations: 3,
    memoryKiB: 65536,
    parallelism: 4);

Stream/Chunked Encryption

EncryptChunkedAsync / DecryptChunkedAsync

For large data volumes with chunk processing.

byte[] key = await provider.RandomBytesAsync(32);
 
// Encrypt
byte[] encrypted = await provider.EncryptChunkedAsync(
    plaintext,
    key,
    chunkSize: 65536);  // 64 KB Chunks
 
// Decrypt
byte[] decrypted = await provider.DecryptChunkedAsync(encrypted, key);

EncryptStreamWithPqKeyAsync / DecryptStreamWithPqKeyAsync

Combines ML-KEM key exchange with chunked encryption.

// Encrypt for recipient
var (kemCiphertext, encryptedData) = await provider.EncryptStreamWithPqKeyAsync(
    plaintext,
    recipientPublicKey,
    chunkSize: 65536);
 
// Decrypt
byte[] decrypted = await provider.DecryptStreamWithPqKeyAsync(
    kemCiphertext,
    encryptedData,
    privateKey);

HKDF Operations

HkdfExtractAsync

byte[] prk = await provider.HkdfExtractAsync(salt, inputKeyMaterial);

HkdfExpandAsync

byte[] okm = await provider.HkdfExpandAsync(prk, info, outputLength: 32);

HkdfDeriveKeyAsync

Extract + Expand in one step.

byte[] key = await provider.HkdfDeriveKeyAsync(
    ikm: sharedSecret,
    length: 32,
    salt: optionalSalt,
    info: contextInfo);

DeriveHybridKeyAsync

Combines classic and PQ secret.

byte[] hybridKey = await provider.DeriveHybridKeyAsync(
    classicSecret: ecdhSecret,
    pqSecret: mlKemSecret,
    outputLength: 32);

TLS 1.3 Key Derivation

DeriveTls13KeysAsync

Tls13KeyMaterial keys = await provider.DeriveTls13KeysAsync(
    sharedSecret,
    clientHello,
    serverHello);
 
// Access derived secrets
var clientKey = keys.ClientHandshakeTrafficSecret;
var serverKey = keys.ServerHandshakeTrafficSecret;

Hybrid Signatures

CreateHybridSignatureAsync

Creates hybrid signature from classic signature and ML-DSA.

// Create classic signature (e.g., ECDSA)
byte[] classicSig = CreateEcdsaSignature(data);
 
// Create hybrid signature
byte[] hybridSig = await provider.CreateHybridSignatureAsync(
    data,
    classicSig,
    mlDsaPrivateKey);

Utility Methods

RandomBytesAsync

Cryptographically secure random numbers via Web Crypto API.

byte[] random = await provider.RandomBytesAsync(32);

Certificate Operations

CreateEphemeralCertificateAsync

byte[] certBytes = await provider.CreateEphemeralCertificateAsync(
    "CN=Browser Certificate",
    TimeSpan.FromHours(1),
    mlDsaPrivateKey);

SignCertificateAsync

byte[] signedCert = await provider.SignCertificateAsync(tbsCertificate, privateKey);

Method Overview

ML-DSA

Method Parameters Return
GenerateMlDsaKeyPairAsync string algorithm Task<(byte[], byte[])>
SignMlDsaAsync byte[] data, byte[] privateKey Task<byte[]>
VerifyMlDsaAsync byte[] data, byte[] signature, byte[] publicKey Task<bool>

ML-KEM

Method Parameters Return
GenerateMlKemKeyPairAsync string algorithm Task<(byte[], byte[])>
EncapsulateAsync byte[] publicKey Task<(byte[], byte[])>
DecapsulateAsync byte[] ciphertext, byte[] privateKey Task<byte[]>

Key Derivation

Method Parameters Return
Pbkdf2Async string password, byte[] salt, int iterations, int outputLength, string hash Task<byte[]>
Pbkdf2WithPqSaltAsync string password, byte[] baseSalt, byte[] pqPublicKey, int iterations, int outputLength Task<byte[]>
Argon2idAsync byte[]/string password, byte[] salt, int outputLength, int iterations, int memoryKiB, int parallelism Task<byte[]>

HKDF

Method Parameters Return
HkdfExtractAsync byte[] salt, byte[] ikm Task<byte[]>
HkdfExpandAsync byte[] prk, byte[] info, int length Task<byte[]>
HkdfDeriveKeyAsync byte[] ikm, int length, byte[]? salt, byte[]? info Task<byte[]>
DeriveHybridKeyAsync byte[] classicSecret, byte[] pqSecret, int outputLength Task<byte[]>

Encryption

Method Parameters Return
EncryptChunkedAsync byte[] plaintext, byte[] key, int chunkSize Task<byte[]>
DecryptChunkedAsync byte[] ciphertext, byte[] key Task<byte[]>
EncryptStreamWithPqKeyAsync byte[] plaintext, byte[] publicKey, int chunkSize Task<(byte[], byte[])>
DecryptStreamWithPqKeyAsync byte[] kemCiphertext, byte[] encryptedData, byte[] privateKey Task<byte[]>

Utility

Method Parameters Return
RandomBytesAsync int length Task<byte[]>
CreateHybridSignatureAsync byte[] data, byte[] classicSig, byte[] pqPrivKey Task<byte[]>
DeriveTls13KeysAsync byte[] sharedSecret, byte[] clientHello, byte[] serverHello Task<Tls13KeyMaterial>

Complete Example

// Blazor Component
@page "/crypto-demo"
@inject ICryptoProvider CryptoProvider
 
<h3>PQ Crypto Demo</h3>
<p>Status: @_status</p>
 
@code {
    private string _status = "Initializing...";
 
    protected override async Task OnInitializedAsync()
    {
        try
        {
            await CryptoProvider.InitializeAsync();
 
            // Key Exchange Demo
            var (alicePublic, alicePrivate) =
                await CryptoProvider.GenerateMlKemKeyPairAsync();
 
            var (sharedSecret, ciphertext) =
                await CryptoProvider.EncapsulateAsync(alicePublic);
 
            var decapsulated =
                await CryptoProvider.DecapsulateAsync(ciphertext, alicePrivate);
 
            bool keysMatch = sharedSecret.SequenceEqual(decapsulated);
 
            // Signature Demo
            var (sigPub, sigPriv) =
                await CryptoProvider.GenerateMlDsaKeyPairAsync();
 
            byte[] message = Encoding.UTF8.GetBytes("Test Message");
            byte[] signature = await CryptoProvider.SignMlDsaAsync(message, sigPriv);
            bool isValid = await CryptoProvider.VerifyMlDsaAsync(message, signature, sigPub);
 
            _status = $"Keys match: {keysMatch}, Signature valid: {isValid}";
        }
        catch (Exception ex)
        {
            _status = $"Error: {ex.Message}";
        }
    }
}

Security Notes

  • Requires .NET 8.0+ with Blazor WebAssembly
  • Browser memory is less secure than server memory
  • Private keys should not be stored long-term in the browser
  • For sensitive operations: prefer server-side processing
  • openssl.wasm and wvds-crypto.js must be correctly loaded
Best Practices for Browser Crypto:
  • Use ephemeral keys for session-based encryption
  • Keep sensitive private keys on server
  • Do not use IndexedDB/localStorage for unencrypted keys
  • Configure CSP headers correctly for WASM

See Also


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

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