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

> Learn how WeKnora uses AES-256-GCM at-rest encryption with key rotation to protect credentials. Discover its secure key management and re-encryption process.

- Repository: [Tencent/WeKnora](https://github.com/tencent/WeKnora)
- Tags: how-to-guide
- Published: 2026-09-12

---

**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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/internal/types/web_search_provider.go) (lines 98-102) and [`internal/types/vectorstore.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/vectorstore.go) (lines 162-169), the code follows this pattern:

```go
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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/internal/types/web_search_provider.go) and [`internal/types/vectorstore.go`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/internal/types/web_search_provider.go) and [`internal/types/vectorstore.go`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/internal/utils/crypto.go), returning `nil` if the key is malformed to prevent weak or incorrect encryption operations.