Repository: wultra/powerauth-crypto (java)

Issue:
Update encryption algorithms for crypto4
### Description

Add new encryption algorithms for crypto4:

- AES is now using always 256bit keys in CTR or GCM mode
  - CTR mode for AEAD, key encryption
  - GCM mode for local vault

Cover the new encryption algorithms by tests.

### Acceptance criteria

AEAD implemented as specified and covered by unit tests.

### Technical specification

Let's define a new primitive that provides authenticated encryption with associated data:

- Common constants:
  - `NONCE_LENGTH = 12` - The purpose of nonce is to provide initial IV value to AES/CTR.
  - `TAG_LENGTH = 32` - The tag length specifies how
  - `LABEL` - The kmac256 label: `String LABEL = "PA4MAC-AEAD"`

- `byte[] AEAD.seal(SecretKey key, byte[] keyContext, byte[] nonce, byte[] associatedData, byte[] plaintext)`
  - Function encrypts plaintext. Parameters:
    - `key` - secret symmetric key.
    - `keyContext` - a context related to the secret key.
    - `nonce` - nonce a part of IV for underlying cipher.
    - `associatedData` - data that is not encrypted but included into MAC calculation.
    - `plaintext` - data to be encrypted.
  - Returns:
    - byte array with encrypted data
  - Implementation:
    ```java
    // Prepare or validate nonce
    if (nonce == null) {
        // Create a new fresh nonce if no nonce provided.
        nonce = Generator.randomBytes(NONCE_LENGTH)
    } else if (nonce.length != NONCE_LENGTH) {
        throw new Exception();
    }
    // Derive encryption and authentication key
    byte[] KEY_ENC = KDF.derive(key, 20_001, keyContext);
    byte[] KEY_MAC = KDF.derive(key, 20_002, keyContext);
    // Prepare IV. Append 4 zero bytes to make IV 16 bytes long. So, the counter in CTR mode always starts at 0.
    // We expect that only 32 least significant bits in nonce is used as a counter, so the maximum length of plaintext is 2^32.
    byte[] IV = ByteUtils.concat(nonce, ByteUtils.zeroBytes(4));
    // Encrypt
    byte[] ENCRYPTED = AES.encrypt(plaintext, IV, KEY_ENC, "AES/CTR/NoPadding");
    // Calculate MAC
    byte[] MAC = Mac.kmac256(KEY_MAC, ByteUtils.concat(nonce, associatedData, ENCRYPTED), LABEL);
    // Prepare final ciphertext
    return ByteUtils.concat(nonce, MAC, ENCRYPTED);
    ```
    
- `byte[] AEAD.open(SecretKey key, byte[] keyContext, byte[] associatedData, byte[] ciphertext)`
  - Function decrypts ciphertext. Parameters:
    - `key` - secret symmetric key.
    - `keyContext` - a context related to the secret key.
    - `associatedData` - data that is included into MAC calculation.
    - `ciphertext` - data to be decrypted.
  - Returns:
    - byte array with decrypted data.
  - Implementation:
    ```java
    if (ciphertext.length < NONCE_LENGTH + TAG_LENGTH) {
        throw Exception();
    }
    // Extract components from ciphertext
    byte[] NONCE = ByteUtils.subarray(ciphertext, 0, NONCE_LENGTH);
    byte[] TAG = ByteUtils.subarray(ciphertext, NONCE_LENGTH, TAG_LENGTH);
    byte[] ENCRYPTED = ByteUtils.subarray(ciphertext, NONCE_LENGTH + TAG_LENGTH, ciphertext.length - NONCE_LENGTH - TAG_LENGTH);
    // Derive encryption and authentication key
    byte[] KEY_ENC = KDF.derive(key, 20_001, keyContext);
    byte[] KEY_MAC = KDF.derive(key, 20_002, keyContext);
    // Calculate mac
    byte[] MAC = Mac.kmac256(KEY_MAC, ByteUtils.concat(NONCE, associatedData, ENCRYPTED), LABEL);
    if (MAC != TAG) {
        throw new Exception();
    }
    // Prepare IV
    byte[] IV = ByteUtils.concat(NONCE, ByteUtils.zeroBytes(4));
    // Decrypt
    byte[] PLAINTEXT = AES.decrypt(ENCRYPTED, IV, KEY_ENC, "AES/CTR/NoPadding");
    // Success
    return PLAINTEXT;
    ```
    
