Scaling an IoT deployment? Join our webinar on May 28th where we dive into real-world scaling pain points and how to overcome them.

Blues Developers
What’s New
Resources
Blog
Technical articles for developers
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Developer Certification
Get certified on wireless connectivity with Blues
Webinars
Listing of Blues technical webinars
Blues.comNotehub.io
Shop
Docs
Button IconHelp
Notehub StatusVisit our Forum
Button IconSign In
Sign In
Sign In
Docs Home
What’s New
Resources
Blog
Technical articles for developers
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Developer Certification
Get certified on wireless connectivity with Blues
Webinars
Listing of Blues technical webinars
Blues.comNotehub.io
Shop
Docs
Guides & Tutorials
Collecting Sensor Data
Routing Data to Cloud
Building Edge ML Applications
Best Practices for Production-Ready Projects
Twilio SMS Guide
Fleet Admin Guide
Using the Notehub API
Notecard Guides
Guide Listing
Asset Tracking
Attention Pin Guide
Connecting to a Wi-Fi Access Point
Debugging with the FTDI Debug Cable
Diagnosing Cellular Connectivity Issues
Diagnosing GPS Issues
Encrypting and Decrypting Data with the Notecard
Device to Cloud OverviewDevice to Cloud WalkthroughCloud to Device OverviewCloud to Device Walkthrough
Feather MCU Low Power Management
Minimizing Latency
Notecard Communication Without a Library
Recovering a Bricked Notecard
Remote Command and Control
Sending and Receiving Large Binary Objects
Serial-Over-I2C Protocol
Understanding Environment Variables
Understanding Notecard Penalty Boxes
Updating ESP32 Host Firmware
Using External SIM Cards
Using JSONata to Transform JSON
homechevron_rightDocschevron_rightGuides & Tutorialschevron_rightNotecard Guideschevron_rightEncrypting and Decrypting Data with the Notecard

Encrypting and Decrypting Data with the Notecard

The Notecard was built with security in mind. With a factory-installed ECC P-384 certificate provisioned at chip manufacture and "off-the-internet" communication between the Notecard Cellular and our Notehub cloud service, you can be sure that your data is safe during transport. On Notecard LoRa the transport is encrypted using standard LoRa transport encryption . Likewise, data sent and received with Notecard WiFi is is always transferred over TLS.

note

If you want to ensure that all data between the Notecard and Notehub is always transferred over a TLS connection, use a Notefile name that ends in "s", such as .qos, .qis, or .dbs.

For certain types of applications, however, you may wish to add an additional layer of security between your host application and your cloud app by encrypting data at rest. To enable this extra layer of security, the Notecard supports end-to-end encryption of your data.

Using an AES-256 symmetric algorithm based on a public key you specify, the Notecard can both encrypt outbound Notes added by your host and decrypt inbound Notes encrypted by your cloud application.

warning

Encrypting and decrypting Notefiles (as demonstrated in this guide) is not supported on the Notecard LoRa.

Table of Contents

  1. Device to Cloud Encryption/Decryption Overview
  2. Device to Cloud Walkthrough
  3. Cloud to Device Encryption/Decryption Overview
  4. Cloud to Device Walkthrough

Device to Cloud Overview

At a high-level, you'll need to take the following steps when sending encrypted data from the Notecard to your cloud application:

  1. Generate an ECC key pair using the secp384r1 curve.
  2. Add the contents of your public key to an environment variable in your Notehub project. We recommend adding your key to a project-level variable to ensure it can be used by all of the devices in your project.
  3. When adding Notes to the Notecard, use the key argument with each note.add request and provide the name of the environment variable added to Notehub in the previous step.
  4. Create a Notehub Route to send your encrypted data to your cloud application.
  5. Upon receipt of your encrypted data on your cloud application, use your private key to decrypt it.

The next section provides step-by-step instructions for using the encryption capabilities of the Notecard, along with examples of decrypting the data using OpenSSL, Node.js, and .NET.

Device to Cloud Walkthrough

