You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support registering custom key managers for user-defined primitives (or provide a migration path) #67
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.
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.
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.
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).)
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.
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).
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:
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.
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?
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?
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.
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.
Activity
tholenst commentedon Feb 23, 2026
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.
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.
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:
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.
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).
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.
ivankotev-taulia commentedon Feb 25, 2026
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:
KeysetHandle.getPrimitive(Configuration, Class)- does this method exist or how do we pass a customConfigurationtogetPrimitive()? I seeConfiguration.createPrimitive(KeysetHandleInterface, Class)but I'm not sure how to bridge from our currentkeysetHandle.getPrimitive(...)call to using theConfigurationinstead.Proto serialization - we already have our own
.protodefinitions for RSA keys and use Tink'sCleartextKeysetHandle+JsonKeysetWriter/Readerfor keyset serialization. Will defining customKeysubclasses still work with the existing JSON keyset serialization or do we would need some custom serialization?Key generation - With the
KeysetHandle.BuilderandimportKeyapproach, the current understanding is that we'd generate the RSA key pair ourselves (viaKeyPairGenerator?) and wrap it in our customKeyclass before importing. Is that the intended flow?Again - thanks in advance!
tholenst commentedon Feb 26, 2026
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.
ivankotev-taulia commentedon Feb 26, 2026
Thanks for the detailed explanation!
To answer your question: we actually use
TINKoutput prefix. The 5-byte prefix is essential for us as we rely on it for multi-key decryption routing during key rotation.tholenst commentedon Mar 2, 2026
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.