Crypto
Drive the browser's Web Crypto engine from C#: strong randomness, hashing, HMAC, AES and RSA-OAEP encryption, PBKDF2 derivation and RSA-PSS / ECDSA signatures - no JavaScript required.
@inject Bit.Butil.Crypto cryptoMDN reference
RandomUuid returns a cryptographically strong v4 UUID as a Guid. GetRandomValues fills up to 65,536 bytes per call with strong random data - ideal for keys, IVs and salts.
Guid uuid = await crypto.RandomUuid();
byte[] iv = await crypto.GetRandomValues(16);Digest computes a SHA-256, SHA-384 or SHA-512 hash of arbitrary bytes via SubtleCrypto.digest. The algorithm is selected with the CryptoKeyHash enum.
byte[] data = Encoding.UTF8.GetBytes("Bit.Butil");
byte[] hash = await crypto.Digest(CryptoKeyHash.Sha256, data);The CryptoAlgorithm overload covers the common cases in one line: pick AES-GCM (authenticated, recommended), AES-CBC or AES-CTR and pass the key, data and IV. This demo generates a 256-bit key plus a random IV, encrypts your text, then decrypts the cipher back.
byte[] key = await crypto.GenerateAesKey(256);
byte[] iv = await crypto.GetRandomValues(16);
byte[] data = Encoding.UTF8.GetBytes("Secret message");
byte[] cipher = await crypto.Encrypt(CryptoAlgorithm.AesGcm, key, data, iv: iv);
byte[] plain = await crypto.Decrypt(CryptoAlgorithm.AesGcm, key, cipher, iv: iv);For full control, pass a typed parameter object instead of the enum: AesGcmCryptoAlgorithmParams (Iv, AdditionalData, TagLength), AesCbcCryptoAlgorithmParams (Iv), AesCtrCryptoAlgorithmParams (Counter, Length) or RsaOaepCryptoAlgorithmParams (Label). The live demo generates an RSA-OAEP key pair, encrypts with the public key and decrypts with the private key.
// AES-GCM with additional authenticated data and explicit tag length
byte[] cipher = await crypto.Encrypt(new AesGcmCryptoAlgorithmParams
{
Iv = iv,
AdditionalData = aad,
TagLength = AesGcmTagLength.Sixteen,
}, key, data);
// RSA-OAEP: encrypt with the SPKI public key, decrypt with the PKCS8 private key
RsaKeyPair pair = await crypto.GenerateRsaKeyPair(2048, CryptoKeyHash.Sha256);
byte[] rsaCipher = await crypto.Encrypt(new RsaOaepCryptoAlgorithmParams(), pair.PublicKey, data);
byte[] rsaPlain = await crypto.Decrypt(new RsaOaepCryptoAlgorithmParams(), pair.PrivateKey, rsaCipher);SignHmac produces a keyed authentication tag over your data; VerifyHmac checks a tag in constant time inside the browser engine. Keys can come from GenerateHmacKey or any shared secret.
byte[] key = await crypto.GenerateHmacKey(CryptoKeyHash.Sha256);
byte[] data = Encoding.UTF8.GetBytes("message");
byte[] tag = await crypto.SignHmac(CryptoKeyHash.Sha256, key, data);
bool valid = await crypto.VerifyHmac(CryptoKeyHash.Sha256, key, tag, data);Generate symmetric keys as raw bytes, or RSA / ECDSA pairs as SPKI (public) and PKCS8 (private) DER bytes ready for Encrypt, SignRsaPss and SignEcdsa - or for export to a server.
byte[] aesKey = await crypto.GenerateAesKey(256);
byte[] hmacKey = await crypto.GenerateHmacKey(CryptoKeyHash.Sha512);
RsaKeyPair rsa = await crypto.GenerateRsaKeyPair(2048, CryptoKeyHash.Sha256);
EcKeyPair ec = await crypto.GenerateEcdsaKeyPair("P-256");DerivePbkdf2 stretches a password into key material using PBKDF2 with a salt, an iteration count and the requested output size in bits. Use a random per-user salt and a six-figure iteration count.
byte[] salt = await crypto.GetRandomValues(16);
byte[] derived = await crypto.DerivePbkdf2(
Encoding.UTF8.GetBytes(password),
salt,
iterations: 100_000,
outputLengthBits: 256);Sign with the PKCS8 private key, verify with the SPKI public key. RSA-PSS takes a salt length; ECDSA takes the named curve (P-256, P-384 or P-521). Both default the hash to SHA-256.
byte[] data = Encoding.UTF8.GetBytes("signed payload");
RsaKeyPair rsa = await crypto.GenerateRsaKeyPair();
byte[] pssSig = await crypto.SignRsaPss(rsa.PrivateKey, data);
bool pssOk = await crypto.VerifyRsaPss(rsa.PublicKey, pssSig, data);
EcKeyPair ec = await crypto.GenerateEcdsaKeyPair("P-256");
byte[] ecSig = await crypto.SignEcdsa(ec.PrivateKey, data, "P-256");
bool ecOk = await crypto.VerifyEcdsa(ec.PublicKey, ecSig, data, "P-256");Re-expresses key material between the four WebCrypto formats, which is how a key that came from a server becomes bytes the methods above accept. CryptoKeyAlgorithm says what the key is - the browser rejects a key imported under the wrong algorithm even when its bytes are right. The demo generates an AES key, publishes it as a JWK, and imports the JWK back to raw bytes.
byte[] key = await crypto.GenerateAesKey(256);
// raw bytes → the JWK a server publishes
CryptoJsonWebKey? jwk = await crypto.ExportJsonWebKey(key, CryptoKeyFormat.Raw, CryptoKeyAlgorithm.AesGcm(256));
// and back again
byte[] roundTripped = await crypto.ImportJsonWebKey(jwk, CryptoKeyAlgorithm.AesGcm(256), CryptoKeyFormat.Raw);
// the same conversion between two byte formats - here a public key from SPKI to JWK's cousin
RsaKeyPair rsa = await crypto.GenerateRsaKeyPair();
CryptoJsonWebKey? publicJwk = await crypto.ExportJsonWebKey(rsa.PublicKey, CryptoKeyFormat.Spki, CryptoKeyAlgorithm.RsaOaep());Two parties each generate a key pair, exchange public keys, and arrive at the same secret without ever sending it. DeriveEcdhBits gives the raw agreement - the x coordinate of a point, not a uniform key - while DeriveEcdhKey runs it through to a usable AES key in one step. The demo plays both sides and checks that they agree.
EcKeyPair alice = await crypto.GenerateEcdhKeyPair("P-256");
EcKeyPair bob = await crypto.GenerateEcdhKeyPair("P-256");
// each side derives from its own private key and the other's public key
byte[] aliceKey = await crypto.DeriveEcdhKey(alice.PrivateKey, bob.PublicKey, CryptoKeyAlgorithm.AesGcm(256));
byte[] bobKey = await crypto.DeriveEcdhKey(bob.PrivateKey, alice.PublicKey, CryptoKeyAlgorithm.AesGcm(256));
// aliceKey and bobKey are the same 32 bytes
// or take the raw agreement and derive from it yourself
byte[] shared = await crypto.DeriveEcdhBits(alice.PrivateKey, bob.PublicKey, 256);HKDF turns one high-entropy secret into as many purpose-bound keys as you need - the info parameter is what keeps the encryption key and the signing key different. PBKDF2 is the one for passwords: it stretches, HKDF does not. Both have a Key form that hands back a key of a stated algorithm rather than loose bits.
byte[] secret = await crypto.GetRandomValues(32);
byte[] salt = await crypto.GetRandomValues(16);
// two keys from one secret, kept apart by their info string
byte[] encryptionKey = await crypto.DeriveHkdfKey(secret, salt, Encoding.UTF8.GetBytes("encryption"), CryptoKeyAlgorithm.AesGcm(256));
byte[] signingKey = await crypto.DeriveHkdfKey(secret, salt, Encoding.UTF8.GetBytes("signing"), CryptoKeyAlgorithm.Hmac());
// passwords go through PBKDF2, never HKDF
byte[] fromPassword = await crypto.DerivePbkdf2Key(
Encoding.UTF8.GetBytes(password), salt, iterations: 100_000, CryptoKeyAlgorithm.AesGcm(256));Encrypts one key with another, so key material can be stored or sent without ever being in the clear. AES-KW exists only for this and needs no IV; AES-GCM works too and takes one. The demo wraps a freshly generated AES key, then unwraps it and checks the bytes came back identical.
byte[] keyToProtect = await crypto.GenerateAesKey(256);
byte[] wrappingKey = await crypto.GenerateAesKwKey(256);
byte[] wrapped = await crypto.WrapKey(
keyToProtect, CryptoKeyFormat.Raw, CryptoKeyAlgorithm.AesGcm(256),
wrappingKey, new AesKwCryptoAlgorithmParams());
// wrapped is safe to store; only the wrapping key opens it
byte[] recovered = await crypto.UnwrapKey(
wrapped, CryptoKeyFormat.Raw, CryptoKeyAlgorithm.AesGcm(256),
wrappingKey, new AesKwCryptoAlgorithmParams());API reference
ValueTask<Guid> RandomUuid()ValueTask<byte[]> GetRandomValues(int length)ValueTask<byte[]> Digest(CryptoKeyHash algorithm, byte[] data)ValueTask<byte[]> SignHmac(CryptoKeyHash algorithm, byte[] key, byte[] data)ValueTask<bool> VerifyHmac(CryptoKeyHash algorithm, byte[] key, byte[] signature, byte[] data)ValueTask<byte[]> GenerateAesKey(int bits = 256)ValueTask<byte[]> GenerateHmacKey(CryptoKeyHash algorithm = CryptoKeyHash.Sha256, int? lengthBits = null)ValueTask<RsaKeyPair> GenerateRsaKeyPair(int modulusLengthBits = 2048, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<EcKeyPair> GenerateEcdsaKeyPair(string curve = "P-256")ValueTask<byte[]> DerivePbkdf2(byte[] password, byte[] salt, int iterations, int outputLengthBits, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<byte[]> SignRsaPss(byte[] privateKey, byte[] data, int saltLength = 32, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<bool> VerifyRsaPss(byte[] publicKey, byte[] signature, byte[] data, int saltLength = 32, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<byte[]> SignEcdsa(byte[] privateKey, byte[] data, string curve = "P-256", CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<bool> VerifyEcdsa(byte[] publicKey, byte[] signature, byte[] data, string curve = "P-256", CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<byte[]> Encrypt<T>(T algorithm, byte[] key, byte[] data, CryptoKeyHash? keyHash = null) where T : ICryptoAlgorithmParamsValueTask<byte[]> Encrypt(CryptoAlgorithm algorithm, byte[] key, byte[] data, byte[]? iv = null, CryptoKeyHash? keyHash = null)ValueTask<byte[]> Decrypt<T>(T algorithm, byte[] key, byte[] data, CryptoKeyHash? keyHash = null) where T : ICryptoAlgorithmParamsValueTask<byte[]> Decrypt(CryptoAlgorithm algorithm, byte[] key, byte[] data, byte[]? iv = null, CryptoKeyHash? keyHash = null)ValueTask<byte[]> ExportKey(byte[] key, CryptoKeyFormat sourceFormat, CryptoKeyFormat targetFormat, CryptoKeyAlgorithm algorithm)ValueTask<CryptoJsonWebKey?> ExportJsonWebKey(byte[] key, CryptoKeyFormat sourceFormat, CryptoKeyAlgorithm algorithm)ValueTask<byte[]> ImportJsonWebKey(CryptoJsonWebKey jwk, CryptoKeyAlgorithm algorithm, CryptoKeyFormat targetFormat = CryptoKeyFormat.Raw)ValueTask<EcKeyPair> GenerateEcdhKeyPair(string curve = "P-256")ValueTask<byte[]> DeriveEcdhBits(byte[] privateKey, byte[] publicKey, int outputLengthBits, string curve = "P-256")ValueTask<byte[]> DeriveEcdhKey(byte[] privateKey, byte[] publicKey, CryptoKeyAlgorithm derivedKeyAlgorithm, string curve = "P-256")ValueTask<byte[]> DeriveHkdfBits(byte[] keyMaterial, byte[]? salt, byte[]? info, int outputLengthBits, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<byte[]> DeriveHkdfKey(byte[] keyMaterial, byte[]? salt, byte[]? info, CryptoKeyAlgorithm derivedKeyAlgorithm, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<byte[]> DerivePbkdf2Key(byte[] password, byte[] salt, int iterations, CryptoKeyAlgorithm derivedKeyAlgorithm, CryptoKeyHash algorithm = CryptoKeyHash.Sha256)ValueTask<byte[]> GenerateAesKwKey(int bits = 256)ValueTask<byte[]> WrapKey<T>(byte[] key, CryptoKeyFormat format, CryptoKeyAlgorithm keyAlgorithm, byte[] wrappingKey, T wrapAlgorithm, CryptoKeyHash? wrappingKeyHash = null) where T : ICryptoAlgorithmParamsValueTask<byte[]> UnwrapKey<T>(byte[] wrappedKey, CryptoKeyFormat format, CryptoKeyAlgorithm unwrappedKeyAlgorithm, byte[] unwrappingKey, T unwrapAlgorithm, CryptoKeyHash? unwrappingKeyHash = null) where T : ICryptoAlgorithmParams