Skip to content

Encrypting a Field

Encrypting a Field

Overview

This is the first stage that actually encrypts something. The goal is the minimum set of moving parts needed to do that: one field marked @Encrypt, one field marked @EncryptedData for the ciphertext to land in, a key source (CryptoKeyProvider), and something to do the actual encrypting (EncryptionServiceDelegate). We deliberately use a fake Base64 "encryption" delegate here so the plumbing is visible without any real cryptography or KMS setup getting in the way — real encryption comes in a later stage.

This stage comes as two projects:

  • starter/ — what you actually work in. It compiles and runs as-is, but PaymentCardEntity isn't annotated yet, so nothing gets encrypted (encryptedData stays null). Look for the // TODO comments — that's the exercise.
  • complete/ — the finished, working reference. Check it if you get stuck, or just to confirm your result.

Follow along

cd stages/02-Encrypt-a-Field/starter
Using an IDE instead? Just open stages/02-Encrypt-a-Field/starter as its own project.

The entity

mango4j-crypto works by annotating fields on a plain Java object. PaymentCardEntity has two fields that matter — this is what they look like once annotated:

@Encrypt
private transient String cardNumber;

@Encrypt marks cardNumber as confidential — note it must be transient, which the library enforces. Its plaintext value is never written to encryptedData or stored anywhere by the library itself; it just stays as a normal, in-memory value on the object you're holding.

@EncryptedData
private String encryptedData;

@EncryptedData marks where the resulting ciphertext goes. This is the field you'd actually persist (to a database, a file, wherever) — never cardNumber itself.

Your turn: in starter/src/main/java/ie/bitstep/mango/workshop/PaymentCardEntity.java, replace the two // TODO comments with @Encrypt and @EncryptedData on the fields above them.

Wiring up CryptoShield

CryptoShield is the object you call encrypt()/decrypt() on. Building one needs two things: something that supplies cryptographic keys (a CryptoKeyProvider), and something that does the actual encrypting (an EncryptionServiceDelegate). Both are already wired up for you in starter/ — this part isn't the exercise.

For this stage, InMemoryCryptoKeyProvider hands back one hardcoded key — a real application would look keys up from wherever it stores them. And rather than wiring up real encryption (KMS, a cipher, ...), we use the library's built-in Base64EncryptionService, which just Base64-encodes data — it exists specifically so you can learn and test the mechanics without any real cryptographic setup.

CryptoShield cryptoShield = new CryptoShield.Builder()
        .withCryptoKeyProvider(new InMemoryCryptoKeyProvider())
        .withAnnotatedEntities(List.of(PaymentCardEntity.class))
        .withEncryptionServiceDelegates(List.of(new Base64EncryptionService()))
        .build();

Encrypting and decrypting

PaymentCardEntity card = new PaymentCardEntity();
card.setCardNumber("5111111111111111");

cryptoShield.encrypt(card);
System.out.println("cardNumber (still in memory): " + card.getCardNumber());
System.out.println("encryptedData:                " + card.getEncryptedData());

encrypt() reads the @Encrypt field(s), builds the ciphertext, and writes it to the @EncryptedData field — cardNumber itself is left untouched, so you can keep using it in your code right after encrypting.

// Simulate loading the entity back from storage: only the ciphertext is known.
PaymentCardEntity loaded = new PaymentCardEntity();
loaded.setEncryptedData(card.getEncryptedData());

cryptoShield.decrypt(loaded);
System.out.println("decrypted cardNumber:         " + loaded.getCardNumber());

decrypt() does the reverse: given only the ciphertext, it reconstructs cardNumber. This is what "loading an entity back from storage" looks like in practice — you'd load a row containing only encryptedData, then call decrypt() to get the real value back.

Running it

Run starter/ now, before making any changes, and you'll see:

cardNumber (still in memory): 5111111111111111
encryptedData:                null
decrypted cardNumber:         null

Nothing's encrypted yet — that's expected, PaymentCardEntity isn't annotated. Once you've added the two annotations, run it again and you should see:

cardNumber (still in memory): 5111111111111111
encryptedData:                {"cryptoKeyId":"workshop-encryption-key","data":{"cipherText":"eyJjYXJkTnVtYmVyIjoiNTExMTExMTExMTExMTExMSJ9"}}
decrypted cardNumber:         5111111111111111

That's complete/ — if yours doesn't match, compare against it.