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
: CreatesKey
objects from correspondingKeyFormat
objects.PrimitiveFromKey
: Creation of cryptographic objects from keys.PrimitiveFromKeyset
: Creation of the cryptographic object from a keyset.KeySerializer
andKeyFormatSerializer
: 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.