How WeKnora Implements AES-256-GCM At-Rest Encryption with Key Rotation for Credentials

WeKnora encrypts sensitive credentials using AES-256-GCM before persistence, sourcing the 32-byte encryption key from the SYSTEM_AES_KEY environment variable, and handles key rotation gracefully by detecting decryption failures and re-encrypting data with the new key on subsequent writes.

Tencent's WeKnora protects sensitive configuration data—such as API keys and database passwords—using AES-256-GCM at-rest encryption with key rotation support. The implementation relies on a stateless encryption layer in internal/utils/crypto.go that reads the current encryption key from environment variables at runtime, ensuring credentials remain secure even if the underlying storage is compromised.

Core Encryption Architecture

Key Retrieval via SYSTEM_AES_KEY

The encryption system centers on the GetAESKey() function located in internal/utils/crypto.go. This function retrieves the encryption key from the SYSTEM_AES_KEY environment variable and validates that it is exactly 32 bytes long, matching the requirements for AES-256. If the variable is unset or contains a key of incorrect length, GetAESKey() returns nil according to lines 19-27, effectively disabling encryption and preventing the use of malformed keys.

Encryption Before Persistence

When persisting credentials, WeKnora checks for a valid key before writing to storage. In internal/types/web_search_provider.go (lines 98-102) and internal/types/vectorstore.go (lines 162-169), the code follows this pattern:

if key := utils.GetAESKey(); key != nil && cred != "" {
    if enc, err := utils.EncryptAESGCM(cred, key); err == nil {
        storedCred = "enc:v1:" + enc
    }
}

The EncryptAESGCM() function generates a random nonce for each encryption operation and returns the ciphertext, which is then prefixed with enc:v1: to identify the encryption version. This prefix allows the system to distinguish encrypted values from plaintext during deserialization.

Decryption and Error Handling at Runtime

During retrieval, DecryptAESGCM() in internal/utils/crypto.go handles the reverse operation. If the SYSTEM_AES_KEY is missing, rotated, or has the wrong length, the function returns the sentinel error ErrEncryptedDataMissingKey as defined in lines 61-63. Callers such as WebSearchProvider (lines 122-124) catch this error, log a warning message indicating the key may be missing or rotated, and treat the credential as unconfigured rather than failing silently or crashing.

Handling Key Rotation Without Downtime

WeKnora's at-rest encryption with key rotation operates statelessly, enabling seamless key updates without code changes or service restarts.

Detection of Rotated Keys: Because GetAESKey() reads the environment variable on every operation, setting a new SYSTEM_AES_KEY immediately changes the active encryption key. When the system attempts to decrypt existing ciphertext with the new key, DecryptAESGCM() fails and returns ErrEncryptedDataMissingKey. The calling code logs this event (e.g., "decrypt failed (SYSTEM_AES_KEY missing/rotated?)") and treats the credential as empty, preventing the use of stale or corrupted data.

Automatic Re-encryption: On the next write operation—such as updating a vector store connection in the UI—the system encrypts the credential with the current key. This effectively migrates the data to the new key without requiring a separate migration script. Administrators can force a full migration by triggering write operations for all stored credentials after updating the environment variable.

This design ensures that old ciphertext encrypted with a previous key cannot be decrypted with the new key, providing a clear security boundary while allowing gradual data migration.

Protected Credential Types

Web Search Provider API Keys

The WebSearchProvider struct in internal/types/web_search_provider.go encrypts API keys before storage (lines 74-102). When loading configurations, the code attempts decryption and handles ErrEncryptedDataMissingKey by clearing the credential and logging the rotation event (lines 122-124).

Vector Store Connection Secrets

In internal/types/vectorstore.go (lines 121-169), the VectorStore type protects sensitive fields including Password and APIKey. The implementation mirrors the web search provider pattern, ensuring consistent encryption behavior across all credential storage.

Summary

  • Key Source: SYSTEM_AES_KEY environment variable must provide exactly 32 bytes for AES-256-GCM encryption.
  • Implementation: internal/utils/crypto.go provides GetAESKey(), EncryptAESGCM(), and DecryptAESGCM() for stateless encryption operations.
  • Storage Format: Encrypted values carry the enc:v1: prefix to distinguish them from plaintext.
  • Rotation Behavior: Decryption failures due to key rotation return ErrEncryptedDataMissingKey, causing the system to treat old credentials as unconfigured until re-encrypted with the new key.
  • Protected Data: Web search API keys and vector store credentials in internal/types/web_search_provider.go and internal/types/vectorstore.go.

Frequently Asked Questions

What happens if SYSTEM_AES_KEY is missing or incorrect?

If the SYSTEM_AES_KEY environment variable is unset or does not contain exactly 32 bytes, GetAESKey() returns nil and encryption is skipped for new writes. For existing encrypted data, DecryptAESGCM() returns ErrEncryptedDataMissingKey, causing the caller to log a warning and treat the credential as empty or unconfigured rather than exposing corrupted data.

How does key rotation work without losing data?

WeKnora handles key rotation transparently by reading the encryption key from the environment on every operation. When a new key is deployed, existing ciphertext fails decryption with the new key, triggering the ErrEncryptedDataMissingKey path. The next time an administrator saves the configuration, the system re-encrypts the credentials with the new key, effectively migrating the data without requiring downtime or manual database updates.

Which credential fields are encrypted in WeKnora?

The system encrypts sensitive fields in WebSearchProvider (web search API keys) and VectorStore (database passwords and API keys). These implementations in internal/types/web_search_provider.go and internal/types/vectorstore.go check for the presence of SYSTEM_AES_KEY and apply EncryptAESGCM() before persistence.

Is there a specific format required for the encryption key?

Yes, the SYSTEM_AES_KEY must be exactly 32 bytes to satisfy AES-256 requirements. The GetAESKey() function enforces this length validation in internal/utils/crypto.go, returning nil if the key is malformed to prevent weak or incorrect encryption operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →