Why dependency injection?

Until now, we avoided explaining how users create an object implementing a primitive from a key. For example, how does a user create an Aead object from a AesGcmKey object?

Similarly, Tink allows serializing Key and KeyFormat objects using the Protocol Buffer library. So far, we did not explain how Tink implements this.

The simplest design for this would be to add functions to the key classes. For example, the abstract base class Key would get a function byte[] serializeKey(), and the class AeadKey would get a function Aead createAead() (for parsing a serialized key most likely another object has to be added, but we ignore this for now).

Tink chooses not to do this in order to avoid the dependencies introduced by it. For example, such a design would mean that the AesGcmKey class has a dependency on the Protocol Buffer library, and some users might want to use Tink without incuring such a dependency, and instead take care of the serialization themselves. Furthermore, it fixes one implementation of the Aead object from the AesGcmKey object, but many such implementations are possible: for example it could be that some machines have a hardware implementation of AES-GCM which can be used, or that some users want to switch providers in Java.

Instead, we prefer to keep the Key classes as lightweight as possible. They should only do the job they are designed for: provide all the data sufficient to specify a cryptographic object completely.

Different design patterns are available to avoid such dependencies. The solution adoped in Tink is to provide a registry, where users can inject objects which create primitives or serialize and deserialize keys.

Registry design

For example, Tink provides the following abstract class PrimitiveFromKey, which one needs to implement to specify how to create e.g. an Aead object from a AesGcmKey object (we omit some Java specific details regarding Java generics from this class).

public abstract class PrimitiveFromKey<PrimitiveT, KeyT extends Key> {
  protected abstract PrimitiveT getPrimitive(KeyT key);

The registry then allows registering and using such objects:

public class Registry {
  public <PrimitiveT, KeyT extends Key> void registerPrimitiveFromKey(
      PrimitiveFromKey<PrimitiveT, KeyT> primitiveFactory);

  /* This API is restricted to SingleKeyUsers -- see below. The
   * clazz parameter is the Java specific way of telling what object type
   * we want. */
  public <P> P getPrimitiveForSingleKey(Key key, Class<P> clazz);

The function getPrimitiveForSingleKey is restricted and only accessible to SingleKeyUser -- see Access Control. It will look up the previously registered PrimitiveFromKey object for the given key type and the given primitive type, and then call its member getPrimitive.

Then, Tink has a standard implementation of PrimitiveFromKey to create Aead objects from AesGcmKey objects.

public class AesGcmPrimitiveFromKeyJce {
  private AesGcmPrimitiveFromKeyJce() {...}
  /* Creates a new PrimitiveFromKey objects and registers it in the given registry. */
  public static void register(Registry registry);
  public static void registerGlobally();

The constructor of this implementation is private; only the two static functions which register it with a registry are public. Not adding the PrimitiveFromKey in the public API ensures that every user goes through a registry. This in turn makes sure that later it is possible to e.g. add monitoring hooks in the registry.

Registrable objects

Tink allows registering the following types of objects:

  • KeyFactory: Creates Key objects from corresponding KeyFormat objects.
  • PrimitiveFromKey: Creation of cryptographic objects from keys.
  • PrimitiveFromKeyset: Creation of the cryptographic object from a keyset.
  • KeySerializer and KeyFormatSerializer: These objects allow serialization of Key and KeyFormat objects.

Global registry

It is useful to allow multiple registries. Tink provides one global registry, accessible via the static function Registry.getGlobalRegistry(). The intention is that users creating a binary will use this registry, and Tink uses this per default. Library writers which base their library on Tink should instead use their own instance of a registry. This allows them to choose their own dependencies and not pollute the global registry.