Skip to content

Support registering custom key managers for user-defined primitives (or provide a migration path) #67

Open

Description

@ivankotev-taulia

Is your feature request related to a problem?

We use Tink as a key management framework for RSA encryption and decryption. We define two custom primitive interfaces with their corresponding custom KeyManager and PrimitiveWrapper implementations. The actual cryptography is standard RSA JCE.

We are currently pinned to an older version of Tink because upgrading to the latest is blocked by a series of breaking changes that removed support for custom primitive registration:

  • PrimitiveSet and PrimitiveWrapper removed from public API
  • Registry.registerKeyManager() rejects custom primitives not in ALLOWED_PRIMITIVES

We had to implement these because Tink doesn't seem to offer a built-in primitive for direct RSA encryption.

What sort of feature would you like to see?

I think any of the following would unblock us:

  1. A supported mechanism for registering custom primitives - Issue Using a custom primitive not supported by Tink #56 seems to mention this was on the radar, but it looks like it has no timeline?
  2. A stable internal API surface for custom primitive registration - if full public support isn't planned, documenting which internal APIs are safe for external use would help us plan upgrades.
  3. A built-in RSA encryption primitive - RSA-OAEP or RSA-PKCS1 encryption (not signing) for small payloads.

Have you considered any alternative solutions?

We can potentially use Tink's internal API. This may work but it is obviously fragile - future internal API changes will require further adaptation.

Thanks in advance for any feedback!

Activity

  1. self-assigned this
    on Feb 23, 2026
  2. tholenst commented on Feb 23, 2026

    @tholenst
    Contributor

    Yes, sorry about that; we weren't aware how many people use their own primitive.

    (Side note: first note direct RSA encryption is tricky to integrate with Tink, because it doesn't do authentication a priori. Hence, if you do key rotation you need to be careful (as just trying all keys on decrypt might decrypt with a different key).)

    Next, let me assure you that we are working on that. In fact, just 4 days ago I added https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/Configuration.java .

    I think at this point (hence when we make the next release) it becomes possible for you to work around things with it.

    1. You need to define your own Key class. For this, you simply define a subclass of https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/Key.java.

    2. To create new keys, you kind of cannot really use generateNew, but you use the addEntry API of KeysetHandle.Builder. This looks something like this:

        KeysetHandle keysetHandle =
            KeysetHandle.newBuilder()
                .addEntry(KeysetHandle.importKey(key0).withFixedId(42).makePrimary())
                .addEntry(KeysetHandle.importKey(key1).withFixedId(43))
                .build();
    

    It's possible that this complains because Tink doesn't know how to serialize/parse these keys, but if so please let me know and I'll get rid of that.

    1. You probably need to be able to parse and serialize keys. Unfortunately, if you use the proto serialization this isn't yet fully working; but it's possible to do it by hand. Note that you can basically just copy paste the protos Tink uses into your own project and then manually create the proto. This would work fine, we anyhow cannot change them. We plan to improve this as well, but this will require some time (I hope to get this done this year though).

    2. You can simply implement a Configuration now. Basically, this will look similar to https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/mac/MacConfigurationV1.java (this is from today, I think, and AFAICT you can simply reimplement the exact same strategy).

    Let me know if I'm missing something or if you need more help; also let me know whether that all makes sense and if Tink can make this easier for you.

  3. ivankotev-taulia commented on Feb 25, 2026

    @ivankotev-taulia
    Author

    Thank you @tholenst for the detailed answer. Great to see improved extensibility coming to Tink!

    I have a couple of follow-up questions based on the shared information:

    1. KeysetHandle.getPrimitive(Configuration, Class) - does this method exist or how do we pass a custom Configuration to getPrimitive()? I see Configuration.createPrimitive(KeysetHandleInterface, Class) but I'm not sure how to bridge from our current keysetHandle.getPrimitive(...) call to using the Configuration instead.

    2. Proto serialization - we already have our own .proto definitions for RSA keys and use Tink's CleartextKeysetHandle + JsonKeysetWriter/Reader for keyset serialization. Will defining custom Key subclasses still work with the existing JSON keyset serialization or do we would need some custom serialization?

    3. Key generation - With the KeysetHandle.Builder and importKey approach, the current understanding is that we'd generate the RSA key pair ourselves (via KeyPairGenerator?) and wrap it in our custom Key class before importing. Is that the intended flow?

    Again - thanks in advance!

  4. tholenst commented on Feb 26, 2026

    @tholenst
    Contributor

    For 1: KeysetHandle.getPrimitive taking a configuration already exists: https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/KeysetHandle.java#L1281 -- you can simply switch to that one.

    For 2: Not yet. I do plan to add a similar argument to CleartextKeysetHandle and JsonKeysetWriter/Reader (though probably you will have to use TinkJsonProtoKeysetFormat (https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/TinkJsonProtoKeysetFormat.java) -- but this switch typically simplifies existing code.

    For 3: Basically you have to implement your own Key class - this is a Tink key as defined in https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/Key.java.

    This hence doesn't need any abstraction, you can simply create the object. To understand what's going on here for your migration, it might be helpful to know the following. In the KeyManager world, Tink did not really expose objects encoding individual keys as a public API, except if one would implement KeyManagers or PrimitiveWrappers, like you did. Otherwise, it would be best to not use them.

    However, that didn't really work since many users still needed access, and the API provided by Protobuf is limited. Hence, we decide to make APIs which we can completely customized.

    In parallel, we learned that the primitive wrappers should do as little work as possible. Before this refactoring, PrimitiveWrappers would e.g. handle the prefixing in AEAD or signatures. We learned that things become easier to understand and better if this is associated with the individual keys. One reason is that the primitive wrappers can be changed almost never, but key types can, so best is to put as much logic as possible into the individual key types.

    This leads to the introduction of the crypto.tink.Key class, which in proto terms contains everything which was previously also done in the wrapper for this key, so it also should contain all the OutputPrefixType and typically the ID. You can see the details in https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/internal/LegacyProtoKey.java -- this is the object we create instead of a proto if needed (we can consider making this public if it helps you but you can also just copy paste it into our own code).

    So if you use e.g. this LegacyProtoKey, you can basically create new keys exactly how you created them in the key manager. After this, you can add them to the KeysetHandle.

    -- Question: do you ever use anything except RAW output prefix? If you only use RAW, one can make things somewhat simpler.

  5. ivankotev-taulia commented on Feb 26, 2026

    @ivankotev-taulia
    Author

    Thanks for the detailed explanation!

    To answer your question: we actually use TINK output prefix. The 5-byte prefix is essential for us as we rely on it for multi-key decryption routing during key rotation.

  6. tholenst commented on Mar 2, 2026

    @tholenst
    Contributor

    I also wanted to add: note that I do plan to improve this situation in this year significantly. So if you have time, you might also wait some.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions