Baichuan "modern" login: nonce, hashed credentials and the session key.

The login takes two round trips, both XOR-encrypted:

cmd 1, class 0x1465, no body   ->  <Encryption><nonce>...</nonce></Encryption>
cmd 1, class 0x1464, loginXml  ->  status 200, or 401 on bad credentials

Everything after that is AES-encrypted with the key from loginCredentials.

Examples

Build the login body from a nonce

import { assertEquals, assertStringIncludes } from "@std/assert";
import {
  loginCredentials,
  loginXml,
  parseNonce,
} from "@hertzg/reolink-api/protocol/login";

const nonce = parseNonce(
  "<body><Encryption><nonce>NONCE-0123</nonce></Encryption></body>",
);
const credentials = loginCredentials({
  username: "admin",
  password: "hunter2",
  nonce,
});

assertEquals(nonce, "NONCE-0123");
assertStringIncludes(
  loginXml(credentials),
  "<userName>329F51D2942C5953794C5C99EB43D46</userName>",
);

Functions

f
loginCredentials(options: LoginCredentialsOptions): LoginCredentials

Derives the hashed credentials and the AES session key from a nonce.

f
md5Modern(text: string): string

Hashes a string the way Baichuan's modern login does: MD5 of the UTF-8 bytes, as upper-case hex, with the last hex digit dropped (31 characters).

f
parseNonce(xml: string): string

Reads the nonce from the camera's reply to the nonce request.

Type Aliases

T
LoginCredentials = { userHash: string; passwordHash: string; aesKey: Uint8Array; }

The hashed credentials sent at login, and the session key that follows.

T
LoginCredentialsOptions = { username: string; password: string; nonce: string; }

What the login needs: the user's credentials and the camera's nonce.