Repository: ably/ably-java
Issue: `IllegalStateException` in `Crypto` `CBCCipher`'s `decrypt` method

We've had this reported by a customer using our Flutter Plugin.

I've reported this issue in this repository, however, as the call stack falls entirely within code from this SDK.

```
Fatal Exception: java.lang.IllegalStateException
Cipher not initialized
javax.crypto.Cipher.checkCipherState (Cipher.java:1640)
javax.crypto.Cipher.doFinal (Cipher.java:2104)
io.ably.lib.util.Crypto$CBCCipher.decrypt (Crypto.java:255)
io.ably.lib.types.BaseMessage.decode (BaseMessage.java:134)
io.ably.lib.types.BaseMessage.decode (BaseMessage.java:80)
io.ably.lib.types.MessageSerializer$MessageBodyHandler.handleResponseBody (MessageSerializer.java:172)
io.ably.lib.types.MessageSerializer$MessageBodyHandler.handleResponseBody (MessageSerializer.java:157)
io.ably.lib.http.BasePaginatedQuery.handleResponse (BasePaginatedQuery.java:159)
io.ably.lib.http.BasePaginatedQuery.handleResponse (BasePaginatedQuery.java:25)
io.ably.lib.http.HttpCore.handleResponse (HttpCore.java:293)
io.ably.lib.http.HttpCore.httpExecute (HttpCore.java:270)
io.ably.lib.http.HttpCore.httpExecute (HttpCore.java:165)
io.ably.lib.http.HttpCore.httpExecuteWithRetry (HttpCore.java:83)
io.ably.lib.http.HttpScheduler$AsyncRequest.httpExecuteWithRetry (HttpScheduler.java:312)
io.ably.lib.http.HttpScheduler$AblyRequestWithFallback.run (HttpScheduler.java:205)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
java.lang.Thread.run (Thread.java:919)
```

They have tens of thousands of Android app users and thus far have just seen one instance of this in their reported crash logs.

See [this internal Slack message](https://ably-real-time.slack.com/archives/C012DA29VM0/p1643265205068800?thread_ts=1643101418.063300&cid=C012DA29VM0), which contains a link to the Firebase console for this Crashlytics entry, to which some the Ably internal team have access.

Other details:

* **Device**: Realme realme 3
* **OS**: Android 10
* **Device state**: background



┆Issue is synchronized with this [Jira Bug](https://ably.atlassian.net/browse/SDK-1518) by [Unito](https://www.unito.io)


Changed files:
lib/src/main/java/io/ably/lib/util/Crypto.java

diff --git a/lib/src/main/java/io/ably/lib/util/Crypto.java b/lib/src/main/java/io/ably/lib/util/Crypto.java
index 8fc19796..912b0a2c 100644
--- a/lib/src/main/java/io/ably/lib/util/Crypto.java
+++ b/lib/src/main/java/io/ably/lib/util/Crypto.java
@@ -171,10 +171,28 @@ public class Crypto {
     /**
      * Interface for a ChannelCipher instance that may be associated with a Channel.
      *
+     * The {@link #encrypt(byte[])} and {@link #decrypt(byte[])} methods must be implemented
+     * in a way that makes them safe to be called from any thread.
      */
     public interface ChannelCipher {
+        /**
+         * Encrypt data.
+         * @param plaintext Data to be encrypted.
+         * @return Encrypted data.
+         * @throws AblyException Encapsulates an exception thrown by the encryption implementation,
+         * typically from either {@link java.security} or {@link javax.crypto}
+         */
         byte[] encrypt(byte[] plaintext) throws AblyException;
+
+        /**
+         * Decrypt data.
+         * @param ciphertext Encrypted data.
+         * @return Unencrypted data.
+         * @throws AblyException Encapsulates an exception thrown by the encryption implementation,
+         * typically from either {@link java.security} or {@link javax.crypto}
+         */
         byte[] decrypt(byte[] ciphertext) throws AblyException;
+
         String getAlgorithm();
     }
 
@@ -232,7 +250,7 @@ public class Crypto {
         }
 
         @Override
-        public byte[] encrypt(byte[] plaintext) {
+        public synchronized byte[] encrypt(byte[] plaintext) {
             if(plaintext == null) return null;
             int plaintextLength = plaintext.length;
             int paddedLength = getPaddedLength(plaintextLength);
@@ -248,7 +266,7 @@ public class Crypto {
         }
 
         @Override
-        public byte[] decrypt(byte[] ciphertext) throws AblyException {
+        public synchronized byte[] decrypt(byte[] ciphertext) throws AblyException {
             if(ciphertext == null) return null;
             byte[] plaintext = null;
             try {