Friday, December 15, 2017

How to deal with exceptions

I recently had a discussion with a friend, who is a relatively junior but very smart software developer. She asked me about exception handling. The questions were pointing to a tips and tricks kind of path and there is definitely a list of them. But I am a believer on context and motivation behind the way we write software so I decided to write my thoughts on exceptions from such a perspective. 

Exceptions in programming (using Java as a stage for our story) are used to notify us that a problem occurred during the execution of our code. Exceptions are a special category of classes. What makes them special is that they extend the Exception class which in turn extends the Throwable class. Being implementations of Throwable allow us to "throw" them when necessary. So, how can an exception happen? Instances of exception classes are thrown either from the JVM or in a section of code using the throw statement. That is the how, but why?

I am sure that most of us cringe when we see exceptions occur, but they are a tool to our benefit. Before the inception of exceptions, special values or error codes were returned to let us know that an operation did not succeed. Forgetting (or being unaware) to check for such error codes, could lead to unpredictable behavior in our applications. So yay for exceptions!

There are 2 things that come to mind as I write the above. Exceptions are a bad event because when they are created we know a problem occurred. Exceptions are a helpful construct because they give us valuable information about what went wrong and allow us to behave in proper way for each situation.

Trying to distil the essence of this design issue: a method/request is triggered to do something but it might fail - how do we best notify the caller that it failed? How do we communicate information about what happened? How we help the client decide what to do next? The problem with using exceptions is that we “give up” and not just that; we do it in an “explosive” way and the clients/callers of our services have to handle the mess

So my first advice when it comes to exceptions, since they are a bad event - try to avoid them. In the sections of software under your control, implement design that makes difficult for errors to happen. You can use features of your language that support this behavior. I believe the most common exception in java is the NullPointerException and Optionals can help us avoid them. Let’s consider we want to retrieve an employee with a specified id:

public Optional<Employee> tryGetEmployee(String employeeId) {
    return Optional.ofNullable(employeeService.getEmployee(employeeId));
}

So much better now. But besides the features of our language, we can design our code in a way that makes it difficult for errors to occur. If we consider a method, which can only receive positive integers as an input, we can set our code up, so that it is extremely unlikely for clients to mistakenly pass invalid input. First we create a PositiveInteger class:
public class PositiveInteger {
  private Integer integerValue;
 
  public PositiveInteger(Integer inputValue) {
     if(inputValue <= 0) {
        throw new IllegalArgumentException("PositiveInteger instances can only be created out of positive integers");
     }
    
     this.integerValue = inputValue;
  }
 
  public Integer getIntegerValue() {
     return integerValue;
  }
}

Then for a method that can only use positive integer as an input:
public void setNumberOfWinners(PositiveInteger numberOfWinners) { … }

These are of course simple examples and I did argue that the heart of the issue is that occasionally problems occur and then we have to inform clients about what happened. So let’s say we retrieve a list of employees from an external back end system and things can go wrong. How to handle this?

We can set our response object to GetEmployeesResponse, which would look something like this:
public class GetEmployeesResponse {
  private Ok ok;
  private Error error;

   …
  class Ok {
    private List<Employee> employeeList;
    ...
  }

  class Error {
    private String errorMessage;
    ...
  }
}


But let’s be realists, you do not have control on every part of your codebase and you are not going to change everything either. Exceptions do and will happen, so let’s start with brief background information on them. 

As mentioned before, the Exception class extends the Throwable class. All exceptions are subclasses of the exception class. Exceptions can be categorized in checked and unchecked exceptions. That simply means that some exceptions, the checked ones, require from us to specify on compile time how the application will behave in case the exception occurs. The unchecked exceptions do not mandate compile time handling from us. To create such exceptions you extend the RuntimeException class which is a direct subclass of Exception. An old and common guideline when it comes to checked vs unchecked is that runtime exceptions are used to signal situations which the application usually cannot anticipate or recover from, while checked exceptions are situations that a well-written application should anticipate and recover from.

Well, I am an advocate of only using runtime exceptions. And if I use a library that has a method with checked exception, I create a wrapper method that turns it into a runtime. Why not checked exceptions then? Uncle Bob in his “Clean Code” book argues, they break the Open/Closed principle, since a change in the signature with a new throws declaration could have effects in many levels of our program calling the method.