Create an ECC Key Pair

First, create an ECC public and private key pair using OpenSSL :

openssl ecparam -genkey -name secp384r1 -noout -out privateKey.pem
openssl ec -in privateKey.pem -pubout -out publicKey.pem

These commands will output two files with .pem extensions: one with your private key and one with the public key.

Add Your Public Key to Notehub as an Environment Variable

Once you have a key pair, you'll need to add the public key to your Notehub project so the Notecard can encrypt Note bodies on your behalf.

Navigate to your Notehub project and click the "Environment" menu item in the left navigation.

Image of the Notehub Left nav, with the environment menu item highlighted

Next, create a new environment variable with any name you wish. You can create the environment variable at the device, fleet, or project level. You'll use this name to tell the Notecard which key to use to encrypt your data.

Copy the complete contents of your public key file into the value field for your environment variable, making sure to prepend the value with secp384r1-aes256cbc:.

For example:

secp384r1-aes256cbc:-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECWghuYIN91/QPZfewFRkTiLKx8cd4jiD
gTiHnM1i97DkYmesEp3CMSO2HVREfVwtRFnQCg1ZvpUGQNrQoJ85N/8uZbbMSVza
AtlirKYB4l3gzQ7YB27oFhudt67cqMIb
-----END PUBLIC KEY-----

Image of the public key environment variable

Add Notes with the key Argument

After the public key environment variable is set, all that's left is to include the key argument when sending note.add requests. Use the name you specified for the environment variable in Notehub and the Notecard will encrypt the Note body before it saves it to a Notefile.

warning

If you try to encrypt data before the Notecard has synced with Notehub (to download the environment variable you just created), you may see this error when issuing the note.add request:

{
 "err": "encryption key environment var not found: <key name>"
}

To avoid this, make sure you complete at least one sync with Notehub after creating the environment variable.

{
  "req": "note.add",
  "file": "sensors.qo",
  "key": "encryption_key",
  "body":
    {
      "temp": 72.22,
      "humidity": 21.3,
      "pressure": 1.002,
      "sound_level": 68.9,
      "heart_rate": 65
    }
}
J *req = notecard.newRequest("note.add");
JAddStringToObject(req, "file", "sensors.qo");
JAddStringToObject(req, "key", "encryption_key");

J *body = JCreateObject();
JAddNumberToObject(body, "temp", bmp280.temperature);
JAddNumberToObject(body, "humidity", sht31d.relative_humidity);
JAddNumberToObject(body, "pressure", bmp280.pressure);
JAddNumberToObject(body, "sound_level", sound_level);
JAddNumberToObject(body, "heart_rate", heart_rate);
JAddItemToObject(req, "body", body);

notecard.sendRequest(req);
req = {"req": "note.add"}
req["file"] = "sensors.qo"
req["key"] = "encryption_key"
req["body"] = {
  "temp": bmp280.temperature,
  "humidity": sht31d.relative_humidity,
  "pressure": bmp280.pressure,
  "sound_level": sound_level,
  "heart_rate": heart_rate,
}

card.Transaction(req)
note

How exactly does the Notecard encrypt my data?

It's important to understand the exact process the Notecard uses to encrypt your Note body, because you'll need to do the reverse to decrypt it once routed to your cloud service from Notehub.

  1. The Notecard generates a random 64 bit AES encryption key (this key rotates every time the Notecard encrypts data).
  2. The clear text Note body you specified in the note.add request is encrypted with the AES key and base64-encoded for transport.
  3. The random AES key is then encrypted with your public key and base64-encoded for transport.
  4. Once encryption is complete, the original Note body is replaced with a new body that contains the original encrypted body and the encrypted AES key.

Route Your Data

After the next Notecard sync with Notehub, your Notes will appear with encrypted data.

A view of encrypted events on the Notehub events view

{
  "event": "6e94880b-34f9-83cd-ac38-b86b5f86af2b",
  "when": 1729196079,
  "file": "mysecrets.qo",
  "body": {
      "alg": "secp384r1-aes256cbc",
      "data": "9MY2ZXrGJ5bOAFBr2nPfnEyrY2wsYRKTRk82BvH3stE=",
      "env": "public_key",
      "key": "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7y24fwyRduMX6oppm2PQhFeKWbuYuBUwGffYLsbfeMQsxegY79bKL7Lw1wwlRAlIrRsxnx1FxRl8YbSpXl4p6cawb6sTztooYEGbpejTyPQ0L5uYWvB0JRFyoWpvMbAd"
}

From Notehub, you can route your encrypted data to your cloud app of choice. Don't forget to include both the data and key fields in any JSONata transformations you might make prior to routing, as you'll need both to properly decrypt the data.

Decrypt Your Data on Your Cloud Service

After you've safely received the encrypted data and key in the event routed from Notehub, you can decrypt it with the following steps:

  1. Decode the base64 encrypted AES key and decrypt it using your private key.
  2. Decode the base64 encrypted data and decrypt it with the decrypted AES key from the previous step.

Use the code samples below as the basis for your cloud application to decrypt the data using OpenSSL, Node.js, .NET, or Python.

# The "ciphertext_key.b64" file below is the ephemeral public `key` argument sent in the event from Notehub.
# The "ciphertext_data.b64" file below is the `data` argument sent in the event in notehub

# Decode the ephemeral public key and convert it to a PEM file.
openssl base64 -A -d -in ciphertext_key.b64 -out ciphertext_dec_key.der

# Use your private key to derive the shared secret from the ephemeral key.
openssl pkeyutl -derive -inkey privateKey.pem -peerform DER -peerkey ciphertext_dec_key.der -out dec_secret_key.bin

# Use a hash function to convert the derived key to a 256-bit AES key.
openssl sha256 -binary dec_secret_key.bin > dec_key.bin

# Decrypt the input file.
openssl base64 -A -d -in ciphertext_data.b64 -out ciphertext_dec_data.bin

# Write the decrypted output to "cleartext_data.bin".
KEYHEX=$(xxd -c 256 -p dec_key.bin) && openssl enc -d -aes-256-cbc -iv 0 -K $KEYHEX -in ciphertext_dec_data.bin -out cleartext_data.bin
const fs = require('fs');
const crypto = require('crypto');

// Step 1: Set the ephemeral public key (base64-encoded string) to the value of the "key" argument that came in from the Notehub event.
const ciphertextKeyBase64 = '[your-public-key-from-notehub-event]';
const ephemeralPublicKeyDer = Buffer.from(ciphertextKeyBase64, 'base64');

// Step 2: Create a KeyObject from the ephemeral public key
const ephemeralPublicKey = crypto.createPublicKey({
  key: ephemeralPublicKeyDer,
  format: 'der',
  type: 'spki', // 'spki' is used for public keys in DER format
});

// Step 3: Read your private key from the file system
const privateKeyPem = fs.readFileSync('privateKey.pem', 'utf8');
const privateKey = crypto.createPrivateKey({
  key: privateKeyPem,
  format: 'pem',
  type: 'sec1', // 'sec1' is used for EC private keys in PEM format
});

// Step 4: Derive the shared secret using ECDH
const sharedSecret = crypto.diffieHellman({
  privateKey: privateKey,
  publicKey: ephemeralPublicKey,
});

// Step 5: Hash the shared secret to obtain the AES-256 key
const aesKey = crypto.createHash('sha256').update(sharedSecret).digest();

// Step 6: Set the ciphertext data (base64-encoded string) to the value of the "data" argument that came in from the Notehub event.
const ciphertextDataBase64 = '[your-data-from-notehub-event]';
const ciphertextData = Buffer.from(ciphertextDataBase64, 'base64');

// Step 7: Decrypt the data using AES-256-CBC
const iv = Buffer.alloc(16, 0);
const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, iv);
let decryptedData = decipher.update(ciphertextData);
decryptedData = Buffer.concat([decryptedData, decipher.final()]);

// Step 8: Output the decrypted data to the console
console.log('Decrypted data:', decryptedData.toString());
// NOTE: this method requires using BouncyCastle
// dotnet add package BouncyCastle.Cryptography

using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

class Program
{
    static void Main(string[] args)
    {
        string ciphertextDataBase64 = "[your-data-from-notehub-event]";
        string ciphertextKeyBase64 = "[your-public-key-from-notehub-event]";

        // Step 1: Set the ephemeral public key (base64-encoded string)
        byte[] ephemeralPublicKeyDer = Convert.FromBase64String(ciphertextKeyBase64);

        // Step 2: Create a public key object from the DER bytes using BouncyCastle
        AsymmetricKeyParameter publicKeyParam = PublicKeyFactory.CreateKey(ephemeralPublicKeyDer);
        ECPublicKeyParameters ephPublicKey = (ECPublicKeyParameters)publicKeyParam;

        // Step 3: Read our private key using BouncyCastle
        string privateKeyPem = File.ReadAllText("privateKey.pem");
        AsymmetricCipherKeyPair keyPair;
        using (var reader = new StringReader(privateKeyPem))
        {
            PemReader pemReader = new PemReader(reader);
            keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
        }
        ECPrivateKeyParameters privateKeyParam = (ECPrivateKeyParameters)keyPair.Private;

        // Step 4: Derive the shared secret using ECDH
        IBasicAgreement agreement = new ECDHBasicAgreement();
        agreement.Init(privateKeyParam);
        var sharedSecretBigInt = agreement.CalculateAgreement(ephPublicKey);
        byte[] sharedSecret = sharedSecretBigInt.ToByteArrayUnsigned();

        // Step 5: Hash the shared secret to obtain the AES-256 key
        byte[] aesKey;
        using (var sha256 = SHA256.Create())
        {
            aesKey = sha256.ComputeHash(sharedSecret);
        }

        // Step 6: Set the ciphertext data (base64-encoded string)
        byte[] ciphertextData = Convert.FromBase64String(ciphertextDataBase64);

        // Step 7: Decrypt the data using AES-256-CBC
        byte[] iv = new byte[16];

        byte[] decryptedData;
        using (var aes = Aes.Create())
        {
            aes.KeySize = 256;
            aes.Key = aesKey;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
            {
                using (var msDecrypt = new MemoryStream(ciphertextData))
                {
                    using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (var msPlain = new MemoryStream())
                        {
                            csDecrypt.CopyTo(msPlain);
                            decryptedData = msPlain.ToArray();
                        }
                    }
                }
            }
        }

        // Step 8: Output the decrypted data to the console
        Console.WriteLine("Decrypted data: " + Encoding.UTF8.GetString(decryptedData));
    }
}
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import base64

def decrypt_data(private_key_path: str, payload: dict) -> bytes:
    """
    Decrypts data that was encrypted using ECDH key exchange and AES-256-CBC encryption.

    Args:
        private_key_path (str): Path to the private key PEM file used for ECDH
        payload (dict): Dictionary containing the encrypted data from Notehub event:
            - key: Base64 encoded ephemeral public key
            - data: Base64 encoded encrypted data

    Returns:
        bytes: The decrypted data with padding removed

    Raises:
        Various cryptography exceptions if decryption fails
    
    Note:
        This function is intended to be used with a pre-generated key pair.
    """
    # 0. Check if payload is valid
    if not payload or not isinstance(payload, dict):
        raise ValueError("Invalid payload")
    
    # 1. Check if algorithm is supported
    if payload['alg'] != 'secp384r1-aes256cbc':
        raise ValueError("Unsupported algorithm")
    
    # 3. Decode the base64 ephemeral public key
    public_key_bytes = base64.b64decode(payload['key'])
    peer_public_key = serialization.load_der_public_key(public_key_bytes)

    # 4. Load private key and derive shared secret
    with open(private_key_path, 'rb') as f:
        private_key = serialization.load_pem_private_key(
            f.read(),
            password=None
        )
    shared_secret = private_key.exchange(ec.ECDH(), peer_public_key)
    print(f"Shared secret: {shared_secret.hex()}")

    # 5. Hash the shared secret to get AES key
    digest = hashes.Hash(hashes.SHA256())
    digest.update(shared_secret)
    aes_key = digest.finalize()
    print(f"AES key: {aes_key.hex()}")

    # 6. Decode the encrypted data
    encrypted_data = base64.b64decode(payload['data'])
    print(f"Encrypted data: {encrypted_data.hex()}")

    # 7. Decrypt with zero IV
    cipher = Cipher(algorithms.AES256(aes_key), modes.CBC(b'\x00' * 16))
    decryptor = cipher.decryptor()
    decrypted = decryptor.update(encrypted_data) + decryptor.finalize()
    
    # 8. Remove padding if present
    try:
        padding_length = decrypted[-1]
        if padding_length <= 16:
            decrypted = decrypted[:-padding_length]
    except:
        pass

    return decrypted

Cloud to Device Overview

At a high-level, you'll need to take the following steps to send encrypted data from your cloud application to a Notecard:

  1. Generate an ECC key pair using the secp384r1 curve.
  2. Acquire your Notecard's static public key using the Notehub API.
  3. Encrypt the Note's body for transport in your cloud application.
  4. Create an inbound Notefile (.qi) using the Notehub API.
  5. Sync the Notefile with your Notecard and decrypt the data.

The next section provides step-by-step instructions for encrypting data on your cloud application (using OpenSSL, Node.js, or .NET) and decrypting the data on the Notecard.

Cloud to Device Walkthrough

Get the Notecard's Static Public Key

You'll need to acquire the public key from each Notecard device that will receive encrypted Notefiles using the Get Device Public Key API (for a single device) or Get Public Keys API (for all devices in a project). These keys are static and do not change over time.

Consult the Notehub API documentation for examples of how to use the Get Device Public Key API or Get Public Keys API.

For example, the Get Public Keys API response will include an array of device_public_keys. You will need to identify the device(s) to which you will be sending encrypted data using by comparing unique DeviceUIDs with the uid returned from this request.

Here is an example of a single element from an array of device_public_keys:

{"uid":"dev:868050012345678","key":"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYyyKpzaG9irLbqjcV1tz4x+YJcZDVKzcO\nr+kt5r5lEQ7HpzrxXQuWUT3Kz7HnGFphySqjATV4Y/HUti6jXPHO\niqaKWixrxQgz9biD\n-----END PUBLIC KEY-----\n"}

Save the key from this JSON response to a file called device_public_key.pem.

note

Make sure the \n new line characters are actually new lines in the .pem file you create.

Encrypt the Data in Your Cloud Application

After you've gathered the public key(s) from your Notehub device(s), you can next encrypt an arbitrary JSON body in your cloud application.

Use the code samples below as the basis for your cloud service to encrypt data using OpenSSL, Node.js, .NET, or Python.

# Generate a temporary private key, and then a public key from the private key as an ASN.1 DER-formatted key.
openssl ecparam -genkey -name secp384r1 -noout -out temp-private.pem
openssl ec -in temp-private.pem -pubout -outform DER -out temp-public.der

# Use the device's public key to derive what will be a shared secret.
openssl pkeyutl -derive -inkey temp-private.pem -peerkey device_public_key.pem -out enc_secret_key.bin

# Use a hash function to convert the derived key to a 256-bit AES key.
openssl sha256 -binary enc_secret_key.bin > enc_key.bin

# Encrypt the input binary using the first 256 bits of the 48-byte shared secret as an AES key.
# NOTE: The "cleartext_data.bin" file represents the JSON body you want to encrypt.
KEYHEX=$(xxd -c 256 -p enc_key.bin) && openssl enc -aes-256-cbc -iv 0 -K $KEYHEX -in cleartext_data.bin -out ciphertext_enc_data.bin

# Convert the encrypted binary file to base64 for transport.
openssl base64 -A -in ciphertext_enc_data.bin -out ciphertext_data.b64

