Skip to content

Quick Start Guide

Get your LSL streams encrypted in under 5 minutes. No code changes required.


Prerequisites

  • An existing LSL setup (outlets and inlets working)
  • Secure LSL library installed (see Installation)

Step 1: Generate Encryption Keys

On each device that runs LSL (your EEG amplifier computer, recording workstation, etc.), run:

./lsl-keygen

You'll be prompted for a passphrase to protect your private key:

LSL Security Key Generator
==========================

Private key will be encrypted with a passphrase for security.
(Press Enter for no passphrase, but this is NOT recommended)

Enter passphrase: ********
Confirm passphrase: ********

Generating Ed25519 keypair...

[OK] Keypair generated successfully!
[OK] Configuration saved to: ~/.lsl_api/lsl_api.cfg
[OK] Private key encrypted with passphrase (2FA enabled)

To unlock at runtime (in order of preference):
  1. Device-bound session token: lsl-config --remember-device (recommended)
  2. Environment variable: LSL_KEY_PASSPHRASE (less secure)
  3. Programmatic: call lsl_security_unlock() in your application

Security is now enabled for this device.
Note: All devices on your network need the same key for secure communication.

Passphrase Protection

Like SSH keys, the private key is encrypted with a passphrase by default. This provides two-factor authentication: something you have (the key file) plus something you know (the passphrase).

For convenience on lab workstations, you can create a device-bound session token that remembers the passphrase securely:

./lsl-config --remember-device --passphrase

Low-risk Environments

For closed lab environments without regulatory requirements, you can skip the passphrase by pressing Enter twice (with confirmation), or use:

./lsl-keygen --insecure

Step 2: Verify Configuration

Check that security is properly configured:

./lsl-config --check

Expected output:

Security configuration status:
  Config file: /Users/you/.lsl_api/lsl_api.cfg
  Security enabled: true
  Key created: 2025-12-05T19:00:00Z
  Key fingerprint: SHA256:70:14:e1:b5:7f:93:ae:af...

[OK] Configuration valid

Step 3: Run Your Applications

That's it! Your existing LSL applications now stream encrypted data automatically.

import pylsl

# Same code as before - encryption is automatic
info = pylsl.StreamInfo('MyEEG', 'EEG', 64, 1000, 'float32', 'myuid')
outlet = pylsl.StreamOutlet(info)

# Data is encrypted transparently
outlet.push_sample([0.0] * 64)
% Same code as before - encryption is automatic
info = lsl_streaminfo(lib, 'MyEEG', 'EEG', 64, 1000, 'cf_float32', 'myuid');
outlet = lsl_outlet(info);

% Data is encrypted transparently
outlet.push_sample(zeros(1, 64));
// Same code as before - encryption is automatic
lsl::stream_info info("MyEEG", "EEG", 64, 1000, lsl::cf_float32, "myuid");
lsl::stream_outlet outlet(info);

// Data is encrypted transparently
std::vector<float> sample(64, 0.0f);
outlet.push_sample(sample);

Verifying You're Using Secure LSL

Before anything else, verify you're using the secure library:

import pylsl
info = pylsl.library_info()
print(info)
# Should contain "security:X.X.X"

# If the library exposes the new API (pylsl with security patch):
# print(f"Secure build: {pylsl.is_secure_build()}")
# print(f"Version: {pylsl.full_version()}")
#include <lsl_cpp.h>
#include <iostream>

int main() {
    std::cout << "Secure build: " << lsl::is_secure_build() << "\n";
    std::cout << "Base version: " << lsl::base_version() << "\n";
    std::cout << "Security version: " << lsl::security_version() << "\n";
    std::cout << "Full version: " << lsl::full_version() << "\n";
    return 0;
}
// Output:
// Secure build: 1
// Base version: 1.16.1
// Security version: 1.0.0
// Full version: 1.16.1-secure.1.0.0-alpha
./lsl-config --check
# Or run lslver:
./lslver
# Look for "security:X.X.X" in the output

Wrong Library?

If you see lsl::is_secure_build() = 0 or the library info doesn't contain "security", you're using the regular liblsl. See Migration Guide for how to switch.


Verifying Encryption is Active

Check Stream Security Status

You can verify a stream is encrypted using the security API:

import pylsl
import lsl_security_helper  # Adds security methods to pylsl

streams = pylsl.resolve_stream('type', 'EEG')
for stream in streams:
    if stream.security_enabled():
        print(f"🔒 {stream.name()} is encrypted")
        print(f"   Fingerprint: {stream.security_fingerprint()}")
    else:
        print(f"⚠️  {stream.name()} is NOT encrypted")
std::vector<lsl::stream_info> streams = lsl::resolve_stream("type", "EEG");
for (const auto& stream : streams) {
    if (stream.security_enabled()) {
        std::cout << "🔒 " << stream.name() << " is encrypted\n";
        std::cout << "   Fingerprint: " << stream.security_fingerprint() << "\n";
    }
}

Visual Confirmation in LabRecorder

With the secure version of LabRecorder, encrypted streams show a lock icon:

Available Streams:
  🔒 EEG-Amplifier (lab-eeg-01)
  🔒 EyeTracker (lab-eye-01)
  🔒 MotionCapture (lab-mocap-01)

What About Devices Without Keys?

Secure LSL uses unanimous security enforcement:

  • If your device has security enabled, it will only connect to other secured devices
  • Connections to unsecured devices fail with a clear error message:
Connection refused: outlet does not have security enabled.
Unanimous security enforcement requires all devices to use encryption.

This ensures you never accidentally mix encrypted and unencrypted streams.

All or Nothing

You must generate keys on all devices in your lab network. Partial deployment is intentionally not supported to prevent security gaps.


Troubleshooting

"Connection refused: security mismatch"

One device has security enabled, another doesn't. Run lsl-keygen on all devices.

"Configuration file not found"

Run lsl-keygen to generate the configuration file.

Streams aren't connecting

Check that all devices have the same security state:

./lsl-config --check

See full troubleshooting guide →


Next Steps