Now, checked or unchecked, since exceptions are a construct to give us insights on what went wrong, they should be as specific and as informative as possible on what happened. So try to use standard exceptions, others will understand what happened easier. When seeing a NullPointerException, the reason is clear to anyone. If you make your own exceptions, make it sensible and specific. For example, a ValidationException lets me know a certain validation failed, an AgeValidationException points me to the specific validation failure. Being specific, allows both to diagnose easier what happened but also to specify a different behavior based on what happened (type of exception). That is the reason why you should always catch the most specific exception first! So here comes another common advice that instructs to not catch on “Exception”. It is a valid advice which I occasionally do not follow. In the boundaries of my api (let’s say the endpoints of my REST service) I always have generic catch Exception clauses. I do not want any surprises and something that I did not manage to predict or guard against in my code, to potentially reveal things to the outside world. 

Be descriptive but also provide exceptions according to the level of abstraction. Consider creating a hierarchy of exceptions that provide semantic information in different abstraction levels. If an exception is thrown from the lower levels of our program, such as a database related exception, it does not have to provide the details to the caller of our API. Catch the exception and throw a more abstract one, that simply informs callers that their attempted operation failed. This might seem like it comes against the common approach of “catch only when you can handle”, but it is not. Simply in this case our “handling” is the triggering of a new exception. In these cases make the whole history of the exception available from throw to throw, by passing the original exception to the constructor of the new exception.

The word “handle” was used many times. What does it mean? An exceptions is considered to be handled when it gets “caught” in our familiar catch clause. When an exception is thrown, first it will search for exception handling in the code from where it happens, if none is found it will go to the calling context of the method it is enclosed and so on until an exception handler is found or the program will terminate. 

One nice piece that I like from uncle Bob again, is that the try-catch-finally blocks define a scope within the program. And besides the lexical scope we should think of its conceptual scope, treat the try block as a transaction. What should we do if something goes wrong? How do we make sure to leave our program in a valid state? Do not ignore exceptions! I am guessing many hours of unhappiness for programmers were caused by silent exceptions. The catch and finally block are the place where you will do your cleaning up. Make sure you wait until you have all the information to handle the exception properly. This can be tied to the throw early-catch late principle. We throw early so we don’t make operations that we have to revert later because of the exception and we catch late in order to have all the information to correctly handle the exception. And by the way, when you catch exceptions, only log when you resolve them, else a single exception event would cause clutter in your logs. Finally, for exception handling, I personally prefer to create an error handling service that I can use in different parts of my code and take appropriate actions in regards to logging, rethrowing, cleaning resources, etc. It centralizes my error handling behavior, avoids code repetition and help me keep more high level perspective of how errors are handled in the application.

So now that we have enough context, paradoxes, rules and their exceptions, we could summarise:
  • Try to avoid exceptions. Use the language features and proper design in order to achieve it
  • Use runtime exceptions, wrap methods with checked exceptions and turn them into runtime
  • Try to use standard exceptions
  • Make your exceptions specific and descriptive
  • Catch the most specific exception first
  • Do not catch on Exception
  • But catch on Exception on the boundaries of your api. Have complete control on what comes out to the world
  • Create a hierarchy of exceptions that matches the layers and functionalities of your application
  • Throw exceptions at the proper abstraction level. Catch an exception and throw a higher level one as you move from layer to layer
  • Pass the complete history of exceptions when rethrowing by providing the exception in the constructor of the new one
  • Think of the try-catch-finally block as a transaction. Make sure you leave your program in a valid state when something goes wrong
  • Catch exception when you can handle it
  • Never have empty catch clauses
  • Log an exception when you handle it
  • Have a global exception handling service and have a strategy on how you handle errors

That was it! Go on and be exceptional!


Sunday, November 5, 2017

In encryption we trust! A tutorial

Many people view encryption as a complicated subject, something difficult to understand. And certain aspects of its implementation can be, but everyone can understand how it works on a higher level.

This is what I want to do with this article. Explain in simple terms how it works and then play around with some code.

Yes, in encryption we trust. What do I mean with trust? We trust that our messages are read only by authorized parties (confidentiality), they are not altered during transmission (integrity) and are indeed sent by those we believe they were sent (authentication).

Wikipedia provides a good definition for encryption: “is the process of encoding a message or information in such a way that only authorized parties can access it”.

So encryption is turning our message with the use of a key (cipher) to an incomprehensible one (ciphertext) which can only be turned back to the original from authorized parties.

There are two types of encryption schemes, symmetric and asymmetric key encryption.

In symmetric encryption the same key is used for encrypting and decrypting the message. Those we wish to access the message must have the key but none else, otherwise our messages are compromised.

Asymmetric key encryption is my interest here. Asymmetric key schemes, use two keys, a private and a public. These pairs of keys are special. They are special because they are generated using a category of algorithms called asymmetric algorithms. The actual algorithms are out of scope for this discussion, but later in the tutorial we will use RSA.

What you need to know now, is that these keys have the following properties. A message encrypted with the:
  1. public key can be decrypted only using the private key
  2. private key can be decrypted only using the public key

Seems simple enough right? So how is it used in practise? Let’s consider two friends, Alice and Bob. They have their own pairs of public and private keys and they want privacy in their chats. Each of them, openly provides their public key but takes good care hiding their private key.

When Alice wants to send a message only to be read from Bob, she uses Bob’s public key to encrypt the message. Then Bob and only him, can decrypt the message using his private key. That’s it.

That explains the use of the first property, but what about the second? Seems there is no reason to encrypt using our private key. Well, there is. How do we know that Alice was the one sent the message? If we can decrypt the message using Alice’s public key, we can be sure that Alice’s private key was used for the encryption, so it was indeed sent from Alice. Simply put:

The public key is used so people can send things only to you and the private key is used to prove your identity.

So we can have confidentiality using the public key and authenticity using the private. What about integrity? To achieve this, we use cryptographic hashing. A good cryptographic hash takes an input message and generates a message digest with the following properties:
  1. The message digest is easy to generate
  2. It is extremely difficult to calculate which input provided the hash
  3. It is extremely unlikely that two different inputs/messages would generate the same hash value

If we want to be sure that the message received was not compromised during transition, the hash value is sent along the encrypted message. In the receiving end we hash the decrypted message with the same algorithm and compare to make sure the hashes are an exact match. If they are, then we can be confident that the message was not altered.

These hashes or message digest have other uses as well. You see, sometimes Bob makes promises and then denies he ever did. We want to keep him in check. In fancy terms, it is called non-repudiation and prevents parties from being able to deny sending a message. Well known application of this, are digital signatures.

Before we move and have some fun with code, let me mention a couple more things.

  1. Asymmetric key algorithms  have actually two algorithms for different functionalities. One is of course for keys generation and the other functionality is for function evaluation. Function evaluation means taking an input (i.e. the message) and a key and result an encrypted or decrypted message, depending the input it got. So function evaluation is how messages are encrypted and decrypted using the public/private keys.
  2. Maybe you already thought, how do we know that a public key is actually related to Bob or Alice? What if it is someone pretending to be them? There is a standard that can help us with that. It is the X.509 which defines the format for public key certificates. These certificates are provided by Certification Authorities and usually contain:
    1. Subject, detailed description of the party (e.g. Alice)
    2. Validity range, for how long the certificate is valid
    3. Public key, which help us send encrypted messages to the party
    4. Certificate authority, the issuer of the certificate
  3. Hashing and encrypting are different things. An encrypted message is intended to eventually be turned back to the original message. A hashed message should not be  possible to be turned back to the original.

