Skip to content

feat(core): Add encryptWithKey and decryptWithKey to Cipher service#28608

Open
guillaumejacquart wants to merge 1 commit intomasterfrom
iam-492-cipher-service-accept-explicit-key-material
Open

feat(core): Add encryptWithKey and decryptWithKey to Cipher service#28608
guillaumejacquart wants to merge 1 commit intomasterfrom
iam-492-cipher-service-accept-explicit-key-material

Conversation

@guillaumejacquart
Copy link
Copy Markdown
Contributor

@guillaumejacquart guillaumejacquart commented Apr 17, 2026

Summary

Adds encryptWithKey(data, key, algorithm) and decryptWithKey(data, key, algorithm) to the Cipher service in packages/core. These let callers pass pre-resolved key material and an algorithm explicitly, instead of relying on the hardcoded InstanceSettings encryption key — unblocking call sites that resolve keys per row via KeyManagerService.

The legacy encrypt/decrypt methods are preserved and now delegate to the new ones, so there's a single CBC implementation and no regression for existing callers. The aes-256-gcm branch is stubbed and throws until the GCM implementation lands in a later ticket.

How to test: pnpm --filter n8n-core test cipher — 12 tests pass, including roundtrip, random-IV, wrong-key failure, GCM throws on both directions, and cross-compat between legacy encrypt and new decryptWithKey.

Related Linear tickets, Github issues, and Community forum posts

https://linear.app/n8n/issue/IAM-492

Review / Merge checklist

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)

🤖 PR Summary generated by AI

Adds explicit-key methods to the Cipher service so callers can pass
pre-resolved key material and an algorithm. Legacy encrypt/decrypt now
delegate to the new methods to keep a single CBC implementation. GCM
branch is stubbed and throws until the algorithm lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@guillaumejacquart guillaumejacquart marked this pull request as ready for review April 17, 2026 09:25
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Architecture diagram
sequenceDiagram
    participant Caller as Caller (e.g. KeyManager)
    participant Cipher as Cipher Service
    participant IS as InstanceSettings
    participant Crypto as Node.js Crypto

    Note over Caller, Crypto: Encryption Flow

    alt Legacy Path: encrypt(data, customKey?)
        Caller->>Cipher: encrypt(data, optionalKey)
        opt No customKey provided
            Cipher->>IS: Get encryptionKey (global)
            IS-->>Cipher: globalKey
        end
        Cipher->>Cipher: CHANGED: delegate to encryptWithKey()
    else NEW: encryptWithKey(data, key, algorithm)
        Caller->>Cipher: encryptWithKey(data, key, algorithm)
    end

    alt NEW: algorithm == 'aes-256-gcm'
        Cipher-->>Caller: Throw "GCM not yet implemented"
    else algorithm == 'aes-256-cbc'
        Cipher->>Crypto: randomBytes(8) (salt)
        Cipher->>Cipher: getKeyAndIv(salt, key)
        Cipher->>Crypto: createCipheriv('aes-256-cbc', derivedKey, iv)
        Cipher-->>Caller: Base64 string (Header + Salt + Ciphertext)
    end

    Note over Caller, Crypto: Decryption Flow

    alt Legacy Path: decrypt(data, customKey?)
        Caller->>Cipher: decrypt(data, optionalKey)
        opt No customKey provided
            Cipher->>IS: Get encryptionKey (global)
            IS-->>Cipher: globalKey
        end
        Cipher->>Cipher: CHANGED: delegate to decryptWithKey()
    else NEW: decryptWithKey(data, key, algorithm)
        Caller->>Cipher: decryptWithKey(data, key, algorithm)
    end

    alt NEW: algorithm == 'aes-256-gcm'
        Cipher-->>Caller: Throw "GCM not yet implemented"
    else algorithm == 'aes-256-cbc'
        Cipher->>Cipher: Extract salt from Base64 input
        Cipher->>Cipher: getKeyAndIv(salt, key)
        Cipher->>Crypto: createDecipheriv('aes-256-cbc', derivedKey, iv)
        Cipher-->>Caller: Plaintext string
    end
Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant