Skip to main content
A factory device is unrestricted: Bluetooth LE control is password-gated, USB is not, and recordings are written unencrypted. Both settings are configurable and independent of each other. Locking the control plane determines who can command the device; encrypting at rest determines who can read the recordings it has already made.

One password, both transports

The device uses a single control password (factory default 123456). BLE always requires it. USB does not enforce it until the device is locked, which keeps bench work convenient while allowing a fielded device to be secured.
Changing the lock in either direction requires the current password, so an unattended device cannot be unlocked without it. Rekeying is available from either transport and applies to both:
Once USB is locked, the form that omits the old password is unavailable and factory-reset is the only remaining route.
info, state, storage and factory-reset respond regardless of lock state, so a locked device still reports its identity and can always be recovered. ef-cli info reports usb access and encryption, so a device that is refusing a command indicates why.
Authenticate once and the remainder of that connection is authenticated. USB and BLE authenticate independently of each other. The grant is time-limited, because USB provides the device no means of detecting that the client which obtained it has disconnected. It ends at whichever occurs first: two minutes idle, ten minutes from the time it was granted (traffic does not extend this), a new authentication attempt on the same link, or a cable cycle. ef::Device repeats the handshake and retries transparently, so a long-lived session is unaffected. A client implementing the wire protocol directly must handle AUTH_REQUIRED on any gated verb and re-authenticate in response. open() succeeds even with an incorrect password, because info, state and storage are required by an operator recovering a device whose password has been lost. Verify that gated calls will succeed before issuing one:

Opening a locked device for one session

Rather than supplying --password with every command, open the device for the duration of the power session:
This does not change the stored policy. The device continues to report itself as locked, so a power loss closes it with no state to retain, and ef-cli info reports LOCKED, open for this session rather than either state alone. The command is refused on a device that is not locked, and a plain lock on from either transport ends the session. Authentication never applies this implicitly. A password authorizes one caller for one call, whereas opening the device is a separate operator decision, since a grant that extended from one client to every other client on the cable is the behavior this replaced.

Encrypting recordings at rest

Recordings can be AES-256-GCM encrypted under a per-device key. The device generates the key itself and returns it once, at creation:
Save the key when create prints it. The device retains a working copy, but no command other than key show prints it again, and a factory reset destroys it. Without a saved copy, recordings made under that key cannot be read.
To use a key you already hold, supply it instead of having the device generate one. The key is exactly 32 bytes, given as a file, on stdin, or as 64 hex characters:
key set is refused while a key already exists, and while the device is recording. The reply names the key by ID and never returns the bytes.
Replacing a key is three steps, not one. encryption delete turns encryption off, and key set does not turn it back on:
--out refuses to overwrite an existing file rather than truncating it, because the file it would replace may be the only copy of another device’s key. Enabling encryption without a key is refused, so a session cannot report that it is encrypting while recording unencrypted. Existing recordings retain the state they were written with; the setting applies only to subsequent recordings. ef-cli info reports the key ID, which is the first four bytes of its SHA-256 and never the key itself. The same ID is written into each recording header, which identifies the saved key required to open a given file. On a locked device the ID requires the password like any other gated field, since that value in a file header would otherwise allow a nearby party to associate a device with recordings in their possession. Whether a key exists is always readable. record list marks each recording [encrypted] or [unencrypted] by reading the container identifier from the file, so it reports the state on disk rather than the current setting.

Reading an encrypted recording

Download the recording, then decrypt it on the host with ef-decrypt:
download neither decrypts nor renames. Without a destination it writes <name>.mcap regardless of the contents, so an encrypted recording is written to a file that no MCAP reader will open unless a destination is specified, as above. The key file is the one written by key show --out: 32 raw bytes, or 64 hex characters with an optional trailing newline. ef-decrypt writes the plaintext to the named output and its summary to stderr, allowing it to be used in a pipeline. A recording truncated by power loss returns exit 1 rather than indicating corruption, and decrypts up to its last complete chunk. An incorrect key is detected at the header rather than through repeated chunk failures, and the error names the key_id the file requires. ef-decrypt requires libcrypto (apt install libssl-dev) and is omitted by the build when it is unavailable.

Destroying the key

Two commands destroy the key, and both are irreversible for every recording written under it, including copies already transferred elsewhere.
The first form allows the key to be saved if older recordings are still needed. The device requires key_id to match the installed key, which prevents an SDK caller from destroying a key it has not identified, and it is refused unless the device is IDLE: a running session holds the key in memory and would otherwise continue writing under a key the device had reported as destroyed. Rotation is delete followed by create, as two deliberate steps. create is refused while a key exists, because replacing one would render every recording written under it permanently undecryptable.

Recovering a lost password

Reset the device over USB, where physical possession serves as the credential:
This restores the password, lock state, encryption setting, WiFi credentials, calibration, capture configuration and recordings to their defaults.
factory-reset also destroys the encryption key, which renders every recording written under it permanently unreadable, wherever those files are now stored. Save the key with key show --out <file> first if those recordings are still required.
Unlike encryption delete, the reset does not return the key before destroying it. It responds unauthenticated over USB, and returning a key to an unauthenticated caller is the exposure that destroying it is intended to remove. Over BLE it requires the password like any other verb, since an unauthenticated remote reset would allow a nearby party to destroy the key for every recording. The key is held in the device’s writable storage, so a full reflash destroys it as a reset does. A saved copy is the only form that survives either.

Scripting these commands

encryption delete --confirm and factory-reset prompt for typed confirmation on a terminal. Without a terminal (ssh box 'ef-cli ...', cron, redirected stdin) they refuse rather than prompt, so pass --yes to confirm in a script:

From the library

Read the current state from get_device_information(): usb_locked, session_unlocked, encryption_enabled, encryption_key_present, encryption_key_id and encryption_algorithm. See the API reference for the full contracts.