Now let’s use a tutorial to help all these sink in. We will allow three individuals Alice, Bob and Paul to communicate with Confidentiality, Integrity and Authentication (further will refer to them as CIA). The complete code is available on github.
The project has a couple of dependencies, as shown below:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tasosmartidis.tutorial.encryption</groupId>
    <artifactId>encryption-tutorial</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>encryption-tutorial</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <lombok.version>1.16.18</lombok.version>
        <commons-codec.version>1.11</commons-codec.version>
        <junit.version>4.12</junit.version>
        <bouncycastle.version>1.58</bouncycastle.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>${commons-codec.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>encryption-tutorial</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
We will start with the EncryptedMessage class, which will provide all the information we need to ensure CIA. The message will contain the actual encrypted message for confidentiality, a hash of the message to be used to ensure integrity and identification of the sender, raw and encrypted for authentication. We also provide a method to compromise the message payload, so we can test the validation against the digest (more on that later).
package com.tasosmartidis.tutorial.encryption.domain;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;

@AllArgsConstructor
@Getter
@EqualsAndHashCode
public class EncryptedMessage {
    private String encryptedMessagePayload;
    private String senderId;
    private String encryptedSenderId;
    private String messageDigest;

    // FOR DEMO PURPOSES ONLY!
    public void compromiseEncryptedMessagePayload(String message) {
        this.encryptedMessagePayload = message;
    }

    @Override
    public String toString() {
        return encryptedMessagePayload;
    }
}


Now let’s get to the encryption part. We will create a base encryptor class independent of the actual asymmetric algorithm and key length. It will create keys and cipher, have methods for encrypting and decrypting text as well as providing access to the keys. It looks something like this:
package com.tasosmartidis.tutorial.encryption.encryptor;

import com.tasosmartidis.tutorial.encryption.domain.EncryptorProperties;
import com.tasosmartidis.tutorial.encryption.exception.DecryptionException;
import com.tasosmartidis.tutorial.encryption.exception.EncryptionException;
import com.tasosmartidis.tutorial.encryption.exception.EncryptorInitializationException;
import com.tasosmartidis.tutorial.encryption.exception.UnauthorizedForDecryptionException;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.*;

public class BaseAsymmetricEncryptor {
    private final KeyPairGenerator keyPairGenerator;
    private final KeyPair keyPair;
    private final Cipher cipher;
    private final EncryptorProperties encryptorProperties;

    protected BaseAsymmetricEncryptor(EncryptorProperties encryptorProperties) {
        this.encryptorProperties = encryptorProperties;
        this.keyPairGenerator = generateKeyPair();
        this.keyPairGenerator.initialize(this.encryptorProperties.getKeyLength());
        this.keyPair = this.keyPairGenerator.generateKeyPair();
        this.cipher = createCipher(encryptorProperties);
    }

    protected PrivateKey getPrivateKey() {
        return this.keyPair.getPrivate();
    }

    public PublicKey getPublicKey() {
        return this.keyPair.getPublic();
    }

    protected String encryptText(String textToEncrypt, Key key) {
        try {
            this.cipher.init(Cipher.ENCRYPT_MODE, key);
            return Base64.encodeBase64String(cipher.doFinal(textToEncrypt.getBytes(StandardCharsets.UTF_8)));
        } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException ex) {
            throw new EncryptionException("Encryption of message failed", ex);
        }
    }

    protected String decryptText(String textToDecrypt, Key key) {
        try {
            this.cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(cipher.doFinal(Base64.decodeBase64(textToDecrypt)), StandardCharsets.UTF_8);
        }catch (InvalidKeyException | BadPaddingException ex){
            throw new UnauthorizedForDecryptionException("Not authorized to decrypt message", ex);
        } catch (IllegalBlockSizeException ex) {
            throw new DecryptionException("Decryption of message failed", ex);
        }
    }

    private Cipher createCipher(EncryptorProperties encryptorProperties) {
        try {
            return Cipher.getInstance(encryptorProperties.getAsymmetricAlgorithm());
        } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
            throw new EncryptorInitializationException("Creation of cipher failed", ex);
        }
    }

    private KeyPairGenerator generateKeyPair() {

        try {
            return KeyPairGenerator.getInstance(this.encryptorProperties.getAsymmetricAlgorithm());
        } catch (NoSuchAlgorithmException ex) {
            throw new EncryptorInitializationException("Creation of encryption keypair failed", ex);
        }
    }

}


There are a lot of exceptions we need to handle for implementing our functionality but since we are not going to do anything with them in case they happen, we will wrap them with semantically meaningful runtime exceptions. I am not going to show here the exception classes since they have simply a constructor. But you can check them out in the project in github under the com.tasosmartidis.tutorial.encryption.exception package.
 
Their actual use you will see in different parts of the code. The constructor of the BaseAsymmetricEncryptor takes an EncryptorProperites instance as an argument.
package com.tasosmartidis.tutorial.encryption.domain;

import lombok.AllArgsConstructor;


@AllArgsConstructor
public class EncryptorProperties {
    private final AsymmetricAlgorithm asymmetricAlgorithm;
    private final int keyLength;

    public String getAsymmetricAlgorithm() {
        return asymmetricAlgorithm.toString();
    }

    public int getKeyLength() {
        return keyLength;
    }
}


We will create an RSA based encryptor implementation. The code should speak for itself:
package com.tasosmartidis.tutorial.encryption.encryptor;

import com.tasosmartidis.tutorial.encryption.domain.AsymmetricAlgorithm;
import com.tasosmartidis.tutorial.encryption.domain.EncryptedMessage;
import com.tasosmartidis.tutorial.encryption.domain.EncryptorProperties;
import org.bouncycastle.jcajce.provider.digest.SHA3;
import org.bouncycastle.util.encoders.Hex;

import java.security.PublicKey;

public class RsaEncryptor extends BaseAsymmetricEncryptor {
    private static final int KEY_LENGTH = 2048;

    public RsaEncryptor() {
        super(new EncryptorProperties(AsymmetricAlgorithm.RSA, KEY_LENGTH));
    }

    public String encryptMessageForPublicKeyOwner(String message, PublicKey key) {
         return super.encryptText(message, key);
    }

    public String encryptMessageWithPrivateKey(String message) {
        return super.encryptText(message, super.getPrivateKey());
    }

    public String decryptReceivedMessage(EncryptedMessage message) {
        return super.decryptText(message.getEncryptedMessagePayload(), super.getPrivateKey());
    }

    public String decryptMessageFromOwnerOfPublicKey(String message, PublicKey publicKey) {
        return super.decryptText(message, publicKey);
    }

    public String hashMessage(String message) {
        SHA3.DigestSHA3 digestSHA3 = new SHA3.Digest512();
        byte[] messageDigest = digestSHA3.digest(message.getBytes());
        return Hex.toHexString(messageDigest);
    }
}


For our demo we will need actors, people that will exchange messages with each other. Each person will have a unique identity, a name and a list of trusted contacts that communicates with.
package com.tasosmartidis.tutorial.encryption.demo;

import com.tasosmartidis.tutorial.encryption.domain.EncryptedMessage;
import com.tasosmartidis.tutorial.encryption.message.RsaMessenger;
import lombok.EqualsAndHashCode;

import java.security.PublicKey;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@EqualsAndHashCode
public class Person {
    private final String id;
    private final String name;
    private final Set<Person> trustedContacts;
    private final RsaMessenger rsaMessenger;

    public Person(String name) {
        this.id = UUID.randomUUID().toString();
        this.name = name;
        this.trustedContacts = new HashSet<>();
        this.rsaMessenger = new RsaMessenger(this.trustedContacts, this.id);
    }

    public PublicKey getPublicKey() {
        return this.rsaMessenger.getPublicKey();
    }

    public String getName() {
        return name;
    }

    public String getId() {
        return id;
    }

    public void addTrustedContact(Person newContact) {
        if(trustedContacts.contains(newContact)) {
            return;
        }

        trustedContacts.add(newContact);
    }

    public EncryptedMessage sendEncryptedMessageToPerson(String message, Person person) {
        return this.rsaMessenger.encryptMessageForPerson(message, person);
    }

    public void readEncryptedMessage(EncryptedMessage encryptedMessage) {
        this.rsaMessenger.readEncryptedMessage(encryptedMessage);
    }

}


Next, let’s create an RsaMessanger class which will allow people to send encrypted messages using the RsaEncryptor. When sending an encrypted message we will provide all the necessary information to guarantee confidentiality, integrity and authentication. When reading we will decrypt the message, we will try to verify that it is send by a trusted contact and ensure that the message has not been compromised, or altered.
package com.tasosmartidis.tutorial.encryption.message;

import com.tasosmartidis.tutorial.encryption.demo.Person;
import com.tasosmartidis.tutorial.encryption.domain.EncryptedMessage;
import com.tasosmartidis.tutorial.encryption.encryptor.RsaEncryptor;
import com.tasosmartidis.tutorial.encryption.exception.PayloadAndDigestMismatchException;

import java.security.PublicKey;
import java.util.Optional;
import java.util.Set;

public class RsaMessenger {

    private final RsaEncryptor encryptionHandler;
    private final Set<Person> trustedContacts;
    private final String personId;

    public RsaMessenger(Set<Person> trustedContacts, String personId) {
        this.encryptionHandler = new RsaEncryptor();
        this.trustedContacts = trustedContacts;
        this.personId = personId;
    }

    public PublicKey getPublicKey() {
        return this.encryptionHandler.getPublicKey();
    }

    public EncryptedMessage encryptMessageForPerson(String message, Person person) {
        String encryptedMessage = this.encryptionHandler.encryptMessageForPublicKeyOwner(message, person.getPublicKey());
        String myEncryptedId = this.encryptionHandler.encryptMessageWithPrivateKey(this.personId);
        String hashedMessage = this.encryptionHandler.hashMessage(message);
        return new EncryptedMessage(encryptedMessage, this.personId, myEncryptedId, hashedMessage);
    }