- `byte[] AEAD.extractNonce(byte[] ciphertext)`
  - Extract nonce from the ciphertext. 
  - Implementation:
    ```java
    if (ciphertext.length < NONCE_LENGTH + TAG_LENGTH) {
        throw Exception();
    }
    return ByteUtils.subarray(ciphertext, 0, NONCE_LENGTH);
    ```
  - This function is useful on the server, when the server needs to validate in advance whether the nonce is unique.

### QA specification

Functionality is covered by unit tests.


Pull request description:
Fix #680: Update encryption algorithms for crypto4

Patch:
diff --git a/powerauth-java-crypto/pom.xml b/powerauth-java-crypto/pom.xml
index fd298770..e1f17ed3 100644
--- a/powerauth-java-crypto/pom.xml
+++ b/powerauth-java-crypto/pom.xml
@@ -59,7 +59,7 @@
 		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk18on</artifactId>
-			<version>1.79</version>
+			<version>1.80</version>
 		</dependency>
 		<dependency>
 			<groupId>org.projectlombok</groupId>
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/util/ByteUtils.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/util/ByteUtils.java
index a4e785fa..b3794a2d 100644
--- a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/util/ByteUtils.java
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/util/ByteUtils.java
@@ -106,6 +106,15 @@ public final class ByteUtils {
         return ByteBuffer.allocate(8).putLong(n).array();
     }
 
