How to Encrypt and Decrypt Data

📘

Prerequisite

You must generate a 256-bit elliptic curve keypair (EC-256), also known as NIST P-256, secp256r1, or prime256v1, and share the public key with Blinc.

The private key must be generated and securely stored in PKCS#8 format within your environment. Never share the private key with Blinc.

We recommend using separate keypairs for test and live/production environments.

For a Blinc request that requires encryption, encrypt the plaintext JSON payload before transmission. Base64-encode the encrypted payload envelope and send it in the data field of the request body.

--data '{"data":"<base64 encrypted payload envelope>"}'

Use Base64-encoded X.509 SubjectPublicKeyInfo (SPKI) DER public keys, without PEM header or footer lines. The sender also creates a fresh, single-use P-256 key pair for each encrypted request. This is separate from the long-term key pair above.

// Sample public key
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3G+eSLh2rQ6zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdQpCOOJw==

Encrypt a request to Blinc

Use this flow only when calling an E2EE-enabled Blinc endpoint. Encrypt the request body with Blinc's public key. Do not use the public key you shared with Blinc.

  1. Import Blinc's public key from Base64.
  2. Generate a new P-256 key pair for this request.
  3. Create a shared AES key using HKDF-SHA256 with info blinc-e2ee.
  4. Convert the request body to UTF-8 bytes, create a random 12-byte nonce, and encrypt it with AES-256-GCM.
  5. Export the new public key as 0x04 || X || Y.
  6. Join the new public key, nonce, ciphertext, and tag together, then Base64-encode the result to get the payload envelope.
single-use public key (65 bytes)
nonce (12 bytes)
ciphertext (N bytes)
GCM tag (16 bytes)

Encrypt code examples

Each example takes the plaintext request body and Blinc's Base64 public key, then returns the Base64 envelope to send as the encrypted request body.

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

public static string Envelope(string plaintext, string publicKeyBase64)
{
    // 1. Import public Key
    using var publicKey = ECDiffieHellman.Create();
    publicKey.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKeyBase64.Trim()), out _);

    // 2. Fresh key pair
    using var ephemeral = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);

    // 3. Derive shared AES Key
    byte[] shared = ephemeral.DeriveRawSecretAgreement(publicKey.PublicKey);
    byte[] aesKey = HKDF.DeriveKey(HashAlgorithmName.SHA256, shared, 32,
        salt: Array.Empty<byte>(),
        info: Encoding.UTF8.GetBytes("blinc-e2ee")
    );

    // 4. Encrypt with AES-256-GCM
    byte[] nonce = RandomNumberGenerator.GetBytes(12);
    byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
    byte[] ciphertext = new byte[plaintextBytes.Length];
    byte[] tag = new byte[16];

    using (var aes = new AesGcm(aesKey, tagSizeInBytes: 16))
    {
        aes.Encrypt(nonce, plaintextBytes, ciphertext, tag);
    }

    // 5. Extract Ephemeral public key: 0x04 || X || Y
    ECParameters q = ephemeral.ExportParameters(false);
    byte[] ephemeralPublic = new byte[65];
    ephemeralPublic[0] = 0x04;
    q.Q.X!.CopyTo(ephemeralPublic, 1);
    q.Q.Y!.CopyTo(ephemeralPublic, 33);

    CryptographicOperations.ZeroMemory(shared);
    CryptographicOperations.ZeroMemory(aesKey);

    // 6. Return envelope as Base64String
    byte[] envelope = [.. ephemeralPublic, .. nonce, .. ciphertext, .. tag];
    return Convert.ToBase64String(envelope);
}

Decrypt a request from Blinc

Use this flow when Blinc sends you an encrypted request. Decrypt the request body with the private key that matches the public key you shared with Blinc.

  1. Base64-decode the encrypted envelope.
  2. Read the first 65 bytes as the sender's single-use public key.
  3. Read the next 12 bytes as the nonce.
  4. Read the last 16 bytes as the GCM tag.
  5. Treat the bytes between the nonce and tag as the ciphertext.
  6. Use ECDH and HKDF-SHA256 with info blinc-e2ee to recreate the AES key, then decrypt with AES-256-GCM.

Decrypt code examples

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

public static string DecryptEnvelope(string encryptedDataBase64, string privateKeyBase64)
{
    // 1. Decode the Base64 envelope
    byte[] envelope = Convert.FromBase64String(encryptedDataBase64);

    // 2. Extract ephemeral public key
    byte[] ephemeralPublic = envelope[..65];

    // 3. Extract nonce
    byte[] nonce = envelope[65..77];

    // 4. Extract ciphertext
    byte[] ciphertext = envelope[77..^16];

    // 5. Extract authentication tag
    byte[] tag = envelope[^16..];

    // 6. Derive the AES key and decrypt
    using var privateKey = ECDiffieHellman.Create();
    privateKey.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKeyBase64.Trim()), out _);

    ECParameters point = new()
    {
        Curve = ECCurve.NamedCurves.nistP256,
        Q = new ECPoint
        {
            X = ephemeralPublic[1..33],
            Y = ephemeralPublic[33..65]
        }
    };

    using var ephemeral = ECDiffieHellman.Create(point);
    byte[] shared = privateKey.DeriveRawSecretAgreement(ephemeral.PublicKey);
    byte[] aesKey = HKDF.DeriveKey(HashAlgorithmName.SHA256, shared, 32,
        salt: Array.Empty<byte>(),
        info: Encoding.UTF8.GetBytes("blinc-e2ee")
    );

    byte[] plaintext = new byte[ciphertext.Length];
    using (var aes = new AesGcm(aesKey, tagSizeInBytes: 16))
    {
        aes.Decrypt(nonce, ciphertext, tag, plaintext);
    }
    CryptographicOperations.ZeroMemory(shared);
    CryptographicOperations.ZeroMemory(aesKey);

    return Encoding.UTF8.GetString(plaintext);
}

Did this page help you?