    public void readEncryptedMessage(EncryptedMessage message) {
        String decryptedMessage = this.encryptionHandler.decryptReceivedMessage(message);
        Optional<Person> sender = tryIdentifyMessageSender(message.getSenderId());

        if(!decryptedMessageHashIsValid(decryptedMessage, message.getMessageDigest())) {
            throw new PayloadAndDigestMismatchException(
                    "Message digest sent does not match the one generated from the received message");
        }

        if(sender.isPresent() && senderSignatureIsValid(sender.get(), message.getEncryptedSenderId())) {
            System.out.println(sender.get().getName() +" send message: " + decryptedMessage);
        }else {
            System.out.println("Unknown source send message: " + decryptedMessage);
        }
    }

    private boolean senderSignatureIsValid(Person sender, String encryptedSenderId) {
        if(rawSenderIdMatchesDecryptedSenderId(sender, encryptedSenderId)) {
            return true;
        }

        return false;
    }

    private boolean rawSenderIdMatchesDecryptedSenderId(Person sender, String encryptedSenderId) {
        return sender.getId().equals(
                this.encryptionHandler.decryptMessageFromOwnerOfPublicKey(encryptedSenderId, sender.getPublicKey()));
    }

    private Optional<Person> tryIdentifyMessageSender(String id) {
        return this.trustedContacts.stream()
                .filter(contact -> contact.getId().equals(id))
                .findFirst();
    }

    private boolean decryptedMessageHashIsValid(String decryptedMessage, String hashedMessage) {
        String decryptedMessageHashed = this.encryptionHandler.hashMessage(decryptedMessage);
        if(decryptedMessageHashed.equals(hashedMessage)) {
            return true;
        }

        return false;
    }
}


Alright! It’s demo time!

We will create some tests to make sure everything works as expected. The scenarios we want to test are:
  1. When Alice (a trusted contact of Bob) sends an encrypted message to him, Bob can decrypt it and know it is from Alice. Also to ensure that the payload was not altered.
  2. The same message from Alice to Bob, is not available for Paul to decrypt and an UnauthorizedForDecryptionException will be thrown.
  3. When Paul (not known to Bob) sends an encrypted message, Bob will be able to read it but not be able to know who send it.
  4. Finally, when we compromise the payload of the encrypted message, the validation with its message digest will recognise it and throw an exception.
package com.tasosmartidis.tutorial.encryption;

import com.tasosmartidis.tutorial.encryption.demo.Person;
import com.tasosmartidis.tutorial.encryption.domain.EncryptedMessage;
import com.tasosmartidis.tutorial.encryption.exception.PayloadAndDigestMismatchException;
import com.tasosmartidis.tutorial.encryption.exception.UnauthorizedForDecryptionException;
import org.junit.Before;
import org.junit.Test;

public class DemoTest {

    private static final String ALICE_MESSAGE_TO_BOB = "Hello Bob";
    private static final String PAULS_MESSAGE_TO_BOB = "Hey there Bob";
    private final Person bob = new Person("Bob");
    private final Person alice = new Person("Alice");
    private final Person paul = new Person("Paul");
    private EncryptedMessage alicesEncryptedMessageToBob;
    private EncryptedMessage paulsEncryptedMessageToBob;

    @Before
    public void setup() {
        bob.addTrustedContact(alice);
        alicesEncryptedMessageToBob = alice.sendEncryptedMessageToPerson(ALICE_MESSAGE_TO_BOB, bob);
        paulsEncryptedMessageToBob = paul.sendEncryptedMessageToPerson(PAULS_MESSAGE_TO_BOB, bob);
    }

    @Test
    public void testBobCanReadAlicesMessage() {
        bob.readEncryptedMessage(alicesEncryptedMessageToBob);
    }

    @Test(expected = UnauthorizedForDecryptionException.class)
    public void testPaulCannotReadAlicesMessageToBob() {
        paul.readEncryptedMessage(alicesEncryptedMessageToBob);
    }

    @Test
    public void testBobCanReadPaulsMessage() {
        bob.readEncryptedMessage(paulsEncryptedMessageToBob);
    }

    @Test(expected = PayloadAndDigestMismatchException.class)
    public void testChangedMessageIdentifiedAndRejected() {
        EncryptedMessage slightlyDifferentMessage = alice.sendEncryptedMessageToPerson(ALICE_MESSAGE_TO_BOB + " ", bob);
        alicesEncryptedMessageToBob.compromiseEncryptedMessagePayload(slightlyDifferentMessage.getEncryptedMessagePayload());

        bob.readEncryptedMessage(alicesEncryptedMessageToBob);
    }
}


Running the test would produce the following result:












That was it! Thanks for reading, and again, you can find the code on github.