+    /**
+     * Generate zero bytes of given length.
+     * @param n Number of zero bytes.
+     * @return Byte aray.
+     */
+    public static byte[] zeroBytes(int n) {
+        return ByteBuffer.allocate(n).array();
+    }
+
     /**
      * Encode a String into a byte array.
      * @param s String to encode.
@@ -115,6 +124,31 @@ public final class ByteUtils {
         return s.getBytes(StandardCharsets.UTF_8);
     }
 
+    /**
+     * Return a subarray from an array.
+     * @param array Input array.
+     * @param start Subarray start index (included).
+     * @param length Subarray end index (excluded).
+     * @return Subarray.
+     */
+    public static byte[] subarray(byte[] array, int start, int length) {
+        if (array == null) {
+            throw new IllegalArgumentException("Input array is null");
+        }
+        if (start < 0) {
+            throw new IllegalArgumentException("Invalid start index");
+        }
+        if (length <= 0) {
+            throw new IllegalArgumentException("Invalid length");
+        }
+        if (length > array.length - start) {
+            throw new IllegalArgumentException("Invalid slicing of subarray");
+        }
+        byte[] result = new byte[length];
+        System.arraycopy(array, start, result, 0, length);
+        return result;
+    }
+
     /**
      * Returns the values from each provided array combined into a single array. For example, {@code
      * concat(new byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}.
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/Aead.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/Aead.java
new file mode 100644
index 00000000..2d94c6a5
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/Aead.java
@@ -0,0 +1,122 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2024 Wultra s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.getlime.security.powerauth.crypto.lib.v4;
+
+import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator;
+import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import io.getlime.security.powerauth.crypto.lib.util.AESEncryptionUtils;
+import io.getlime.security.powerauth.crypto.lib.util.ByteUtils;
+import io.getlime.security.powerauth.crypto.lib.util.SideChannelUtils;
+import io.getlime.security.powerauth.crypto.lib.v4.kdf.Kdf;
+import io.getlime.security.powerauth.crypto.lib.v4.kdf.Kmac;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+
+/**
+ * Implementation of authenticated encryption with associated data.
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class Aead {
+
+    private static final int NONCE_LENGTH = 12;
+    private static final int KEY_LENGTH = 32;
+    private static final int TAG_LENGTH = 32;
+
+    private static final byte[] CRYPTO4_AEAD_KMAC_CUSTOM_BYTES = "PA4MAC-AEAD".getBytes(StandardCharsets.UTF_8);
+    private static final long KEY_ENCRYPTION_INDEX = 20_001L;
+    private static final long KEY_MAC_INDEX = 20_002L;
+
+    private static final KeyGenerator KEY_GENERATOR = new KeyGenerator();
+    private static final AESEncryptionUtils aes = new AESEncryptionUtils();
+
+    /**
+     * Encrypt provided plaintext data using AEAD.
+     *
+     * @param key Secret key used for encryption.
+     * @param keyContext Context data used during key derivation.
+     * @param nonce A 12-byte array used as a unique nonce. If not specified, a random nonce is generated.
+     * @param associatedData Additional data used as additional input for MAC derivation.
+     * @param plaintext Plaintext data to encrypt.
+     * @return Byte array with nonce, MAC, and encrypted ciphertext.
+     * @throws CryptoProviderException Thrown in case the cryptographic provider could not be initialized.
+     * @throws GenericCryptoException Thrown in case of any cryptography error.
+     * @throws InvalidKeyException Thrown in case the secret key is invalid.
+     */
+    public static byte[] seal(SecretKey key, byte[] keyContext, byte[] nonce, byte[] associatedData, byte[] plaintext) throws CryptoProviderException, GenericCryptoException, InvalidKeyException {
+        if (nonce == null) {
+            nonce = KEY_GENERATOR.generateRandomBytes(NONCE_LENGTH);
+        } else if (nonce.length != NONCE_LENGTH) {
+            throw new GenericCryptoException("Invalid nonce length: " + nonce.length);
+        }
+        final SecretKey keyEncryption = Kdf.derive(key, KEY_ENCRYPTION_INDEX, KEY_LENGTH, keyContext);
+        final SecretKey keyMac = Kdf.derive(key, KEY_MAC_INDEX, TAG_LENGTH, keyContext);
+        final byte[] iv = ByteUtils.concat(nonce, ByteUtils.zeroBytes(4));
+        final byte[] encrypted = aes.encrypt(plaintext, iv, keyEncryption, "AES/CTR/NoPadding");
+        final byte[] mac = Kmac.kmac256(keyMac, ByteUtils.concat(nonce, associatedData, encrypted), TAG_LENGTH, CRYPTO4_AEAD_KMAC_CUSTOM_BYTES);
+        return ByteUtils.concat(nonce, mac, encrypted);
+    }
+
+    /**
+     * Decrypt provided ciphertext data using AEAD.
+     *
+     * @param key Secret key used for decryption.
+     * @param keyContext Context data used during key derivation.
+     * @param associatedData AAdditional data used as additional input for MAC derivation.
+     * @param ciphertext Byte array with nonce, MAC, and encrypted ciphertext.
+     * @return Byte array with decrypted data.
+     * @throws CryptoProviderException Thrown in case the cryptographic provider could not be initialized.
+     * @throws GenericCryptoException Thrown in case of any cryptography error.
+     * @throws InvalidKeyException Thrown in case the secret key is invalid.
+     */
+    public static byte[] open(SecretKey key, byte[] keyContext, byte[] associatedData, byte[] ciphertext) throws CryptoProviderException, GenericCryptoException, InvalidKeyException {
+        if (ciphertext.length < NONCE_LENGTH + TAG_LENGTH) {
+            throw new GenericCryptoException("Invalid ciphertext length: " + ciphertext.length);
+        }
+        final byte[] nonce = ByteUtils.subarray(ciphertext, 0, NONCE_LENGTH);
+        final byte[] tag = ByteUtils.subarray(ciphertext, NONCE_LENGTH, TAG_LENGTH);
+        final byte[] encrypted = ByteUtils.subarray(ciphertext, NONCE_LENGTH + TAG_LENGTH, ciphertext.length - NONCE_LENGTH - TAG_LENGTH);
+        final SecretKey keyEncryption = Kdf.derive(key, KEY_ENCRYPTION_INDEX, KEY_LENGTH, keyContext);
+        final SecretKey keyMac = Kdf.derive(key, KEY_MAC_INDEX, TAG_LENGTH, keyContext);
+        byte[] mac = Kmac.kmac256(keyMac, ByteUtils.concat(nonce, associatedData, encrypted), TAG_LENGTH, CRYPTO4_AEAD_KMAC_CUSTOM_BYTES);
+        if (!SideChannelUtils.constantTimeAreEqual(mac, tag)) {
+            throw new GenericCryptoException("Invalid MAC");
+        }
+        byte[] iv = ByteUtils.concat(nonce, ByteUtils.zeroBytes(4));
+        return aes.decrypt(encrypted, iv, keyEncryption, "AES/CTR/NoPadding");
+    }
+
+    /**
+     * Extract the nonce from provided ciphertext.
+     *
+     * @param ciphertext Byte array containing the nonce, MAC, and encrypted ciphertext.
+     * @return Byte array with extracted nonce.
+     * @throws GenericCryptoException Thrown in case the input ciphertext in invalid.
+     */
+    public static byte[] extractNonce(byte[] ciphertext) throws GenericCryptoException {
+        if (ciphertext.length < NONCE_LENGTH + TAG_LENGTH) {
+            throw new GenericCryptoException("Invalid ciphertext length: " + ciphertext.length);
+        }
+        return ByteUtils.subarray(ciphertext, 0, NONCE_LENGTH);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kdf.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kdf.java
index a0ba7c8e..ab038035 100644
--- a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kdf.java
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kdf.java
@@ -20,8 +20,6 @@ package io.getlime.security.powerauth.crypto.lib.v4.kdf;
 import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import io.getlime.security.powerauth.crypto.lib.util.ByteUtils;
 import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor;
-import org.bouncycastle.crypto.macs.KMAC;
-import org.bouncycastle.crypto.params.KeyParameter;
 
 import javax.crypto.SecretKey;
 import java.nio.charset.StandardCharsets;
@@ -35,7 +33,6 @@ public class Kdf {
 
     private static final byte[] CRYPTO4_KDF_CUSTOM_BYTES = "PA4KDF".getBytes(StandardCharsets.UTF_8);
     private static final byte[] CRYPTO4_PBKDF_CUSTOM_BYTES = "PA4PBKDF".getBytes(StandardCharsets.UTF_8);
-    private static final int KMAC_BIT_LENGTH = 256;
 
     private static final KeyConvertor KEY_CONVERTOR = new KeyConvertor();
 
@@ -63,7 +60,7 @@ public class Kdf {
         } else {
             data = indexBytes;
         }
-        final byte[] output = kmac256(key, data, outLength, CRYPTO4_KDF_CUSTOM_BYTES);
+        final byte[] output = Kmac.kmac256(key, data, outLength, CRYPTO4_KDF_CUSTOM_BYTES);
         return KEY_CONVERTOR.convertBytesToSharedSecretKey(output);
     }
 
@@ -88,40 +85,10 @@ public class Kdf {
         }
         final byte[] passwordBytes = ByteUtils.encodeString(password);
         final SecretKey key = KEY_CONVERTOR.convertBytesToSharedSecretKey(passwordBytes);
-        final byte[] output = kmac256(key, salt, outLength, CRYPTO4_PBKDF_CUSTOM_BYTES);
+        final byte[] output = Kmac.kmac256(key, salt, outLength, CRYPTO4_PBKDF_CUSTOM_BYTES);
         return KEY_CONVERTOR.convertBytesToSharedSecretKey(output);
     }
 