# Convert the temporary public ASN.1 DER-formatted key to base64 for transport.
openssl base64 -A -in temp-public.der -out ciphertext_key.b64

# Create a Note body to be sent to the device
echo '{"alg":"secp384r1-aes256cbc","data":"'$(<ciphertext_data.b64)'","key":"'$(<ciphertext_key.b64)'"}' >body.json

# The result from the previous command will look something like this:
# {"alg":"secp384r1-aes256cbc","data":"cQAFX5QrzwDnmdPRZsmxRoZLkQZbSU/3BnE=","key":"MHYwEAYHKoZIzj0CAQYFgAEYkwodOA8VY4L+NbGtWSROVd8Sjo75D9x7dmHm++73q20NkgpqVXiPhDUy/kIBFRTAYxFTCZolhqI5nu5Rq8LP30i6R"}
const fs = require('fs');
const crypto = require('crypto');

// Generate a temporary EC key pair on the 'secp384r1' curve
const { publicKey: tempPublicKey, privateKey: tempPrivateKey } = crypto.generateKeyPairSync('ec', {
  namedCurve: 'secp384r1',
  publicKeyEncoding: { type: 'spki', format: 'der' }, // DER format for public key
  privateKeyEncoding: { type: 'sec1', format: 'pem' }  // PEM format for private key
});

// Read the device's public key from the filesystem
const devicePublicKeyPem = fs.readFileSync('device_public_key.pem', 'utf8');

// Extract the private key scalar for ECDH
const tempPrivateKeyObject = crypto.createPrivateKey(tempPrivateKey);
const tempPrivateKeyJWK = tempPrivateKeyObject.export({ format: 'jwk' });
const privateKeyScalar = Buffer.from(tempPrivateKeyJWK.d, 'base64url');

// Create an ECDH object and set the private key scalar
const ecdh = crypto.createECDH('secp384r1');
ecdh.setPrivateKey(privateKeyScalar);

// Import the device's public key and extract x and y coordinates
const devicePublicKeyObject = crypto.createPublicKey(devicePublicKeyPem);
const devicePublicKeyJWK = devicePublicKeyObject.export({ format: 'jwk' });
const xBuffer = Buffer.from(devicePublicKeyJWK.x, 'base64url');
const yBuffer = Buffer.from(devicePublicKeyJWK.y, 'base64url');

// Construct the uncompressed device public key
const uncompressedDevicePublicKey = Buffer.concat([Buffer.from([0x04]), xBuffer, yBuffer]);

// Compute the shared secret
const sharedSecret = ecdh.computeSecret(uncompressedDevicePublicKey);

// Derive the AES-256 key by hashing the shared secret
const aesKey = crypto.createHash('sha256').update(sharedSecret).digest();

// Specify the cleartext data to be encrypted for the Notefile
const cleartextData = Buffer.from('{"hello":"world"}', 'utf8');

// Encrypt the cleartext data using AES-256-CBC
const iv = Buffer.alloc(16, 0);
const cipher = crypto.createCipheriv('aes-256-cbc', aesKey, iv);
const encryptedData = Buffer.concat([cipher.update(cleartextData), cipher.final()]);

// Convert the encrypted data to base64
const encryptedDataBase64 = encryptedData.toString('base64');

// Convert the temporary public key to base64
const tempPublicKeyBase64 = tempPublicKey.toString('base64');

// Create the JSON body to be sent to the device
const body = {
  alg: 'secp384r1-aes256cbc',
  data: encryptedDataBase64,
  key: tempPublicKeyBase64
};

const bodyJson = JSON.stringify(body);

console.log(bodyJson);
// NOTE: this method requires using BouncyCastle
// dotnet add package BouncyCastle.Cryptography

using System.Text;
using System.Security.Cryptography;
using System.Text.Json;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X509;

