MuntashirAkon/AppManager · error · IOException

Unauthorized client: HMAC mismatch.

Error message

Unauthorized client: HMAC mismatch.

What it means

During the server side of the mutual HMAC handshake, the client must prove it holds the shared token by sending HMAC(token, Nonce_S). The server recomputes the expected HMAC and compares it in constant time (MessageDigest.isEqual); a mismatch means the client is not authorized, so an IOException('Unauthorized client: HMAC mismatch.') is thrown and the connection is refused.

Source

Thrown at libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java:218

            // Prove legitimacy of server to the client (HMAC_S = HMAC(token, Nonce_C))
            byte[] serverHmac = AuthUtils.calculateHmac(token, nonceC);
            sendMessage(serverHmac);

            // Send challenge to client (Nonce_S)
            byte[] nonceS = AuthUtils.generateNonce();
            sendMessage(nonceS);

            // Receive client's HMAC (HMAC_C)
            byte[] clientHmac = readMessage();

            // Validate client (HMAC_C == HMAC(token, Nonce_S)?)
            byte[] expectedClientHmac = AuthUtils.calculateHmac(token, nonceS);
            if (MessageDigest.isEqual(clientHmac, expectedClientHmac)) {
                FLog.log("DataTransmission#shakeHands: Authentication successful.");
            } else {
                FLog.log("DataTransmission#shakeHands: Authentication failed.");
                throw new IOException("Unauthorized client: HMAC mismatch.");
            }
        }
    }

    /**
     * Handle for messages received. For asynchronous operations or when the socket is not active,
     * nothing is done. But when server is running {@link #onReceiveMessage(byte[])} is called.
     *
     * @throws IOException When it fails to read the message received
     */
    public void handleReceive() throws IOException {
        if (!mAsync) return;
        while (mRunning) {
            onReceiveMessage(readMessage());
        }
    }

    /**

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Restart the AppManager server so the client re-reads the freshly generated token (the token is per-session in this design).
  2. Verify the client passes the exact same token string to DataTransmission that the server was started with (check ServerHandler/mConfigParams token path).
  3. Confirm no encoding mismatch: token must be the identical string on both sides (no trailing whitespace/newline).
  4. If you did not initiate this connection, treat it as an unauthorized probe and consider firewalling the port to local access only.

Example fix

// before: client caches token from previous server session
String token = readOldTokenFile();
// after: fetch the token the running server was started with
String token = serverConfig.getToken();
if (token == null) throw new IOException("Server token missing; restart server.");
transmission.shakeHands(token, Role.Client);
Defensive patterns

Strategy: try-catch

Validate before calling

if (token == null || token.isEmpty()) {
    throw new IOException("No server token available; (re)start the server first.");
}
// confirm the token equals the one the running server was started with

Try / catch

try {
    transmission.shakeHands(token, Role.Client);
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().contains("HMAC mismatch")) {
        // refetch token from the running server and retry once, else abort
    }
}

Prevention

When it happens

Trigger: shakeHands(role=Server) receives a clientHmac byte array that does not equal AuthUtils.calculateHmac(token, nonceS): the client was started with a different/missing token, the client's token file is stale after server restart, or a rogue/foreign client connects to the port.

Common situations: AppManager server regenerated its one-time token but an old client session still has the previous token; manually launching the server with a custom token while the app uses the default; security tooling or an attacker probing the root server port; clipboard/config corruption of the token.

Understand the failure class

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/ffe63890535bc6b4. Report an issue: GitHub.