@zigbang-smarthome/seal
v26.5.0
Published
Wire-compatible TypeScript port of Samsung SDS SEAL 2.0.1. See ./docs/protocol.md.
Readme
@zigbang-smarthome/seal
Wire-compatible TypeScript port of Samsung SDS SEAL 2.0.1 (com.sds.seal) — a proprietary IoT device authentication / encryption protocol used by Samsung-branded smart doorlocks, wallpads, and lobby phones manufactured 2014~. Server-side only. Runs on Bun, Node 18+, Deno, and Cloudflare Workers (workerd).
🪦 DEPRECATED — Legacy compatibility only
SEAL has not received an updated release since the 2016-09-23 build of v2.0.1, and no public security review has been published. This package exists solely so server infrastructure operating SEAL-speaking devices doesn't have to remain pinned to the original Java runtime. Do not adopt SEAL for new projects. Modern alternatives:
Install
bun add @zigbang-smarthome/seal
# or: npm i @zigbang-smarthome/seal / pnpm add @zigbang-smarthome/sealZero runtime dependencies on Node-specific APIs. Uses crypto.subtle (WebCrypto), TextEncoder/TextDecoder, btoa/atob, and native bigint. Pulls in three audited libraries:
| Package | Why |
|---|---|
| @noble/hashes | byte utilities (concatBytes, bytesToHex, hexToBytes); audited by Trail of Bits |
| bigint-mod-arith | modular exponentiation |
| fast-xml-parser | optional — only loaded by the XML config helper |
What's in the box
import {
// wire codec
decodeHandshakeRequest, encodeHandshakeRequest,
decodeHandshakeResponse, encodeHandshakeResponse,
decodeEncryptedMessage, encodeEncryptedMessage,
decodeOnePass, encodeOnePass,
// server side
processHandshake, decryptOnePass,
encryptMessage, decryptMessage,
// client side
SealClient, encryptOnePass,
// public-key derivation (id → publicKey)
AlphabetPublicKeyGenerator, PublicParamPublicKeyGenerator, isValidKeyPair,
// ClientLongTermKey
deriveCltk, checkCltk,
// server config XML loader
parseServerSettingsXml,
// primitives (re-exported for convenience)
modPow, sha256, aesCbcEncrypt, aesCbcDecrypt,
bigintToBytesSigned, bytesToBigintUnsigned,
bytesToHex, hexToBytes,
utf8, fromUtf8, b64encode, b64decode,
randomBigint,
// typed errors (all extend SealError)
SealError, InvalidMessageFormatError, VersionIncompatibleError,
DecryptFailedError, UserAuthFailedError, IdRangeViolationError, InvalidSettingsError,
} from "@zigbang-smarthome/seal";Full protocol specification (wire format, handshake math, OnePass derivation, security analysis): docs/protocol.md.
Quick start
Server — handshake
import {
processHandshake, encodeHandshakeResponse, decodeHandshakeRequest,
encryptMessage, decryptMessage, encodeEncryptedMessage, decodeEncryptedMessage,
randomBigint, type SealServerConfig,
} from "@zigbang-smarthome/seal";
const cfg: SealServerConfig = {
serverId: "MYSERVER",
versions: {
1: { n: 0x...n, g: 0x...n, serverPrivateKey: 0x...n },
2: { n: 0x...n, g: 0x...n, serverPrivateKey: 0x...n },
},
};
// Inbound HsReq from device
const req = decodeHandshakeRequest(b64FromDevice);
// Process — `r` is the server's 160-bit ephemeral.
const r = randomBigint(160);
const { response, sessionKey } = await processHandshake(cfg, req, longTermKey, r);
// Send wire-encoded response back
const respB64 = encodeHandshakeResponse(response);
if (sessionKey === null) {
// auth failure — `response` is the header-only blob
return;
}
// Subsequent traffic
const em = await encryptMessage(sessionKey, "hello device");
const wire = encodeEncryptedMessage(em);
const inbound = decodeEncryptedMessage(b64FromDevice2);
const plaintext = await decryptMessage(sessionKey, inbound);Client — handshake
import { SealClient, encodeHandshakeRequest, decodeHandshakeResponse, randomBigint, modPow } from "@zigbang-smarthome/seal";
const client = await SealClient.connect({
n, g, serverId: "MYSERVER",
serverPublicKey: modPow(g, sk, n), // out-of-band knowledge of g^sk
x: randomBigint(160),
});
const req = await client.makeHandshakeRequest({
clientId: "DEVICE001",
clientPw: "longTermKey",
extraParams: ["serial", "model"],
});
const reqB64 = encodeHandshakeRequest(req);
// ...send to server, receive respB64...
const ok = await client.finishHandshake(decodeHandshakeResponse(respB64));
if (ok) {
// `client.sessionKey` is now usable
}OnePass — anonymous one-shot encryption
import { encryptOnePass, decryptOnePass, encodeOnePass, decodeOnePass, randomBigint } from "@zigbang-smarthome/seal";
// Client
const opMsg = await encryptOnePass({
n, g, serverId: "MYSERVER",
serverPublicKey: modPow(g, sk, n),
plaintext: "diagnostic-payload",
x1: randomBigint(160),
x2: randomBigint(160),
});
const wire = encodeOnePass(opMsg);
// Server
const recovered = await decryptOnePass(cfg, decodeOnePass(wire));Public-key generators
makePublicKey(id) deterministically derives the public key for an entity (server or client) from its id string. Used at config-load time to verify g^sk == publicKey(serverId).
import { AlphabetPublicKeyGenerator, PublicParamPublicKeyGenerator, isValidKeyPair } from "@zigbang-smarthome/seal";
// v1: in-algorithm derivation (4-char chunking + sort permutation + SHA-256)
const v1 = new AlphabetPublicKeyGenerator(n1, g1);
const pk1 = await v1.makePublicKey("MYSERVER");
// v2: precomputed binary lookup table file
const v2 = new PublicParamPublicKeyGenerator(binFileBytes);
const pk2 = await v2.makePublicKey("MYSERVER");
// validate (g, sk) pair against the generator
const ok = await isValidKeyPair(v1, sk1, "MYSERVER");XML config loader
Parses the server-settings format used by Samsung SDS SEAL deployments (XXE-protected — DOCTYPE rejected before parse).
import { parseServerSettingsXml } from "@zigbang-smarthome/seal";
const xml = await readFile("/etc/seal/server-settings.xml", "utf-8");
const cfg = parseServerSettingsXml(xml, "/etc/seal/server-settings.xml");
// cfg.versions[1] = { publicParameterN, publicParameterG, serverPrivateKey } (hex strings)
// cfg.versions[2] = { binFilePath, serverPrivateKey } (hex string for sk)Runtime support
| Runtime | Status | |---|---| | Bun 1.3+ | ✅ | | Node 18+ | ✅ | | Deno 2+ | ✅ | | Cloudflare Workers (workerd) | ✅ |
The library only assumes Web-standard globals (crypto, TextEncoder, btoa/atob, bigint). On Node 16/17, polyfill globalThis.crypto from node:crypto's webcrypto.
⚠️ Known security issues
These are properties of the SEAL protocol itself. Wire compatibility requires preserving them, but every consumer should be aware:
- Predictable server ephemeral randomness. The Java reference implementation uses
new java.util.Random(System.currentTimeMillis())for its 160-bit ephemeral, which is not a CSPRNG. This port intentionally diverges on this point —randomBigint(160)usescrypto.getRandomValues. Callers must pass a CSPRNG-derived value asr. - No authenticated cipher.
EncryptedMessageis AES-128-CBC-PKCS5 with no MAC; bit-flip attacks on the ciphertext propagate to plaintext (CBC malleability). - No forward secrecy. The server's long-term private key is static; compromise replays all past sessions.
- OnePass has no replay protection. No nonce or timestamp.
- 1024-bit modulus. NIST SP 800-131A deprecates 1024-bit DH-style operations by 2030.
- 1-byte length prefix in wire format. Caps each
BigInteger-bearing field at 255 bytes, indirectly enforcing the small modulus. - No public cryptographic review. Not a peer-reviewed protocol; 0 third-party analysis published.
Full analysis: docs/protocol.md §8.
How it's verified
The actual seal-server-2.0.1.jar runs in a deterministic harness (Java) that emits byte-level test vectors. The TS implementation is asserted against those vectors at every release across all four runtimes.
seal-server-2.0.1.jar (real Java JAR)
↓ harness/SealVectorGen.java with reflection-injected randomness
test-vectors/vectors.json (Java-produced bytes)
↓ test/vectors.test.ts replays the same inputs
↓
TS output == Java output, byte-for-byte (37 tests × 4 runtimes)Coverage: handshake (cipher3 / sessionKey / confirmMessage), AES-128-CBC-PKCS5, OnePass derivation, ClientLongTermKey, v1/v2 public-key generators, all four message-type wire codecs, BigInteger signed-byte semantics, auth-failure paths.
License
The licensing scope of the underlying SEAL protocol is currently under review. Until that completes, this package is published as private and should be treated as internal-use-only — do not redistribute or fork outside the publishing organization. The npm-distributable form will be tagged once review concludes.
For internal status and contract verification details, contributors should consult the project tracker rather than this README.
Layout
@zigbang-smarthome/seal/
├── src/index.ts — implementation
├── docs/protocol.md — full protocol spec
├── test/vectors.test.ts — cross-runtime test suite
├── test-vectors/ — JSON vectors emitted by harness
└── harness/ — Java vector generator (Maven)