-    /**
-     * Compute the KMAC256 of the given data using provided secret key, output length and optional customization string.
-     *
-     * @param key          The secret key, must be a valid {@link SecretKey} with a 256-bit key length.
-     * @param data         The input data used for the KMAC.
-     * @param outLength    The length of generated output bytes.
-     * @param customString An optional customization string, use null value for no customization.
-     * @return KMAC256 output byte array.
-     * @throws GenericCryptoException Thrown in case of any cryptography error.
-     */
-    static byte[] kmac256(SecretKey key, byte[] data, int outLength, byte[] customString) throws GenericCryptoException {
-        if (key == null) {
-            throw new GenericCryptoException("Missing secret key for KDF");
-        }
-        if (data == null) {
-            throw new GenericCryptoException("Missing data for KDF");
-        }
-        if (outLength <= 0) {
-            throw new GenericCryptoException("Invalid output length for KDF");
-        }
-        final KMAC kmac = new KMAC(KMAC_BIT_LENGTH, customString);
-        final byte[] keyBytes = key.getEncoded();
-        if (keyBytes == null) {
-            throw new GenericCryptoException("Secret key encoding is null");
-        }
-        kmac.init(new KeyParameter(keyBytes));
-        kmac.update(data, 0, data.length);
-        final byte[] output = new byte[outLength];
-        kmac.doFinal(output, 0, outLength);
-        return output;
-    }
+
 
 }
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kmac.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kmac.java
new file mode 100644
index 00000000..2082559f
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/Kmac.java
@@ -0,0 +1,67 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2024 Wultra s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.getlime.security.powerauth.crypto.lib.v4.kdf;
+
+import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import org.bouncycastle.crypto.macs.KMAC;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Keccak Message Authentication Code.
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class Kmac {
+
+    private static final int KMAC_BIT_LENGTH = 256;
+
+    /**
+     * Compute the KMAC256 of the given data using provided secret key, output length and optional customization string.
+     *
+     * @param key          The secret key, must be a valid {@link SecretKey} with a 256-bit key length.
+     * @param data         The input data used for the KMAC.
+     * @param outLength    The length of generated output bytes.
+     * @param customString An optional customization string, use null value for no customization.
+     * @return KMAC256 output byte array.
+     * @throws GenericCryptoException Thrown in case of any cryptography error.
+     */
+    public static byte[] kmac256(SecretKey key, byte[] data, int outLength, byte[] customString) throws GenericCryptoException {
+        if (key == null) {
+            throw new GenericCryptoException("Missing secret key for KMAC");
+        }
+        if (data == null) {
+            throw new GenericCryptoException("Missing data for KDF");
+        }
+        if (outLength <= 0) {
+            throw new GenericCryptoException("Invalid output length for KDF");
+        }
+        final KMAC kmac = new KMAC(KMAC_BIT_LENGTH, customString);
+        final byte[] keyBytes = key.getEncoded();
+        if (keyBytes == null) {
+            throw new GenericCryptoException("Secret key encoding is null");
+        }
+        kmac.init(new KeyParameter(keyBytes));
+        kmac.update(data, 0, data.length);
+        final byte[] output = new byte[outLength];
+        kmac.doFinal(output, 0, outLength);
+        return output;
+    }
+
+}


