Skip to content

Cryptoshield

What's CryptoShield?

The central class applications interact with: cryptoShield.encrypt(entity) and cryptoShield.decrypt(entity). It's configured once, at startup, with a CryptoKeyProvider, the list of annotated entity classes it should know about, and the Encryption Service Delegates available to it.

Gaps

  • No protection against concurrent encrypt()/decrypt() calls on the same entity instance. Both methods are plain reflective field reads/writes, with no locking, no volatile, and no version or dirty check anywhere in the class. encrypt() never writes to the source (@Encrypt) fields, so a concurrent reader can never observe raw ciphertext there, but decrypt()'s field.set(...) on those fields is unconditional: it never checks whether the field already holds an unsaved in-memory value before overwriting it.

In practice this doesn't come up much in a typical Spring app: each request thread usually fetches its own entity instance (a repository call returns a fresh object per request, or per Hibernate session), so two threads racing on the same object is already outside the well-trodden path, not something the normal request/response flow produces on its own.

The gap is worth knowing about anyway, because encrypt()/decrypt() are purely POJO operations: field reads and writes on whatever object reference is handed to them, with no concept of a database, a transaction, or a Hibernate session. They don't know or care whether that reference came from a repository call, a cache, a queue message, or a background job holding onto one entity for a while. Leaning on "Spring/Hibernate handles concurrency for me" is really leaning on the request-per-thread, one-entity-per-session pattern those frameworks encourage, not on anything encrypt()/decrypt() themselves provide. The moment an entity instance is shared outside that pattern, a cached singleton, a long-lived object handed to a background job, something passed into an async task or a manually multi-threaded batch process, that assumption stops holding, and the race is real with no protection at any layer of the library. See Transient vs. Encrypted Blobs for the underlying mechanism and a runnable demo of the equivalent race. Avoiding it is the calling application's responsibility: don't share a mutable entity instance across threads without your own synchronization, wherever that sharing happens to occur.