class Program
{
    static void Main(string[] args)
    {
        // Generate a temporary EC key pair on the 'secp384r1' curve
        X9ECParameters ecParams = ECNamedCurveTable.GetByName("secp384r1");
        ECDomainParameters domainParams = new ECDomainParameters(ecParams);

        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        SecureRandom random = new SecureRandom();
        ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(domainParams, random);
        keyGen.Init(keyGenParams);

        AsymmetricCipherKeyPair tempKeyPair = keyGen.GenerateKeyPair();
        ECPrivateKeyParameters tempPrivateKeyParams = (ECPrivateKeyParameters)tempKeyPair.Private;
        ECPublicKeyParameters tempPublicKeyParams = (ECPublicKeyParameters)tempKeyPair.Public;

        // Read the device's public key from the filesystem
        string devicePublicKeyPem = File.ReadAllText("device_public_key.pem");

        // Cleartext data to be encrypted for the Notefile
        byte[] cleartextData = Encoding.UTF8.GetBytes("{\"hello\":\"world\"}");

        // Parse the device's public key
        AsymmetricKeyParameter devicePublicKey;
        using (var reader = new StringReader(devicePublicKeyPem))
        {
            PemReader pemReader = new PemReader(reader);
            devicePublicKey = (AsymmetricKeyParameter)pemReader.ReadObject();
        }

        // Perform ECDH to compute the shared secret
        IBasicAgreement ecdhAgree = new ECDHBasicAgreement();
        ecdhAgree.Init(tempPrivateKeyParams);

        ECPublicKeyParameters devicePubKeyParams = (ECPublicKeyParameters)devicePublicKey;

        var sharedSecretBigInt = ecdhAgree.CalculateAgreement(devicePubKeyParams);

        // Convert shared secret to byte array
        byte[] sharedSecret = sharedSecretBigInt.ToByteArrayUnsigned();

        // Derive the AES-256 key by hashing the shared secret
        byte[] aesKey;
        using (SHA256 sha256 = SHA256.Create())
        {
            aesKey = sha256.ComputeHash(sharedSecret);
        }

        // Encrypt the cleartext data using AES-256-CBC
        byte[] iv = new byte[16];

        byte[] encryptedData;
        using (var aes = Aes.Create())
        {
            aes.KeySize = 256;
            aes.Key = aesKey;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            aes.IV = iv;

            using (var encryptor = aes.CreateEncryptor())
            {
                encryptedData = encryptor.TransformFinalBlock(cleartextData, 0, cleartextData.Length);
            }
        }

        // Convert the encrypted data to base64
        string encryptedDataBase64 = Convert.ToBase64String(encryptedData);

        // Convert the temporary public key to DER format and then to base64
        byte[] tempPublicKeyDer;
        {
            // Create SubjectPublicKeyInfo with the correct algorithm identifier
            AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.IdECPublicKey, SecObjectIdentifiers.SecP384r1);
            SubjectPublicKeyInfo tempPublicKeyInfo = new SubjectPublicKeyInfo(algId, tempPublicKeyParams.Q.GetEncoded(false));
            tempPublicKeyDer = tempPublicKeyInfo.GetDerEncoded();
        }
        string tempPublicKeyBase64 = Convert.ToBase64String(tempPublicKeyDer);

        // Create the JSON body to be sent to the device
        var body = new
        {
            alg = "secp384r1-aes256cbc",
            data = encryptedDataBase64,
            key = tempPublicKeyBase64
        };

        // Configure the JSON serializer options to avoid escaping characters
        var jsonOptions = new JsonSerializerOptions
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };

        string bodyJson = JsonSerializer.Serialize(body, jsonOptions);

        Console.WriteLine(bodyJson);
    }
}
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hashes, serialization
import os
import base64

