OtterMind/Chat2DB · error · Error
Decryption error
Error message
Decryption error
What it means
CryptographyUtil.decryptAes throws 'Decryption error' when crypto.subtle.decrypt fails for an AES-GCM ciphertext. The method derives the key from the accessKey (SHA-256, first 16 bytes), splits the base64-decoded payload into a 12-byte nonce and the ciphertext+tag, then attempts GCM decryption. Failure means the key is wrong, the data is corrupt/truncated, the nonce/tag is mismatched, or the base64 encoding is invalid. The original exception is logged to console before rethrowing as a generic Error.
Source
Thrown at chat2db-community-client/src/utils/cryptography.ts:121
// AES-GCM decryption
public async decryptAes(encryptedValue: string | null): Promise<string | null> {
if (encryptedValue === null) return null;
const key = await this.generateAESKeyFromToken(this.accessKey);
const decoded = atob(encryptedValue);
const decodedBytes = new Uint8Array(decoded.split('').map((char) => char.charCodeAt(0)));
const nonce = decodedBytes.slice(0, GCM_NONCE_LENGTH);
const encryptedBytes = decodedBytes.slice(GCM_NONCE_LENGTH);
try {
const decryptedData = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: nonce, tagLength: 128 },
key,
encryptedBytes,
);
return new TextDecoder().decode(decryptedData);
} catch (exception) {
console.error('decrypt aes error', exception);
throw new Error('Decryption error');
}
}
// Derive signing key
private async deriveSigningKey(date: string): Promise<ArrayBuffer> {
const kSecret = new TextEncoder().encode('CHAT2DB' + this.secretKey);
const kDate = await this.hmacSHA256(date, kSecret);
return this.hmacSHA256(this.country, kDate);
}
// Compute signature
public async calculateSignature(canonicalRequest: string): Promise<string> {
const stringToSign = await this.createStringToSign(canonicalRequest);
const currentTime = this.getCurrentTimeZoneFormatted();
const signingKey = await this.deriveSigningKey(currentTime.substring(0, 8));
const signatureArrayBuffer = await this.hmacSHA256(stringToSign, signingKey);
return this.bufferToHex(signatureArrayBuffer);
}View on GitHub (pinned to 5ee1e990e7)
Solutions
- Verify the accessKey matches the one used at encryption time (decryptAes uses this.accessKey, encryptAes also uses this.accessKey — they must be the same instance config).
- Check console.error('decrypt aes error', exception) output for the specific WebCrypto error (OperationError usually indicates key/data mismatch).
- Ensure the encrypted value is a complete, uncorrupted base64 string with at least 12 bytes (nonce) + 16 bytes (tag).
- If the key rotated, re-encrypt the data with the new key or fall back to a default/empty value.
Example fix
// before
const decrypted = await crypto.decryptAes(encryptedValue);
// after
let decrypted = encryptedValue;
try {
decrypted = await crypto.decryptAes(encryptedValue);
} catch (e) {
console.warn('Decryption failed, using raw value as fallback');
} Defensive patterns
Strategy: try-catch
Validate before calling
function isLikelyEncryptedAes(value: string | null): boolean {
if (!value) return false;
try {
const decoded = atob(value);
return decoded.length >= 28; // 12 (nonce) + 16 (tag) minimum
} catch {
return false;
}
}
if (!isLikelyEncryptedAes(encryptedValue)) {
return encryptedValue; // not encrypted, return as-is
} Type guard
function isBase64String(v: unknown): v is string {
if (typeof v !== 'string') return false;
try { atob(v); return true; } catch { return false; }
} Try / catch
let decrypted = encryptedValue;
try {
decrypted = await crypto.decryptAes(encryptedValue);
} catch (e) {
if (e instanceof Error && e.message === 'Decryption error') {
// key mismatch or corrupt data — return raw value as fallback
decrypted = encryptedValue;
} else {
throw e;
}
} Prevention
- Ensure the CryptographyUtil instance used for decryption has the same accessKey as the one used for encryption.
- Validate the base64 integrity before attempting decryption.
- Log the original WebCrypto exception (console.error) for diagnosis.
- Avoid re-using encrypted data across key rotations without re-encryption.
When it happens
Trigger: Calling decryptAes(encryptedValue) where: (1) the accessKey used for decryption differs from the one used for encryption, (2) the encryptedValue was not produced by encryptAes (different format/nonce), (3) the base64 string is corrupted or truncated, (4) the GCM authentication tag does not match (data tampering or truncation).
Common situations: The accessKey/secretKey changed server-side but stale encrypted data remains. Decoding a value encrypted by a different instance/region with a different key. Data corruption in storage or transit. Encoding mismatch (URL-safe base64 vs standard base64).
Related errors
- License flow is disabled in community mode
- Pricing is disabled in community mode
- The baseURL is not valid!
- Fetch failed with status ${response.status}
- The response body is empty.
AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14).
Data as JSON: /api/errors/e089049e5332aa35.
Report an issue: GitHub.