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
- Restart the AppManager server so the client re-reads the freshly generated token (the token is per-session in this design).
- Verify the client passes the exact same token string to DataTransmission that the server was started with (check ServerHandler/mConfigParams token path).
- Confirm no encoding mismatch: token must be the identical string on both sides (no trailing whitespace/newline).
- 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
- Never cache the token across server restarts; read it fresh from the server's config each session.
- Compare token strings for exact equality (trim nothing silently) on both ends.
- Restrict the server port to localhost so only your app can even attempt the HMAC handshake.
- Treat repeated mismatches as a probe and log the peer for auditing.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized server: HMAC mismatch.
- Error! Invalid characters in arguments.\n
- Signing info verification failed.\nInstalled: ${certChecksum
- Zip slip vulnerability detected!\nExpected dest: " + new Fil
- Zip slip vulnerability detected!\nExpected dest: " + new Fil
AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12).
Data as JSON: /api/errors/ffe63890535bc6b4.
Report an issue: GitHub.