Test patch:
diff --git a/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/AeadTest.java b/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/AeadTest.java
new file mode 100644
index 00000000..0bc7afda
--- /dev/null
+++ b/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/AeadTest.java
@@ -0,0 +1,110 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2024 Wultra s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.getlime.security.powerauth.crypto.lib.v4;
+
+import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator;
+import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.crypto.SecretKey;
+
+import java.security.Security;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test for AEAD encryption and decryption.
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+class AeadTest {
+
+    private static final KeyGenerator KEY_GENERATOR = new KeyGenerator();
+    private SecretKey key;
+    private byte[] plaintext;
+    private byte[] keyContext;
+    private byte[] associatedData;
+
+    static {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    @BeforeEach
+    void setUp() throws Exception {
+        key = KEY_GENERATOR.generateRandomSecretKey();
+        plaintext = KEY_GENERATOR.generateRandomBytes(128);
+        keyContext =  KEY_GENERATOR.generateRandomBytes(16);
+        associatedData = KEY_GENERATOR.generateRandomBytes(16);
+    }
+
+    @Test
+    void testAeadSuccess() throws Exception {
+        byte[] nonce = new KeyGenerator().generateRandomBytes(12);
+        byte[] ciphertext = Aead.seal(key, keyContext, nonce, associatedData, plaintext);
+        assertNotNull(ciphertext);
+        byte[] decrypted = Aead.open(key, keyContext, associatedData, ciphertext);
+        assertArrayEquals(plaintext, decrypted);
+    }
+
+   @Test
+    void testAeadSuccessGeneratedNonce() throws Exception {
+        byte[] ciphertext = Aead.seal(key, keyContext, null, associatedData, plaintext);
+        assertNotNull(ciphertext);
+        byte[] decrypted = Aead.open(key, keyContext, associatedData, ciphertext);
+        assertArrayEquals(plaintext, decrypted);
+    }
+
+    @Test
+    void testInvalidMac() throws Exception {
+        byte[] nonce = new KeyGenerator().generateRandomBytes(12);
+        byte[] ciphertext = Aead.seal(key, keyContext, nonce, associatedData, plaintext);
+        ciphertext[0] += 1;
+        assertThrows(GenericCryptoException.class, () -> Aead.open(key, keyContext, associatedData, ciphertext));
+    }
+
+    @Test
+    void testInvalidNonceLength() {
+        byte[] invalidNonce = new byte[11];
+        assertThrows(GenericCryptoException.class, () -> Aead.seal(key, keyContext, invalidNonce, associatedData, plaintext), "Invalid nonce length should throw exception");
+    }
+
+    @Test
+    void testExtractNonce() throws Exception {
+        byte[] nonce = new KeyGenerator().generateRandomBytes(12);
+        byte[] ciphertext = Aead.seal(key, keyContext, nonce, associatedData, plaintext);
+        byte[] extractedNonce = Aead.extractNonce(ciphertext);
+        assertArrayEquals(nonce, extractedNonce);
+    }
+
+    @Test
+    void testInvalidKey() throws Exception {
+        SecretKey wrongKey = KEY_GENERATOR.generateRandomSecretKey();
+        byte[] ciphertext = Aead.seal(key, keyContext, null, associatedData, plaintext);
+        assertThrows(GenericCryptoException.class, () -> Aead.open(wrongKey, keyContext, associatedData, ciphertext), "Decryption with an invalid key should throw exception");
+    }
+
+    @Test
+    void testNullAssociatedData() throws Exception {
+        byte[] ciphertext = Aead.seal(key, keyContext, null, null, plaintext);
+        byte[] decrypted = Aead.open(key, keyContext, null, ciphertext);
+        assertArrayEquals(plaintext, decrypted);
+    }
+
+}
\ No newline at end of file
diff --git a/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/KdfTest.java b/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/KdfTest.java
index 717cd787..325310af 100644
--- a/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/KdfTest.java
+++ b/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/lib/v4/kdf/KdfTest.java
@@ -42,7 +42,7 @@ class KdfTest {
         int outputLength = 64;
         byte[] expectedOutput = Hex.decode("20C570C31346F703C9AC36C61C03CB64C3970D0CFC787E9B79599D273A68D2F7F69D4CC3DE9D104A351689F27CF6F5951F0103F33F4F24871024D9C27773A8DD");
         SecretKey secretKey = new SecretKeySpec(key, "AES");
-        byte[] output = Kdf.kmac256(secretKey, data, outputLength, customString);
+        byte[] output = Kmac.kmac256(secretKey, data, outputLength, customString);
         assertArrayEquals(expectedOutput, output, "KMAC256 output does not match expected output.");
     }
 
@@ -70,7 +70,7 @@ class KdfTest {
                 "589D27CF5E15369CBBFF8B9A4C2EB178" +
                 "00855D0235FF635DA82533EC6B759B69");
         SecretKey secretKey = new SecretKeySpec(key, "AES");
-        byte[] output = Kdf.kmac256(secretKey, data, outputLength, customString);
+        byte[] output = Kmac.kmac256(secretKey, data, outputLength, customString);
         assertArrayEquals(expectedOutput, output, "KMAC256 output does not match expected output.");
     }
 
@@ -98,7 +98,7 @@ class KdfTest {
                 "70FBACFDE50033AEA585F1A2708510C3" +
                 "2D07880801BD182898FE476876FC8965");
         SecretKey secretKey = new SecretKeySpec(key, "AES");
-        byte[] output = Kdf.kmac256(secretKey, data, outputLength, customString);
+        byte[] output = Kmac.kmac256(secretKey, data, outputLength, customString);
         assertArrayEquals(expectedOutput, output, "KMAC256 output does not match expected output.");
     }