laurent22/joplin · error · Error
Authentication failed! ${error}
Error message
Authentication failed! ${error} What it means
Thrown by the mobile E2EE crypto module when GCM authentication fails during decryption — i.e. `decipher.update`/`decipher.final` raises because the auth tag does not match the ciphertext. This means the data was tampered with, the wrong key was used, or the AAD/plaintextLength are inconsistent. The original error is interpolated into the message.
Source
Thrown at packages/app-mobile/services/e2ee/crypto.ts:55
const authTag = cipher.getAuthTag();
return Buffer.concat([encryptedData[0], encryptedData[1], authTag]);
};
const decryptRaw = (data: ArrayBuffer, algorithm: CipherAlgorithm, key: CryptoBuffer, iv: ArrayBuffer, authTagLength: number, associatedData: CryptoBuffer) => {
const decipher = QuickCrypto.createDecipheriv(algorithm, key, iv, { authTagLength: authTagLength } as CipherGCMOptions) as unknown as DecipherGCM;
const plaintextLength = data.byteLength - authTagLength;
const authTag = new Uint8Array(data, plaintextLength, authTagLength);
const encryptedData = new Uint8Array(data, 0, plaintextLength);
decipher.setAuthTag(authTag);
decipher.setAAD(associatedData, { plaintextLength: plaintextLength });
try {
return Buffer.concat([decipher.update(encryptedData), decipher.final()]);
} catch (error) {
throw new Error(`Authentication failed! ${error}`);
}
};
const crypto: Crypto = {
randomBytes: async (size: number) => {
return new Promise((resolve, reject) => {
QuickCrypto.randomBytes(size, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
},
digest: async (algorithm: Digest, data: Uint8Array) => {View on GitHub (pinned to 2654b33620)
Solutions
- Confirm the master key / password matches the one used to encrypt (re-derive and compare).
- Verify `data.byteLength > authTagLength` before slicing the auth tag.
- Ensure the same `associatedData` and `authTagLength` are passed that were used at encryption time.
- If the blob is from a partial download, re-fetch the resource before decrypting.
Example fix
// before
return Buffer.concat([decipher.update(encryptedData), decipher.final()]);
// after
if (data.byteLength <= authTagLength) {
throw new Error('Ciphertext too short to contain an auth tag');
}
try {
return Buffer.concat([decipher.update(encryptedData), decipher.final()]);
} catch (error) {
throw new Error(`Authentication failed! ${error}`);
} Defensive patterns
Strategy: validation
Validate before calling
if (data.byteLength <= authTagLength) {
throw new Error('Ciphertext too short to contain an auth tag');
}
// Verify the derived key matches the encryption key before attempting decrypt. Type guard
null
Try / catch
try {
return await decrypt({ data, key, iv, associatedData, authTagLength });
} catch (error) {
if (/Authentication failed/i.test(error.message)) {
logger.warn('GCM auth tag mismatch — wrong key or corrupted blob');
return null;
}
throw error;
} Prevention
- Derive the key from the same master password used to encrypt.
- Validate ciphertext length is greater than authTagLength before slicing.
- Pass identical associatedData and authTagLength to encrypt and decrypt.
- Re-fetch partially-downloaded resources before retrying decryption.
When it happens
Trigger: Decrypting a master-key-encrypted note/resource with the wrong key, a corrupted blob, an auth tag sliced from the wrong offset, or mismatched `associatedData`/`plaintextLength`. Reached via the `decrypt` helper in `packages/app-mobile/services/e2ee/crypto.ts` when `data.byteLength <= authTagLength` is also a risk (negative plaintextLength).
Common situations: User changed/recovered the master password on a different device so the derived key differs; sync payload truncated or partially written; an older client wrote the blob without AAD and a newer client expects it; concurrency during partial download of an encrypted resource.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Could not verify your identity: %s
- Encrypted items cannot be modified
- No item with ID ${itemId}
- Cannot change encrypted item
- Please specify the sync target path.
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/01bc59470dd3357e.
Report an issue: GitHub.