csha
    • CSha
    • Building and installation
    • Commands and examples
    • Architecture
    • Changelog
    • AI usage
    • License
    • Quick start
    • Blob Streams
    • Docker images as PNGs
    • Formats and keys
    • C++ API
    • Troubleshooting
    • Encryption algorithms
    • Testing

    Blob Streams

    Blob Streams adds live, two-way encryption to CSha. Plaintext enters on the
    sending device, encrypted byte chunks pass through the network or relay, and
    the receiving device authenticates and decrypts them as they arrive. The same
    connection carries replies in the opposite direction.
    Alice's device                 Relay/server                  Bob's device
    private identity key           no device private keys        private identity key
    trusted Bob certificate        forwards opaque TLS bytes     trusted Alice certificate
    plaintext -> encryption  --------------------------------->  decryption -> plaintext
    plaintext <- decryption  <---------------------------------  encryption <- plaintext
    This is a C++ library and a macOS/Linux CLI, not a phone application or hosted
    messaging service. Both users must be online. The included relay pairs one
    conversation and exits afterward; it does not store messages for offline users.

    Identity and two-way keys#

    Each device generates its own Ed25519 private key and self-signed public
    certificate. The two users exchange only public certificates and verify their
    SHA-256 public-key fingerprints over an already trusted channel (for example,
    in person). The certificate is the explicit peer identity; a hostname or a
    certificate supplied by the relay does not establish trust.
    The devices use OpenSSL TLS 1.3 with mutual certificate authentication,
    ephemeral X25519 key agreement, and AES-256-GCM. TLS derives independent traffic
    secrets for the two directions. Certificate verification is mandatory on both
    sides, including the expected public key and certificate validity dates. The
    private key never travels to the peer or relay. Generated certificates expire
    after one year; generate and exchange a new identity to rotate it.
    Only TLS 1.3 and the csha-blob-stream/1 ALPN protocol are accepted. Early data,
    session tickets and resumption are disabled. Every new connection performs a
    fresh authenticated handshake. TLS authenticates records and their ordering;
    replayed, modified, or out-of-order records fail. A clean stream ending requires
    TLS close_notify; a raw TCP disconnect is reported as truncation. Previously
    received complete records may already have been delivered before a later error.
    Applications transferring an atomic file should write to a temporary destination
    and publish it only after successful stream completion.
    These are separate keys and transport from the image-map formats. No map is
    generated for network messages. The implementation uses the existing TLS
    protocol rather than introducing a custom cryptographic handshake. References:
    OpenSSL peer verification
    and authenticated shutdown.

    Build#

    OpenSSL 3.0 or newer with SSL and Crypto is required. The existing build produces
    csha and csha_blob_streams; the latter links only OpenSSL, not OpenCV. Tests
    also require Python 3.9+ and permission to open loopback sockets.
    cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
    cmake --build build --parallel
    ctest --test-dir build --output-on-failure
    ./build/csha blob-streams --help

    Create device identities#

    On Alice's device:
    ./build/csha blob-streams keygen --identity alice
    ./build/csha blob-streams fingerprint --peer alice.cert.pem
    On Bob's device:
    ./build/csha blob-streams keygen --identity bob
    ./build/csha blob-streams fingerprint --peer bob.cert.pem
    Exchange alice.cert.pem and bob.cert.pem and verify their fingerprints. Keep
    alice.key.pem and bob.key.pem on their respective devices. The CLI creates
    private keys with mode 0600 and refuses to overwrite an existing identity.
    Keys are unencrypted PEM files; a phone integration should use device-protected
    storage and supply the identity to the library locally. Never include private
    keys in HTTP requests, logs, source control, or relay configuration.

    Direct communication#

    Bob listens on an address reachable by Alice:
    ./build/csha blob-streams listen --address 0.0.0.0:7443 \
      --identity bob --peer alice.cert.pem
    Alice connects:
    ./build/csha blob-streams connect --address BOB_HOST:7443 \
      --identity alice --peer bob.cert.pem
    Both commands read local plaintext from stdin and write received plaintext to
    stdout. Status and errors go to stderr, so stdout can be a binary file or pipe.
    Terminal input is normally delivered after Enter; pipes are forwarded as bytes
    become available. EOF closes only the local sending direction. Each process
    continues receiving until the peer also sends an authenticated EOF.
    For example, Bob can receive a file while sending nothing:
    ./build/csha blob-streams listen --address 0.0.0.0:7443 \
      --identity bob --peer alice.cert.pem </dev/null >received.bin
    Alice sends the file:
    ./build/csha blob-streams connect --address BOB_HOST:7443 \
      --identity alice --peer bob.cert.pem <source.bin >reply.bin

    HTTP relay: both devices connect outward#

    On the relay host:
    ./build/csha blob-streams relay --address 0.0.0.0:8080 --room conversation-42
    Alice:
    ./build/csha blob-streams connect --relay RELAY_HOST:8080 \
      --room conversation-42 --role initiator --identity alice --peer bob.cert.pem
    Bob:
    ./build/csha blob-streams connect --relay RELAY_HOST:8080 \
      --room conversation-42 --role responder --identity bob --peer alice.cert.pem
    The relay accepts the Blob Streams HTTP/1.1 CONNECT request for one configured
    room and one endpoint of each role. It replies 200 Connection Established
    once both endpoints have joined, then forwards the end-to-end TLS byte stream.
    It never loads a device identity. Roles choose who starts the TLS handshake;
    both endpoints can send and receive equally.
    This is a paired CONNECT relay, not a general-purpose HTTP proxy. Its exact
    request is:
    Header lines use CRLF. The CLI accepts only its paired relay response. This is
    not REST encryption or a browser HTTP client. The library below can be adapted
    to an ordered WebSocket or other bidirectional byte transport.
    The HTTP room and routing information are visible to the relay and network.
    Message sizes, timing, and endpoint addresses also remain visible. The room is
    a pairing label, not an authentication key. An untrusted relay cannot impersonate
    a pinned device, but it can withhold, interrupt, or misroute a connection. The
    sample relay has no account system, rate limiting, persistence, or NAT traversal
    beyond both devices connecting outward. Those belong in a deployed relay service.
    TLS and HTTP setup have a 10-second timeout. Start both relay clients within
    that interval. --timeout SECONDS configures idle/accept timeouts (default 300,
    range 1 through 86400). No automatic reconnect or plaintext retry occurs.

    Embed in a device or HTTP/WebSocket application#

    The public header is src/streams/blob_stream.h. Link csha_blob_streams and
    OpenSSL. The library itself uses no sockets and can be called by a mobile app's
    network adapter; the provided CLI adapter uses POSIX sockets.
    Use one owner/thread per session. Keep transport bytes in order in each direction;
    a session accepts arbitrary fragmentation, including individual bytes. Drain both
    outputs after each receive/send operation, and apply transport backpressure.
    Input and send calls accept at most 16 KiB; internal queues are capped at 256 KiB.
    The CLI adds bounded queues and polls stdin, stdout, and the socket so a slow
    consumer does not make memory grow with the conversation length. Error states
    are terminal; discard a failed session and do not deliver queued plaintext from it.
    finish() is idempotent, and sending after finishing is an error.
    The engine does not preserve application message boundaries: applications can
    send newline-delimited text, length-prefixed messages, media frames, or binary
    file bytes. It does not translate human languages.

    Validation and remaining integration#

    Automated tests exercise mutual trust failures, mismatched private keys,
    fragmented handshakes and binary records, live delivery before EOF, independent
    directions, tampering, duplicate/out-of-order records, replay into a new session,
    truncation, queue limits, and authenticated shutdown. Process tests exercise both
    direct TCP and the HTTP relay, simultaneous transfers larger than all buffers,
    private-key permissions and overwrite rejection.
    The tests run locally; no phone build, cellular-network test, public deployment,
    or independent security audit has been performed.

    Command reference#

    CommandRequired inputsResult
    keygen--identity PREFIXNew PREFIX.key.pem and PREFIX.cert.pem; refuses replacement
    fingerprint--peer CERT.pemPublic-key fingerprint to compare through a trusted channel
    listen--address HOST:PORT --identity PREFIX --peer CERT.pemAccept one direct peer and exchange stdin/stdout bytes
    connect (direct)--address HOST:PORT --identity PREFIX --peer CERT.pemConnect to a listening peer
    relay--address HOST:PORT --room NAMEPair one initiator and one responder, then forward bytes
    connect (relay)--relay HOST:PORT --room NAME --role initiator or responder, plus identity and peerJoin the paired relay and authenticate the end peer
    Use --timeout SECONDS for idle/accept timeout configuration. A relay endpoint
    needs a matching room and opposite roles, but role selection does not grant
    one side more authority or make it send-only.

    Local two-terminal walkthrough#

    Generate both identities in a new test directory with the keygen commands
    above. Since this is a local demonstration, both public certificates are
    available there; on separate devices, exchange and verify them first.
    Terminal 1:
    csha blob-streams listen --address 127.0.0.1:7443 \
      --identity bob --peer alice.cert.pem
    Terminal 2:
    csha blob-streams connect --address 127.0.0.1:7443 \
      --identity alice --peer bob.cert.pem
    Type a line in either terminal and press Enter. It should appear in the other
    terminal before either process exits. End stdin on both sides to finish the
    session. A second conversation needs a new listening process.

    Publish a received file only on success#

    For an atomic file workflow, receive into a temporary filename and rename it
    only when the command exits successfully. Run in a new directory and choose
    an unused final filename:
    csha blob-streams listen --address 127.0.0.1:7443 \
      --identity bob --peer alice.cert.pem </dev/null >received.part && mv received.part received.bin
    On failure, received.part may contain previously authenticated chunks but not
    a complete file. Do not treat it as a successful transfer. This completion rule
    also applies to adapters using the C++ session API.
    See Troubleshooting for pairing, timeout, and
    shutdown problems, and C++ API for a method table.
    Modified at 2026-10-17 15:07:06
    Previous
    Quick start
    Next
    Docker images as PNGs
    Built with