def encrypt_message(public_key_path: str, message: bytes) -> dict:
    """
    Encrypts a message using ECDH key exchange and AES-256-CBC encryption for sending to a Notecard.

    Args:
        public_key_path (str): Path to the recipient's public key PEM file
        message (bytes): The message to encrypt

    Returns:
        dict: Dictionary containing the encrypted data:
            - ephemeral_public_key: Base64 encoded ephemeral public key in DER format
            - iv: Base64 encoded initialization vector
            - ciphertext: Base64 encoded encrypted message

    Raises:
        Various cryptography exceptions if encryption fails

    Note:
        This function is intended to be used with a pre-generated key pair.
    """
    # 1. Load recipient's public key
    with open(public_key_path, 'rb') as key_file:
        public_key = serialization.load_pem_public_key(key_file.read())
    
    # 2. Generate ephemeral key pair
    ephemeral_private_key = ec.generate_private_key(ec.SECP384R1())
    ephemeral_public_key = ephemeral_private_key.public_key()
    
    # 3. Perform ECDH key exchange
    shared_secret = ephemeral_private_key.exchange(
        ec.ECDH(), 
        public_key
    )
    
    # 4. Derive AES key from shared secret
    aes_key = hashes.Hash(hashes.SHA256())
    aes_key.update(shared_secret)
    aes_key = aes_key.finalize()[:32]  # First 32 bytes for AES-256
    
    # 5. Generate IV (Initialization Vector)
    iv = os.urandom(16)
    
    # 6. Encrypt message with AES-256-CBC
    cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    
    # Pad message to block size
    padded_message = message + b'\0' * (16 - len(message) % 16)
    ciphertext = encryptor.update(padded_message) + encryptor.finalize()
    
    # 7. Prepare output
    return {
        'ephemeral_public_key': base64.b64encode(
            ephemeral_public_key.public_bytes(
                encoding=serialization.Encoding.DER,
                format=serialization.PublicFormat.SubjectPublicKeyInfo
            )
        ).decode(),
        'iv': base64.b64encode(iv).decode(),
        'ciphertext': base64.b64encode(ciphertext).decode()
    }

Generate an Inbound (.qi) Notefile with the Notehub API

Next, you'll need to create and send a new inbound (.qi) Notefile on Notehub that includes the encrypted data. This is accomplished by using the Add Note API.

Consult the Notehub API documentation for examples of how to use the Add Note API.

As an example, a cURL request will end up looking something like this:

curl -X POST 
     -L 'https://api.notefile.net/v1/projects/<projectUID>/devices/<deviceUID>/notes/<file>/<note>' 
     -H 'Authorization: Bearer <access_token>' 
     -d '{"body": {"alg":"secp384r1-aes256cbc","data":"ji1O7IEZk5IOiYKAAEWWLyZ8r97TxQxdE=","key":"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2YeuC8lDvpSPYNa3b+CfHY0RyNJr2NsEfgix/rEpNEf4asGWuBkPojpVFraXM8cYtTbTLkviu0kq7FU0I5/5y1v7WCHyDw4x/Y"}}'

When the Notefile is created in your Notehub project, it will appear in the Events section:

An example inbound Notefile created with Notehub API

Sync Your Notecard with Notehub and Decrypt the Note

With the inbound Notefile created, all that is left to do is perform a sync on each Notecard device and decrypt the downloaded data.

For example, you can use the hub.sync API to manually initiate a sync...

{"req":"hub.sync"}

...and then use the note.get API with the "decrypt":true argument to decrypt, read, and delete the Notefile:

>
{"req":"note.get","file":"mysecrets.qi","decrypt":true,"delete":true}
{
 "time": 1729110462,
 "body": {
  "hello": "world"
 }
}

Additional Resources

  • note.add API Reference
  • Notehub Route Tutorials
Can we improve this page? Send us feedback
© 2025 Blues Inc.
© 2025 Blues Inc.
TermsPrivacy
Notecard Disconnected
Having trouble connecting?

Try changing your USB cable as some cables do not support transferring data. If that does not solve your problem, contact us at support@blues.com and we will get you set up with another tool to communicate with the Notecard.

Advanced Usage

The help command gives more info.

Connect a Notecard
Use USB to connect and start issuing requests from the browser.
Try Notecard Simulator
Experiment with Notecard's latest firmware on a Simulator assigned to your free Notehub account.

Don't have an account? Sign up