Repository: wultra/powerauth-crypto
Pull request #757 (2025-05-26 12:05:29)

Fix #756: Separate v4 server and client classes
This pull requests separates v3 and v4 classes for client and server usage and cleans up the API to be used from client and server.


Patch:
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java
index 34174f41..401d12eb 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java
@@ -38,7 +38,15 @@ import java.util.Arrays;
 
 /**
  * Class implementing a cryptography used on the client side in order
- * to complete the PowerAuth Client activation.
+ * to complete the PowerAuth Client activation (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak, petr@wultra.com
  * @author Roman Strobl, roman.strobl@wultra.com
@@ -48,11 +56,6 @@ public class PowerAuthClientActivation {
     private static final SignatureUtils SIGNATURE_UTILS = new SignatureUtils();
     private static final KeyGenerator KEY_GENERATOR = new KeyGenerator();
 
-    /**
-     * Custom bytes for MAC for counter data.
-     */
-    private static final byte[] KMAC_STATUS_CUSTOM_BYTES = CustomString.PA4MAC_STATUS.value().getBytes(StandardCharsets.UTF_8);
-
     /**
      * Generate a device related activation key pair.
      *
@@ -181,7 +184,6 @@ public class PowerAuthClientActivation {
      *     <li>3.1</li>
      *     <li>3.2</li>
      *     <li>3.3</li>
-     *     <li>4.0</li>
      * </ul>
      *
      * @param statusBlob Activation status blob.
@@ -194,7 +196,7 @@ public class PowerAuthClientActivation {
 
         // check if the prefix is OK
         int prefix = buffer.getInt(0);
-        statusInfo.setValid(prefix == ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE_V3 || prefix == ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE_V4);
+        statusInfo.setValid(prefix == ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE_V3);
 
         // fetch the activation status byte
         statusInfo.setActivationStatus(buffer.get(4));
@@ -227,28 +229,6 @@ public class PowerAuthClientActivation {
         return statusInfo;
     }
 
-    /**
-     * Verify MAC for activation status.
-     *
-     * <p><b>PowerAuth protocol versions:</b>
-     * <ul>
-     *     <li>4.0</li>
-     * </ul>
-     *
-     * @param statusData Activation status data.
-     * @param expectedStatusMac Expected status MAC.
-     * @param keyCtrStatusMac Key for calculating MAC for activation data.
-     * @param protocolVersion Protocol version.
-     * @return Activation status MAC.
-     * @throws GenericCryptoException In case of a cryptography error.
-     */
-    public boolean verifyStatusMac(byte[] statusData, byte[] expectedStatusMac, SecretKey keyCtrStatusMac, ProtocolVersion protocolVersion) throws GenericCryptoException {
-        if (protocolVersion.getMajorVersion() < 4) {
-            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
-        }
-        return SideChannelUtils.constantTimeAreEqual(expectedStatusMac, Kmac.kmac256(keyCtrStatusMac, statusData, KMAC_STATUS_CUSTOM_BYTES));
-    }
-
     /**
      * Verify whether client's value of hash based counter is equal to the value received from the server. The value
      * received from the server is already hashed, so the function has to calculate hash from the client's counter
@@ -273,6 +253,9 @@ public class PowerAuthClientActivation {
      */
     public boolean verifyHashForHashBasedCounter(byte[] receivedCtrDataHash, byte[] expectedCtrData, SecretKey transportKey, ProtocolVersion protocolVersion)
             throws CryptoProviderException, InvalidKeyException, GenericCryptoException {
+        if (protocolVersion.getMajorVersion() != 3) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
         return new HashBasedCounterUtils().verifyHashForHashBasedCounter(receivedCtrDataHash, expectedCtrData, transportKey, protocolVersion);
     }
 
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/authentication/PowerAuthClientAuthentication.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/authentication/PowerAuthClientAuthentication.java
index c7aa6832..60922fc6 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/authentication/PowerAuthClientAuthentication.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/authentication/PowerAuthClientAuthentication.java
@@ -25,7 +25,7 @@ import javax.crypto.SecretKey;
 import java.util.List;
 
 /**
- * Class implementing client-side authentication related processes.
+ * Class implementing client-side authentication related processes (V3).
  *
  * <p><b>PowerAuth protocol versions:</b>
  * <ul>
@@ -35,12 +35,9 @@ import java.util.List;
  *     <li>3.3</li>
  * </ul>
  *
- * @deprecated For version 3.x, use the {@link com.wultra.security.powerauth.crypto.lib.util.AuthenticationCodeLegacyUtils} directly. For version 4.0 or higher, use the {@link com.wultra.security.powerauth.crypto.lib.util.AuthenticationCodeUtils} directly.
- *
  * @author Petr Dvorak, petr@wultra.com
  *
  */
-@Deprecated
 public class PowerAuthClientAuthentication {
 
     private final AuthenticationCodeLegacyUtils authenticationCodeUtils = new AuthenticationCodeLegacyUtils();
@@ -68,8 +65,8 @@ public class PowerAuthClientAuthentication {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    public String authenticateCodeForData(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration authenticationCodeConfiguration) throws GenericCryptoException, CryptoProviderException {
-        return authenticationCodeUtils.computePowerAuthCode(data, factorKeys, ctrData, authenticationCodeConfiguration);
+    public String computeAuthCode(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration authenticationCodeConfiguration) throws GenericCryptoException, CryptoProviderException {
+        return authenticationCodeUtils.computeAuthCode(data, factorKeys, ctrData, authenticationCodeConfiguration);
     }
 
 }
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/keyfactory/PowerAuthClientKeyFactory.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/keyfactory/PowerAuthClientKeyFactory.java
index 18a1b6aa..ad361915 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/keyfactory/PowerAuthClientKeyFactory.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/keyfactory/PowerAuthClientKeyFactory.java
@@ -30,10 +30,17 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * Class implementing client side key factory for keys related to PowerAuth processes.
+ * Class implementing client side key factory for keys related to PowerAuth processes (V3).
  *
- * @author Petr Dvorak, petr@wultra.com
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
+ * @author Petr Dvorak, petr@wultra.com
  */
 public class PowerAuthClientKeyFactory {
 
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/token/ClientTokenGenerator.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/token/ClientTokenGenerator.java
index c4f9bcde..7d92786f 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/token/ClientTokenGenerator.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/token/ClientTokenGenerator.java
@@ -16,13 +16,22 @@
  */
 package com.wultra.security.powerauth.crypto.client.token;
 
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
 import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
 import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import com.wultra.security.powerauth.crypto.lib.util.TokenUtils;
 
 
 /**
- * Class that simplifies working with tokens on the client side.
+ * Class that simplifies working with tokens on the client side (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak, petr@wultra.com
  */
@@ -62,6 +71,10 @@ public class ClientTokenGenerator {
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
     public byte[] computeTokenDigest(byte[] nonce, byte[] timestamp, String version, byte[] tokenSecret) throws GenericCryptoException, CryptoProviderException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 3) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
         return tokenUtils.computeTokenDigest(nonce, timestamp, version, tokenSecret);
     }
 
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/activation/PowerAuthClientActivation.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/activation/PowerAuthClientActivation.java
new file mode 100644
index 00000000..ac6a36e2
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/activation/PowerAuthClientActivation.java
@@ -0,0 +1,274 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2025 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 com.wultra.security.powerauth.crypto.client.v4.activation;
+
+import com.wultra.security.powerauth.crypto.lib.enums.EcCurve;
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
+import com.wultra.security.powerauth.crypto.lib.generator.KeyGenerator;
+import com.wultra.security.powerauth.crypto.lib.model.ActivationStatusBlobInfo;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.*;
+import com.wultra.security.powerauth.crypto.lib.v4.PqcDsa;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.CustomString;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.Kmac;
+
+import javax.crypto.SecretKey;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Arrays;
+
+/**
+ * Class implementing a cryptography used on the client side in order
+ * to complete the PowerAuth Client activation (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class PowerAuthClientActivation {
+
+    private static final SignatureUtils SIGNATURE_UTILS = new SignatureUtils();
+    private static final KeyGenerator KEY_GENERATOR = new KeyGenerator();
+    private static final PqcDsa PQC_DSA = new PqcDsa();
+
+    /**
+     * Custom bytes for MAC for counter data.
+     */
+    private static final byte[] KMAC_STATUS_CUSTOM_BYTES = CustomString.PA4MAC_STATUS.value().getBytes(StandardCharsets.UTF_8);
+
+    /**
+     * Generate a device related EC key pair.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @return A new device EC key pair.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public KeyPair generateDeviceEcKeyPair() throws CryptoProviderException {
+        return KEY_GENERATOR.generateKeyPair(EcCurve.P384);
+    }
+
+    /**
+     * Generate a device related PQC DSA key pair.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @return A new device PQC key pair.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public KeyPair generateDevicePqcKeyPair() throws CryptoProviderException {
+        return PQC_DSA.generateKeyPair();
+    }
+
+    /**
+     * Verify the EC activation code signature.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param activationCode Activation code.
+     * @param signature Activation data EC signature.
+     * @param masterPublicKey Master Public Key.
+     * @return Returns true if the signature matches activation data, false otherwise.
+     * @throws InvalidKeyException If provided master public key is invalid.
+     * @throws GenericCryptoException In case signature computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public boolean verifyActivationCodeEcSignature(String activationCode, byte[] signature, PublicKey masterPublicKey) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
+        final byte[] bytes = activationCode.getBytes(StandardCharsets.UTF_8);
+        return SIGNATURE_UTILS.validateECDSASignature(EcCurve.P384, bytes, signature, masterPublicKey);
+    }
+
+    /**
+     * Verify the PQC activation code signature.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param activationCode Activation code.
+     * @param signature Activation data PQC signature.
+     * @param masterPublicKey Master Public Key.
+     * @return Returns true if the signature matches activation data, false otherwise.
+     * @throws InvalidKeyException If provided master public key is invalid.
+     * @throws GenericCryptoException In case signature computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public boolean verifyActivationCodePqcSignature(String activationCode, byte[] signature, PublicKey masterPublicKey) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
+        final byte[] bytes = activationCode.getBytes(StandardCharsets.UTF_8);
+        return PQC_DSA.verify(masterPublicKey, bytes, signature);
+    }
+
+    /**
+     * Compute a fingerprint for the version 4 activation for algorithm EC_P384. The fingerprint can be used for visual validation of exchanged device public key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param devicePublicKey Device public key.
+     * @param serverPublicKey Server public key.
+     * @param activationId Activation ID.
+     * @return Fingerprint of the public key.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case fingerprint could not be calculated.
+     */
+    public String computeActivationEcFingerprint(PublicKey devicePublicKey, PublicKey serverPublicKey, String activationId) throws GenericCryptoException, CryptoProviderException {
+        // TODO - support for crypto4
+        return "";
+    }
+
+    /**
+     * Compute a fingerprint for the version 4 activation for algorithm EC_P384_ML_L3. The fingerprint can be used for visual validation of exchanged device public key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param ecDevicePublicKey EC device public key.
+     * @param pqcDevicePublicKey PQC device public key.
+     * @param ecServerPublicKey EC server public key.
+     * @param pqcServerPublicKey PQC server public key.
+     * @param activationId Activation ID.
+     * @return Fingerprint of the public keys.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case fingerprint could not be calculated.
+     */
+    public String computeActivationHybridFingerprint(PublicKey ecDevicePublicKey, PublicKey pqcDevicePublicKey, PublicKey ecServerPublicKey, PublicKey pqcServerPublicKey, String activationId) throws GenericCryptoException, CryptoProviderException {
+        // TODO - support for crypto4
+        return "";
+    }
+
+    /**
+     * Returns an activation status from the activation blob as described in PowerAuth Specification.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param statusBlob Activation status blob.
+     * @return Status information from the status blob.
+     */
+    public ActivationStatusBlobInfo getStatusFromBlob(byte[] statusBlob) {
+        // Prepare objects to read status info into
+        ActivationStatusBlobInfo statusInfo = new ActivationStatusBlobInfo();
+        ByteBuffer buffer = ByteBuffer.wrap(statusBlob);
+
+        // check if the prefix is OK
+        int prefix = buffer.getInt(0);
+        statusInfo.setValid(prefix == ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE_V4);
+
+        // fetch the activation status byte
+        statusInfo.setActivationStatus(buffer.get(4));
+
+        // fetch the current version status byte
+        statusInfo.setCurrentVersion(buffer.get(5));
+
+        // fetch the upgrade version status byte
+        statusInfo.setUpgradeVersion(buffer.get(6));
+
+        // fetch the status flags
+        statusInfo.setStatusFlags(buffer.get(7));
+
+        // fetch ctr byte value
+        statusInfo.setCtrByte(buffer.get(12));
+
+        // fetch the failed attempt count
+        statusInfo.setFailedAttempts(buffer.get(13));
+
+        // fetch the max allowed failed attempt count
+        statusInfo.setMaxFailedAttempts(buffer.get(14));
+
+        // fetch counter's look ahead window value
+        statusInfo.setCtrLookAhead(buffer.get(15));
+
+        // extract counter data from second half of status blob
+        byte[] ctrData = Arrays.copyOfRange(statusBlob, 16, statusBlob.length);
+        statusInfo.setCtrDataHash(ctrData);
+
+        return statusInfo;
+    }
+
+    /**
+     * Verify MAC for activation status.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param statusData Activation status data.
+     * @param expectedStatusMac Expected status MAC.
+     * @param keyCtrStatusMac Key for calculating MAC for activation data.
+     * @param protocolVersion Protocol version.
+     * @return Activation status MAC.
+     * @throws GenericCryptoException In case of a cryptography error.
+     */
+    public boolean verifyStatusMac(byte[] statusData, byte[] expectedStatusMac, SecretKey keyCtrStatusMac, ProtocolVersion protocolVersion) throws GenericCryptoException {
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return SideChannelUtils.constantTimeAreEqual(expectedStatusMac, Kmac.kmac256(keyCtrStatusMac, statusData, KMAC_STATUS_CUSTOM_BYTES));
+    }
+
+    /**
+     * Verify whether client's value of hash based counter is equal to the value received from the server. The value
+     * received from the server is already hashed, so the function has to calculate hash from the client's counter
+     * and then compare both values.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param receivedCtrDataHash Value received from the server, containing hash, calculated from hash based counter.
+     * @param expectedCtrData Expected hash based counter.
+     * @param transportKey Transport key.
+     * @param protocolVersion Protocol version.
+     * @return {@code true} in case that received hash equals to hash calculated from counter data.
+     * @throws InvalidKeyException When invalid key is provided.
+     * @throws GenericCryptoException In case key derivation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public boolean verifyHashForHashBasedCounter(byte[] receivedCtrDataHash, byte[] expectedCtrData, SecretKey transportKey, ProtocolVersion protocolVersion)
+            throws CryptoProviderException, InvalidKeyException, GenericCryptoException {
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return new HashBasedCounterUtils().verifyHashForHashBasedCounter(receivedCtrDataHash, expectedCtrData, transportKey, protocolVersion);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/authentication/PowerAuthClientAuthentication.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/authentication/PowerAuthClientAuthentication.java
new file mode 100644
index 00000000..cc2d8b80
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/authentication/PowerAuthClientAuthentication.java
@@ -0,0 +1,66 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2025 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 com.wultra.security.powerauth.crypto.client.v4.authentication;
+
+import com.wultra.security.powerauth.crypto.lib.config.AuthenticationCodeConfiguration;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.AuthenticationCodeUtils;
+
+import javax.crypto.SecretKey;
+import java.util.List;
+
+/**
+ * Class implementing client-side authentication related processes (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ *
+ */
+public class PowerAuthClientAuthentication {
+
+    private static final AuthenticationCodeUtils AUTHENTICATION_CODE_UTILS = new AuthenticationCodeUtils();
+
+    /**
+     * Compute a PowerAuth authentication code for given data, factor keys and
+     * counter. Authentication code keys are symmetric keys deduced using
+     * private device key KEY_DEVICE_PRIVATE and server public key
+     * KEY_SERVER_PUBLIC, and then using KDF function with proper index. See
+     * PowerAuth protocol specification for details.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param data Data to be signed.
+     * @param factorKeys Factor keys.
+     * @param ctrData Hash based counter / index of the derived key KEY_DERIVED.
+     * @param authenticationCodeConfiguration Format and parameters of authentication code to calculate.
+     * @return PowerAuth authentication code for given data.
+     * @throws GenericCryptoException In case authentication code computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public String computeAuthCode(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration authenticationCodeConfiguration) throws GenericCryptoException, CryptoProviderException {
+        return AUTHENTICATION_CODE_UTILS.computeAuthCode(data, factorKeys, ctrData, authenticationCodeConfiguration);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/keyfactory/PowerAuthClientKeyFactory.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/keyfactory/PowerAuthClientKeyFactory.java
new file mode 100644
index 00000000..a09b661a
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/keyfactory/PowerAuthClientKeyFactory.java
@@ -0,0 +1,224 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2025 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 com.wultra.security.powerauth.crypto.client.v4.keyfactory;
+
+import com.wultra.security.powerauth.crypto.lib.enums.PowerAuthCodeType;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.KeyFactory;
+
+import javax.crypto.SecretKey;
+import java.security.InvalidKeyException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class implementing client side key factory for keys related to PowerAuth processes (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class PowerAuthClientKeyFactory {
+
+    /**
+     * Return a correct list of keys for given factor key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param powerAuthCodeType Requested type of a factor.
+     * @param possessionFactorKey Possession factor related factor key.
+     * @param knowledgeFactorKey Knowledge factor related factor key.
+     * @param biometryFactorKey Biometry factor related factor key.
+     * @return List with correct keys
+     */
+    public List<SecretKey> keysForAuthenticationCodeType(PowerAuthCodeType powerAuthCodeType, SecretKey possessionFactorKey, SecretKey knowledgeFactorKey, SecretKey biometryFactorKey) {
+        final List<SecretKey> factorKeys = new ArrayList<>();
+        if (powerAuthCodeType == null) {
+            return factorKeys;
+        }
+        if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION)) {
+            factorKeys.add(possessionFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.KNOWLEDGE)) {
+            factorKeys.add(knowledgeFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.BIOMETRY)) {
+            factorKeys.add(biometryFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE)) {
+            factorKeys.add(possessionFactorKey);
+            factorKeys.add(knowledgeFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_BIOMETRY)) {
+            factorKeys.add(possessionFactorKey);
+            factorKeys.add(biometryFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE_BIOMETRY)) {
+            factorKeys.add(possessionFactorKey);
+            factorKeys.add(knowledgeFactorKey);
+            factorKeys.add(biometryFactorKey);
+        }
+        return factorKeys;
+    }
+
+    /**
+     * Generate a list with authentication code keys for given authentication code type and activation secret key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param powerAuthCodeType Requested authentication code type.
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return List with keys constructed from master secret that are needed to
+     *         get requested authentication code type.
+     * @throws InvalidKeyException In case master secret key is invalid.
+     * @throws GenericCryptoException In case key derivation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public List<SecretKey> keysForAuthenticationCodeType(PowerAuthCodeType powerAuthCodeType, SecretKey keyActivationSecret) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
+        final List<SecretKey> factorKeys = new ArrayList<>();
+        if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION)) {
+            final SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.KNOWLEDGE)) {
+            final SecretKey factorKey = generateKnowledgeFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.BIOMETRY)) {
+            final SecretKey factorKey = generateBiometryFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE)) {
+            SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateKnowledgeFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_BIOMETRY)) {
+            SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateBiometryFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE_BIOMETRY)) {
+            SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateKnowledgeFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateBiometryFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        }
+        return factorKeys;
+    }
+
+    /**
+     * Generate a factor key KEY_FACTOR_BIOMETRY from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyAuthenticationCodeBiometry(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of factor key KEY_FACTOR_BIOMETRY.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateBiometryFactorKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyAuthenticationCodeBiometry(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_FACTOR_KNOWLEDGE from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyAuthenticationCodeKnowledge(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of factor key KEY_FACTOR_KNOWLEDGE.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKnowledgeFactorKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyAuthenticationCodeKnowledge(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_FACTOR_POSSESSION from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyAuthenticationCodePossession(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of factor key KEY_FACTOR_POSSESSION.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generatePossessionFactorKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyAuthenticationCodePossession(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_GET_APP_TEMP_KEY from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacGetActTempKey(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_GET_APP_TEMP_KEY.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacGetAppTempKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacGetAppTempKey(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_GET_ACT_TEMP_KEY from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacGetActTempKey(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_GET_ACT_TEMP_KEY.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacGetActTempKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacGetActTempKey(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_STATUS from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacStatus(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_STATUS.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacStatus(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacStatus(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_CTR_DATA from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacCtrData(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_CTR_DATA.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacCtrData(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacCtrData(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_E2EE_SHARED_INFO2 from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyE2eeSharedInfo2(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_E2EE_SHARED_INFO2.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateSharedInfo2Key(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyE2eeSharedInfo2(keyActivationSecret);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/token/ClientTokenGenerator.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/token/ClientTokenGenerator.java
new file mode 100644
index 00000000..f3448477
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/token/ClientTokenGenerator.java
@@ -0,0 +1,77 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2025 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 com.wultra.security.powerauth.crypto.client.v4.token;
+
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.TokenUtils;
+
+/**
+ * Class that simplifies working with tokens on the client side (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class ClientTokenGenerator {
+
+    private static final TokenUtils TOKEN_UTILS = new TokenUtils();
+
+    /**
+     * Generate random token nonce, 16 random bytes.
+     *
+     * @return Random token nonce.
+     * @throws CryptoProviderException In case key cryptography provider is incorrectly initialized.
+     */
+    public byte[] generateTokenNonce() throws CryptoProviderException {
+        return TOKEN_UTILS.generateTokenNonce();
+    }
+
+    /**
+     * Helper method to get current timestamp for the purpose of token timestamping, encoded as bytes (from string
+     * representation).
+     *
+     * @return Current timestamp in milliseconds.
+     */
+    public byte[] generateTokenTimestamp() {
+        return TOKEN_UTILS.generateTokenTimestamp();
+    }
+
+    /**
+     * Compute the digest of provided token information using given token secret.
+     *
+     * @param nonce Token nonce, 16 random bytes.
+     * @param timestamp Token timestamp, Unix timestamp format encoded as bytes (from string representation).
+     * @param version Protocol version.
+     * @param tokenSecret Token secret, 16 random bytes.
+     * @return Token digest computed using provided data bytes with given token secret.
+     * @throws GenericCryptoException In case digest computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public byte[] computeTokenDigest(byte[] nonce, byte[] timestamp, String version, byte[] tokenSecret) throws GenericCryptoException, CryptoProviderException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return TOKEN_UTILS.computeTokenDigest(nonce, timestamp, version, tokenSecret);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/vault/PowerAuthClientVault.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/vault/PowerAuthClientVault.java
new file mode 100644
index 00000000..49d38228
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/v4/vault/PowerAuthClientVault.java
@@ -0,0 +1,29 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2025 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 com.wultra.security.powerauth.crypto.client.v4.vault;
+
+/**
+ * Class implementing client-side processes related to PowerAuth secure vault (V4).
+ *
+ * @author Roman Strobl, roman.strobl
+ *
+ */
+public class PowerAuthClientVault {
+
+    // TODO - support for crypto4
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/vault/PowerAuthClientVault.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/vault/PowerAuthClientVault.java
index bf96cd7d..eae36868 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/vault/PowerAuthClientVault.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/client/vault/PowerAuthClientVault.java
@@ -28,10 +28,17 @@ import java.security.PrivateKey;
 import java.security.spec.InvalidKeySpecException;
 
 /**
- * Class implementing client-side processes related to PowerAuth secure vault.
+ * Class implementing client-side processes related to PowerAuth secure vault (V3).
  *
- * @author Petr Dvorak
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
+ * @author Petr Dvorak, petr@wultra.com
  */
 public class PowerAuthClientVault {
 
@@ -45,6 +52,7 @@ public class PowerAuthClientVault {
      *     <li>3.0</li>
      *     <li>3.1</li>
      *     <li>3.2</li>
+     *     <li>3.3</li>
      * </ul>
      *
      * @param cVaultEncryptionKey Encrypted vault encryption key KEY_ENCRYPTION_VAULT.
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/model/ActivationVersion.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/model/ActivationVersion.java
index d09bfda0..8ce6967f 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/model/ActivationVersion.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/model/ActivationVersion.java
@@ -24,12 +24,13 @@ package com.wultra.security.powerauth.crypto.lib.model;
 public enum ActivationVersion {
 
     /**
-     * Version 2.
+     * Version 3.
      */
-    VERSION_2,
+    VERSION_3,
 
     /**
-     * Version 3.
+     * Version 4.
      */
-    VERSION_3
+    VERSION_4
+
 }
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/AuthenticationCodeLegacyUtils.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/AuthenticationCodeLegacyUtils.java
index cc4eac9e..148f97bc 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/AuthenticationCodeLegacyUtils.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/AuthenticationCodeLegacyUtils.java
@@ -46,7 +46,7 @@ public class AuthenticationCodeLegacyUtils {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    private String computePowerAuthDecimalCode(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, Integer length) throws GenericCryptoException, CryptoProviderException {
+    private String computeAuthDecimalCode(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, Integer length) throws GenericCryptoException, CryptoProviderException {
         // Determine the length of the authentication code component, validate length
         final int decimalLength;
         if (length != null) {
@@ -63,7 +63,7 @@ public class AuthenticationCodeLegacyUtils {
         // Prepare holder for authentication code components
         final String[] stringComponents = new String[factorKeys.size()];
         // Compute authentication code components
-        final List<byte[]> authCodeComponents = computePowerAuthCodeComponents(data, factorKeys, ctrData);
+        final List<byte[]> authCodeComponents = computeAuthCodeComponents(data, factorKeys, ctrData);
         // Convert byte authentication code into decimal authentication code
         for (int i = 0; i < authCodeComponents.size(); i++) {
             final byte[] component = authCodeComponents.get(i);
@@ -93,11 +93,11 @@ public class AuthenticationCodeLegacyUtils {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    private String computePowerAuthBase64Code(byte[] data, List<SecretKey> factorKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
+    private String computeAuthBase64Code(byte[] data, List<SecretKey> factorKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
         // Prepare array of bytes for a complete authentication code
         final byte[] authenticationCodeBytes = new byte[factorKeys.size() * PowerAuthConfiguration.AUTH_CODE_BINARY_LENGTH];
         // Compute authentication code components
-        final List<byte[]> authenticationCodeComponents = computePowerAuthCodeComponents(data, factorKeys, ctrData);
+        final List<byte[]> authenticationCodeComponents = computeAuthCodeComponents(data, factorKeys, ctrData);
         // Convert authentication code components into one Base64 encoded string
         for (int i = 0; i < authenticationCodeComponents.size(); i++) {
             final byte[] component = authenticationCodeComponents.get(i);
@@ -129,7 +129,7 @@ public class AuthenticationCodeLegacyUtils {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    private List<byte[]> computePowerAuthCodeComponents(byte[] data, List<SecretKey> factorKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
+    private List<byte[]> computeAuthCodeComponents(byte[] data, List<SecretKey> factorKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
         // Prepare a hash
         final HMACHashUtilities hmac = new HMACHashUtilities();
 
@@ -178,7 +178,7 @@ public class AuthenticationCodeLegacyUtils {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    public String computePowerAuthCode(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration configuration) throws GenericCryptoException, CryptoProviderException {
+    public String computeAuthCode(byte[] data, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration configuration) throws GenericCryptoException, CryptoProviderException {
         if (factorKeys == null) {
             throw new GenericCryptoException("Missing factorKeys parameter");
         }
@@ -193,11 +193,11 @@ public class AuthenticationCodeLegacyUtils {
         }
         switch (configuration.getAuthenticationCodeFormat()) {
             case BASE64 -> {
-                return computePowerAuthBase64Code(data, factorKeys, ctrData);
+                return computeAuthBase64Code(data, factorKeys, ctrData);
             }
             case DECIMAL -> {
                 final Integer len = ((DecimalAuthenticationCodeConfiguration) configuration).getLength();
-                return computePowerAuthDecimalCode(data, factorKeys, ctrData, len);
+                return computeAuthDecimalCode(data, factorKeys, ctrData, len);
             }
             default ->
                     throw new GenericCryptoException("Unsupported format of PowerAuth authentication code.");
@@ -224,8 +224,8 @@ public class AuthenticationCodeLegacyUtils {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    public boolean validatePowerAuthCode(byte[] data, String authenticationCode, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration configuration) throws GenericCryptoException, CryptoProviderException {
-        return authenticationCode.equals(computePowerAuthCode(data, factorKeys, ctrData, configuration));
+    public boolean validateAuthCode(byte[] data, String authenticationCode, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration configuration) throws GenericCryptoException, CryptoProviderException {
+        return authenticationCode.equals(computeAuthCode(data, factorKeys, ctrData, configuration));
     }
 
 }
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/ECPublicKeyFingerprint.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/ECPublicKeyFingerprint.java
index 6d01a3e0..406b2669 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/ECPublicKeyFingerprint.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/util/ECPublicKeyFingerprint.java
@@ -59,9 +59,6 @@ public class ECPublicKeyFingerprint {
             // Prepare fingerprint data
             byte[] fingerprintData;
             switch (activationVersion) {
-                case VERSION_2 ->
-                    // In version 2 the activation fingerprint is computed from device public key bytes
-                        fingerprintData = toByteArray(devicePublicKey);
                 case VERSION_3 -> {
                     if (serverPublicKey == null) {
                         throw new GenericCryptoException("Server public key is invalid");
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsa.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsa.java
index f5bdcaaa..e1cea2fb 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsa.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsa.java
@@ -17,6 +17,7 @@
 
 package com.wultra.security.powerauth.crypto.lib.v4;
 
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
 import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import lombok.NoArgsConstructor;
 import org.bouncycastle.jcajce.spec.MLDSAParameterSpec;
@@ -35,15 +36,15 @@ public class PqcDsa {
      * Generate PQC DSA keypair.
      *
      * @return Keypair.
-     * @throws GenericCryptoException Thrown in case of any cryptography error.
+     * @throws CryptoProviderException Thrown in case the cryptography provider is incorrectly initialized.
      */
-    public KeyPair generateKeyPair() throws GenericCryptoException {
+    public KeyPair generateKeyPair() throws CryptoProviderException {
         try {
             final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("MLDSA", "BC");
             keyPairGenerator.initialize(MLDSAParameterSpec.ml_dsa_65);
             return keyPairGenerator.generateKeyPair();
         } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
-            throw new GenericCryptoException("Error generating key pair", e);
+            throw new CryptoProviderException("Error generating key pair", e);
         }
     }
 
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java
index aeeee0a2..02b15d0f 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java
@@ -24,8 +24,6 @@ import com.wultra.security.powerauth.crypto.lib.model.ActivationVersion;
 import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
 import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import com.wultra.security.powerauth.crypto.lib.util.*;
-import com.wultra.security.powerauth.crypto.lib.v4.kdf.CustomString;
-import com.wultra.security.powerauth.crypto.lib.v4.kdf.Kmac;
 
 import javax.crypto.SecretKey;
 import java.nio.ByteBuffer;
@@ -39,7 +37,15 @@ import java.util.Arrays;
 
 /**
  * Class implementing cryptography used on a server side in order to assure
- * PowerAuth Server activation related processes.
+ * PowerAuth Server activation related processes (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak, petr@wultra.com
  * @author Roman Strobl, roman.strobl@wultra.com
@@ -49,11 +55,6 @@ public class PowerAuthServerActivation {
     private static final KeyGenerator KEY_GENERATOR = new KeyGenerator();
     private static final SignatureUtils SIGNATURE_UTILS = new SignatureUtils();
 
-    /**
-     * Custom bytes for MAC for counter data.
-     */
-    private static final byte[] KMAC_STATUS_CUSTOM_BYTES = CustomString.PA4MAC_STATUS.value().getBytes(StandardCharsets.UTF_8);
-
     /**
      * Generate a server related activation key pair.
      *
@@ -98,6 +99,45 @@ public class PowerAuthServerActivation {
         return SIGNATURE_UTILS.computeECDSASignature(EcCurve.P256, bytes, masterPrivateKey);
     }
 
+
+
+    /**
+     * Compute a fingerprint for the version 3 activation. The fingerprint can be used for visual validation of exchanged device public key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>3.0</li>
+     *     <li>3.1</li>
+     *     <li>3.2</li>
+     *     <li>3.3</li>
+     * </ul>
+     *
+     * @param devicePublicKey Device public key.
+     * @param serverPublicKey Server public key.
+     * @param activationId Activation ID.
+     * @return Fingerprint of the public key.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case fingerprint could not be calculated.
+     */
+    public String computeActivationFingerprint(PublicKey devicePublicKey, PublicKey serverPublicKey, String activationId) throws GenericCryptoException, CryptoProviderException {
+        return computeActivationFingerprint(devicePublicKey, serverPublicKey, activationId, ActivationVersion.VERSION_3);
+    }
+
+    /**
+     * Compute a fingerprint for the activation. The fingerprint can be used for visual validation of exchanged public keys.
+     *
+     * @param devicePublicKey Device public key.
+     * @param serverPublicKey Server public key.
+     * @param activationId Activation ID.
+     * @param activationVersion Activation version.
+     * @return Fingerprint of the public key.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case fingerprint could not be calculated.
+     */
+    public String computeActivationFingerprint(PublicKey devicePublicKey, PublicKey serverPublicKey, String activationId, ActivationVersion activationVersion) throws GenericCryptoException, CryptoProviderException {
+        return ECPublicKeyFingerprint.compute(((ECPublicKey)devicePublicKey), (ECPublicKey)serverPublicKey, activationId, activationVersion);
+    }
+
     /**
      * Returns an encrypted status blob as described in PowerAuth Specification.
      *
@@ -144,7 +184,6 @@ public class PowerAuthServerActivation {
      *     <li>3.1</li>
      *     <li>3.2</li>
      *     <li>3.3</li>
-     *     <li>4.0</li>
      * </ul>
      *
      * @param statusBlobInfo Activation status blog information.
@@ -160,20 +199,8 @@ public class PowerAuthServerActivation {
         final int magicValue;
         final byte[] statusFlagsAndReserved;
         final int blobLength;
-        if (protocolVersion.getMajorVersion() == 4) {
-            if (statusBlobInfo.getCtrDataHash() == null) {
-                throw new GenericCryptoException("Missing ctrDataHash in statusBlobInfo object");
-            }
-            magicValue = ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE_V4;
-            // Status flags 1 byte + reserved 5 bytes
-            statusFlagsAndReserved = ByteBuffer.allocate(5)
-                    .put(statusBlobInfo.getStatusFlags())
-                    .put(KEY_GENERATOR.generateRandomBytes(4))
-                    .array();
-            ctrDataHash = statusBlobInfo.getCtrDataHash();
-            ctrByte = statusBlobInfo.getCtrByte();
-            ctrLookAhead = statusBlobInfo.getCtrLookAhead();
-            blobLength = 48;
+        if (protocolVersion.getMajorVersion() != 3) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
         } else if (protocolVersion != ProtocolVersion.V30) {
             if (statusBlobInfo.getCtrDataHash() == null) {
                 throw new GenericCryptoException("Missing ctrDataHash in statusBlobInfo object");
@@ -237,65 +264,10 @@ public class PowerAuthServerActivation {
      */
     public byte[] calculateHashFromHashBasedCounter(byte[] ctrData, SecretKey keyCtrDataMac, ProtocolVersion protocolVersion)
             throws CryptoProviderException, InvalidKeyException, GenericCryptoException {
-        return new HashBasedCounterUtils().calculateHashFromHashBasedCounter(ctrData, keyCtrDataMac, protocolVersion);
-    }
-
-    /**
-     * Calculate MAC for activation status.
-     *
-     * <p><b>PowerAuth protocol versions:</b>
-     * <ul>
-     *     <li>4.0</li>
-     * </ul>
-     *
-     * @param statusData Activation status data.
-     * @param keyCtrStatusMac Key for calculating MAC for activation data.
-     * @param protocolVersion Protocol version.
-     * @return Activation status MAC.
-     * @throws GenericCryptoException In case of a cryptography error.
-     */
-    public byte[] calculateStatusMac(byte[] statusData, SecretKey keyCtrStatusMac, ProtocolVersion protocolVersion) throws GenericCryptoException {
-        if (protocolVersion.getMajorVersion() < 4) {
+        if (protocolVersion.getMajorVersion() != 3) {
             throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
         }
-        return Kmac.kmac256(keyCtrStatusMac, statusData, KMAC_STATUS_CUSTOM_BYTES);
-    }
-
-    /**
-     * Compute a fingerprint for the version 3 activation. The fingerprint can be used for visual validation of exchanged device public key.
-     *
-     * <p><b>PowerAuth protocol versions:</b>
-     * <ul>
-     *     <li>3.0</li>
-     *     <li>3.1</li>
-     *     <li>3.2</li>
-     *     <li>3.3</li>
-     * </ul>
-     *
-     * @param devicePublicKey Device public key.
-     * @param serverPublicKey Server public key.
-     * @param activationId Activation ID.
-     * @return Fingerprint of the public key.
-     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
-     * @throws GenericCryptoException In case fingerprint could not be calculated.
-     */
-    public String computeActivationFingerprint(PublicKey devicePublicKey, PublicKey serverPublicKey, String activationId) throws GenericCryptoException, CryptoProviderException {
-        return computeActivationFingerprint(devicePublicKey, serverPublicKey, activationId, ActivationVersion.VERSION_3);
-    }
-
-    /**
-     * Compute a fingerprint for the activation. The fingerprint can be used for visual validation of exchanged public keys.
-     *
-     * @param devicePublicKey Device public key.
-     * @param serverPublicKey Server public key.
-     * @param activationId Activation ID.
-     * @param activationVersion Activation version.
-     * @return Fingerprint of the public key.
-     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
-     * @throws GenericCryptoException In case fingerprint could not be calculated.
-     */
-    public String computeActivationFingerprint(PublicKey devicePublicKey, PublicKey serverPublicKey, String activationId, ActivationVersion activationVersion) throws GenericCryptoException, CryptoProviderException {
-        return ECPublicKeyFingerprint.compute(((ECPublicKey)devicePublicKey), (ECPublicKey)serverPublicKey, activationId, activationVersion);
+        return new HashBasedCounterUtils().calculateHashFromHashBasedCounter(ctrData, keyCtrDataMac, protocolVersion);
     }
 
 }
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/authentication/PowerAuthServerAuthentication.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/authentication/PowerAuthServerAuthentication.java
index e15f52bf..5621bb85 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/authentication/PowerAuthServerAuthentication.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/authentication/PowerAuthServerAuthentication.java
@@ -26,7 +26,7 @@ import java.util.List;
 
 /**
  * Class implementing processes PowerAuth Server uses to compute and validate
- * authentication codes.
+ * authentication codes (V3).
  *
  * <p><b>PowerAuth protocol versions:</b>
  * <ul>
@@ -36,12 +36,8 @@ import java.util.List;
  *     <li>3.3</li>
  * </ul>
  *
- * @deprecated For version 3.x, use the {@link com.wultra.security.powerauth.crypto.lib.util.AuthenticationCodeLegacyUtils} directly. For version 4.0 or higher, use the {@link com.wultra.security.powerauth.crypto.lib.util.AuthenticationCodeUtils} directly.
- *
- * @author Petr Dvorak
- *
+ * @author Petr Dvorak, petr@wultra.com
  */
-@Deprecated
 public class PowerAuthServerAuthentication {
 
     private final AuthenticationCodeLegacyUtils authenticationCodeUtils = new AuthenticationCodeLegacyUtils();
@@ -67,8 +63,8 @@ public class PowerAuthServerAuthentication {
      * @throws GenericCryptoException In case authentication code computation fails.
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
-    public boolean verifyAuthenticationForData(byte[] data, String authenticationCode, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration authenticationCodeConfiguration) throws GenericCryptoException, CryptoProviderException {
-        return authenticationCodeUtils.validatePowerAuthCode(data, authenticationCode, factorKeys, ctrData, authenticationCodeConfiguration);
+    public boolean validateAuthCode(byte[] data, String authenticationCode, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration authenticationCodeConfiguration) throws GenericCryptoException, CryptoProviderException {
+        return authenticationCodeUtils.validateAuthCode(data, authenticationCode, factorKeys, ctrData, authenticationCodeConfiguration);
     }
 
 }
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/keyfactory/PowerAuthServerKeyFactory.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/keyfactory/PowerAuthServerKeyFactory.java
index 75b7a9fb..45978e83 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/keyfactory/PowerAuthServerKeyFactory.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/keyfactory/PowerAuthServerKeyFactory.java
@@ -31,7 +31,7 @@ import java.util.List;
 
 
 /**
- * Key factory used on server side to generate PowerAuth related keys.
+ * Key factory used on server side to generate PowerAuth related keys (V3).
  *
  * <p><b>PowerAuth protocol versions:</b>
  * <ul>
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenGenerator.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenGenerator.java
index 6b149960..dcc04fbf 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenGenerator.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenGenerator.java
@@ -21,7 +21,15 @@ import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoExc
 import com.wultra.security.powerauth.crypto.lib.util.TokenUtils;
 
 /**
- * Server side class used for generating new tokens.
+ * Server side class used for generating new tokens (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak, petr@wultra.com
  * @author Roman Strobl, roman.strobl@wultra.com
@@ -49,7 +57,6 @@ public class ServerTokenGenerator {
         final byte[] tokenSecret;
         switch (version) {
             case "3.0", "3.1", "3.2", "3.3" -> tokenSecret = tokenUtils.generateTokenSecret(16);
-            case "4.0" -> tokenSecret = tokenUtils.generateTokenSecret(32);
             default -> throw new GenericCryptoException("Unsupported protocol version: " + version);
         }
         return tokenSecret;
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenVerifier.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenVerifier.java
index 5d24e91d..82b36243 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenVerifier.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/token/ServerTokenVerifier.java
@@ -16,12 +16,21 @@
  */
 package com.wultra.security.powerauth.crypto.server.token;
 
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
 import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
 import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import com.wultra.security.powerauth.crypto.lib.util.TokenUtils;
 
 /**
- * Class to simplify token verification on the server side.
+ * Class to simplify token verification on the server side (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak, petr@wultra.com
  */
@@ -52,6 +61,10 @@ public class ServerTokenVerifier {
      * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
      */
     public boolean validateTokenDigest(byte[] nonce, byte[] timestamp, String version, byte[] tokenSecret, byte[] tokenDigest) throws GenericCryptoException, CryptoProviderException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 3) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
         return tokenUtils.validateTokenDigest(nonce, timestamp, version, tokenSecret, tokenDigest);
     }
 
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/util/DataDigest.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/util/DataDigest.java
index 017b1ccd..439c7410 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/util/DataDigest.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/util/DataDigest.java
@@ -16,12 +16,11 @@
  */
 package com.wultra.security.powerauth.crypto.server.util;
 
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
 import com.wultra.security.powerauth.crypto.lib.generator.KeyGenerator;
 import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
 import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import com.wultra.security.powerauth.crypto.lib.util.HMACHashUtilities;
-import com.wultra.security.powerauth.crypto.lib.v4.kdf.CustomString;
-import com.wultra.security.powerauth.crypto.lib.v4.kdf.Kmac;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,14 +29,20 @@ import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
- * This is a helper class that provides utility methods for computing various data digests.
+ * This is a helper class that provides utility methods for computing various data digests (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak, petr@wultra.com
  */
 public class DataDigest {
 
-    private static final byte[] KMAC_DATA_DIGEST_CUSTOM_BYTES = CustomString.PA4DIGEST_DATA.value().getBytes(StandardCharsets.UTF_8);
-
     private static final Logger logger = LoggerFactory.getLogger(DataDigest.class);
 
     /**
@@ -149,9 +154,12 @@ public class DataDigest {
     }
 
     private byte[] generateDigest(String version, byte[] operationData, byte[] key) throws GenericCryptoException, CryptoProviderException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 3) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
         return switch (version) {
             case "3.0", "3.1", "3.2", "3.3" -> hmac.hash(key, operationData);
-            case "4.0" -> Kmac.kmac256(key, operationData, KMAC_DATA_DIGEST_CUSTOM_BYTES);
             default -> throw new GenericCryptoException("Unsupported protocol version: " + version);
         };
     }
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/activation/PowerAuthServerActivation.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/activation/PowerAuthServerActivation.java
new file mode 100644
index 00000000..4f85886b
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/activation/PowerAuthServerActivation.java
@@ -0,0 +1,252 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2025 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 com.wultra.security.powerauth.crypto.server.v4.activation;
+
+import com.wultra.security.powerauth.crypto.lib.enums.EcCurve;
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
+import com.wultra.security.powerauth.crypto.lib.generator.KeyGenerator;
+import com.wultra.security.powerauth.crypto.lib.model.ActivationStatusBlobInfo;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.*;
+import com.wultra.security.powerauth.crypto.lib.v4.PqcDsa;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.CustomString;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.Kmac;
+
+import javax.crypto.SecretKey;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * Class implementing cryptography used on a server side in order to assure
+ * PowerAuth Server activation related processes (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class PowerAuthServerActivation {
+
+    private static final KeyGenerator KEY_GENERATOR = new KeyGenerator();
+    private static final SignatureUtils SIGNATURE_UTILS = new SignatureUtils();
+    private static final PqcDsa PQC_DSA = new PqcDsa();
+
+    /**
+     * Custom bytes for MAC for counter data.
+     */
+    private static final byte[] KMAC_STATUS_CUSTOM_BYTES = CustomString.PA4MAC_STATUS.value().getBytes(StandardCharsets.UTF_8);
+
+    /**
+     * Generate a server related activation EC key pair.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @return A new server key pair.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public KeyPair generateEcServerKeyPair() throws CryptoProviderException {
+        return KEY_GENERATOR.generateKeyPair(EcCurve.P384);
+    }
+
+    /**
+     * Generate a server related activation PQC key pair.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @return A new server key pair.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public KeyPair generatePqcServerKeyPair() throws CryptoProviderException {
+        return PQC_DSA.generateKeyPair();
+    }
+
+    /**
+     * Generate signature for the activation code.
+     * <p>
+     * Signature is then computed using the master private key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param activationCode Short activation ID.
+     * @param masterPrivateKey Master Private Key.
+     * @return Signature of activation data using Master Private Key.
+     * @throws InvalidKeyException In case Master Private Key is invalid.
+     * @throws GenericCryptoException In case signature computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public byte[] generateActivationSignature(String activationCode,
+                                              PrivateKey masterPrivateKey) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
+        byte[] bytes = activationCode.getBytes(StandardCharsets.UTF_8);
+        return SIGNATURE_UTILS.computeECDSASignature(EcCurve.P384, bytes, masterPrivateKey);
+    }
+
+    /**
+     * Generate activations status blob for different protocol versions.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param statusBlobInfo Activation status blog information.
+     * @param protocolVersion Protocol version.
+     * @return Status blob byte array.
+     * @throws GenericCryptoException In case encryption fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public byte[] generateStatusBlob(ActivationStatusBlobInfo statusBlobInfo, ProtocolVersion protocolVersion) throws GenericCryptoException, CryptoProviderException {
+        final byte[] ctrDataHash;
+        final byte ctrByte;
+        final byte ctrLookAhead;
+        final int magicValue;
+        final byte[] statusFlagsAndReserved;
+        final int blobLength;
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        if (statusBlobInfo.getCtrDataHash() == null) {
+            throw new GenericCryptoException("Missing ctrDataHash in statusBlobInfo object");
+        }
+        magicValue = ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE_V4;
+        // Status flags 1 byte + reserved 5 bytes
+        statusFlagsAndReserved = ByteBuffer.allocate(5)
+                .put(statusBlobInfo.getStatusFlags())
+                .put(KEY_GENERATOR.generateRandomBytes(4))
+                .array();
+        ctrDataHash = statusBlobInfo.getCtrDataHash();
+        ctrByte = statusBlobInfo.getCtrByte();
+        ctrLookAhead = statusBlobInfo.getCtrLookAhead();
+        blobLength = 48;
+        return ByteBuffer.allocate(blobLength)
+                .putInt(magicValue)                          // 4 bytes
+                .put(statusBlobInfo.getActivationStatus())   // 1 byte
+                .put(statusBlobInfo.getCurrentVersion())     // 1 byte
+                .put(statusBlobInfo.getUpgradeVersion())     // 1 byte
+                .put(statusFlagsAndReserved)                 // 5 bytes
+                .put(ctrByte)                                // 1 byte
+                .put(statusBlobInfo.getFailedAttempts())     // 1 byte
+                .put(statusBlobInfo.getMaxFailedAttempts())  // 1 byte
+                .put(ctrLookAhead)                           // 1 byte
+                .put(ctrDataHash)                            // 16 bytes
+                .array();
+    }
+
+    /**
+     * Calculate MAC for activation status.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param statusData Activation status data.
+     * @param keyCtrStatusMac Key for calculating MAC for activation data.
+     * @param protocolVersion Protocol version.
+     * @return Activation status MAC.
+     * @throws GenericCryptoException In case of a cryptography error.
+     */
+    public byte[] calculateStatusMac(byte[] statusData, SecretKey keyCtrStatusMac, ProtocolVersion protocolVersion) throws GenericCryptoException {
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return Kmac.kmac256(keyCtrStatusMac, statusData, KMAC_STATUS_CUSTOM_BYTES);
+    }
+
+    /**
+     * Compute a fingerprint for the version 4 activation for algorithm EC_P384. The fingerprint can be used for visual validation of exchanged device public key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param devicePublicKey Device public key.
+     * @param serverPublicKey Server public key.
+     * @param activationId Activation ID.
+     * @return Fingerprint of the public key.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case fingerprint could not be calculated.
+     */
+    public String computeActivationEcFingerprint(PublicKey devicePublicKey, PublicKey serverPublicKey, String activationId) throws GenericCryptoException, CryptoProviderException {
+        // TODO - support for crypto4
+        return "";
+    }
+
+    /**
+     * Compute a fingerprint for the version 4 activation for algorithm EC_P384_ML_L3. The fingerprint can be used for visual validation of exchanged device public key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param ecDevicePublicKey EC device public key.
+     * @param pqcDevicePublicKey PQC device public key.
+     * @param ecServerPublicKey EC server public key.
+     * @param pqcServerPublicKey PQC server public key.
+     * @param activationId Activation ID.
+     * @return Fingerprint of the public keys.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case fingerprint could not be calculated.
+     */
+    public String computeActivationHybridFingerprint(PublicKey ecDevicePublicKey, PublicKey pqcDevicePublicKey, PublicKey ecServerPublicKey, PublicKey pqcServerPublicKey, String activationId) throws GenericCryptoException, CryptoProviderException {
+        // TODO - support for crypto4
+        return "";
+    }
+
+    /**
+     * Calculate hash from value representing the hash based counter. HMAC-SHA256 is currently used as a hashing
+     * function.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param ctrData Hash-based counter.
+     * @param keyCtrDataMac Key for calculating the counter data hash.
+     * @param protocolVersion Protocol version.
+     * @return Hash calculated from provided hash-based counter.
+     * @throws GenericCryptoException In case that key derivation fails or you provided invalid ctrData.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     * @throws InvalidKeyException In case that transport key is not valid.
+     */
+    public byte[] calculateHashFromHashBasedCounter(byte[] ctrData, SecretKey keyCtrDataMac, ProtocolVersion protocolVersion)
+            throws CryptoProviderException, InvalidKeyException, GenericCryptoException {
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return new HashBasedCounterUtils().calculateHashFromHashBasedCounter(ctrData, keyCtrDataMac, protocolVersion);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/authentication/PowerAuthServerAuthentication.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/authentication/PowerAuthServerAuthentication.java
new file mode 100644
index 00000000..06833c8f
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/authentication/PowerAuthServerAuthentication.java
@@ -0,0 +1,64 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2018 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 com.wultra.security.powerauth.crypto.server.v4.authentication;
+
+import com.wultra.security.powerauth.crypto.lib.config.AuthenticationCodeConfiguration;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.AuthenticationCodeUtils;
+
+import javax.crypto.SecretKey;
+import java.util.List;
+
+/**
+ * Class implementing processes PowerAuth Server uses to compute and validate
+ * authentication codes (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class PowerAuthServerAuthentication {
+
+    private final AuthenticationCodeUtils authenticationCodeUtils = new AuthenticationCodeUtils();
+
+    /**
+     * Verify the authentication code against data using authentication code key list and
+     * counter.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param data Signed data.
+     * @param authenticationCode Authentication code for the data.
+     * @param factorKeys Keys used for verification.
+     * @param ctrData Hash based counter / derived signing key index.
+     * @param authenticationCodeConfiguration Format and parameters of authentication code to verify.
+     * @return Returns "true" if the authentication code matches, "false" otherwise.
+     * @throws GenericCryptoException In case authentication code computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public boolean validateAuthCode(byte[] data, String authenticationCode, List<SecretKey> factorKeys, byte[] ctrData, AuthenticationCodeConfiguration authenticationCodeConfiguration) throws GenericCryptoException, CryptoProviderException {
+        return authenticationCodeUtils.validateAuthCode(data, authenticationCode, factorKeys, ctrData, authenticationCodeConfiguration);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/keyfactory/PowerAuthServerKeyFactory.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/keyfactory/PowerAuthServerKeyFactory.java
new file mode 100644
index 00000000..4b3e3029
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/keyfactory/PowerAuthServerKeyFactory.java
@@ -0,0 +1,226 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2018 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 com.wultra.security.powerauth.crypto.server.v4.keyfactory;
+
+import com.wultra.security.powerauth.crypto.lib.enums.PowerAuthCodeType;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.KeyFactory;
+
+import javax.crypto.SecretKey;
+import java.security.InvalidKeyException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Key factory used on server side to generate PowerAuth related keys (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ *
+ */
+public class PowerAuthServerKeyFactory {
+
+    /**
+     * Return a correct list of keys for given factor key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param powerAuthCodeType Requested type of a factor.
+     * @param possessionFactorKey Possession factor related factor key.
+     * @param knowledgeFactorKey Knowledge factor related factor key.
+     * @param biometryFactorKey Biometry factor related factor key.
+     * @return List with correct keys
+     */
+    public List<SecretKey> keysForAuthenticationCodeType(PowerAuthCodeType powerAuthCodeType, SecretKey possessionFactorKey, SecretKey knowledgeFactorKey, SecretKey biometryFactorKey) {
+        final List<SecretKey> factorKeys = new ArrayList<>();
+        if (powerAuthCodeType == null) {
+            return factorKeys;
+        }
+        if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION)) {
+            factorKeys.add(possessionFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.KNOWLEDGE)) {
+            factorKeys.add(knowledgeFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.BIOMETRY)) {
+            factorKeys.add(biometryFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE)) {
+            factorKeys.add(possessionFactorKey);
+            factorKeys.add(knowledgeFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_BIOMETRY)) {
+            factorKeys.add(possessionFactorKey);
+            factorKeys.add(biometryFactorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE_BIOMETRY)) {
+            factorKeys.add(possessionFactorKey);
+            factorKeys.add(knowledgeFactorKey);
+            factorKeys.add(biometryFactorKey);
+        }
+        return factorKeys;
+    }
+
+    /**
+     * Generate a list with authentication code keys for given authentication code type and activation secret key.
+     *
+     * <p><b>PowerAuth protocol versions:</b>
+     * <ul>
+     *     <li>4.0</li>
+     * </ul>
+     *
+     * @param powerAuthCodeType Requested authentication code type.
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return List with keys constructed from master secret that are needed to
+     *         get requested authentication code type.
+     * @throws InvalidKeyException In case master secret key is invalid.
+     * @throws GenericCryptoException In case key derivation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public List<SecretKey> keysForAuthenticationCodeType(PowerAuthCodeType powerAuthCodeType, SecretKey keyActivationSecret) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
+        final List<SecretKey> factorKeys = new ArrayList<>();
+        if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION)) {
+            final SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.KNOWLEDGE)) {
+            final SecretKey factorKey = generateKnowledgeFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.BIOMETRY)) {
+            final SecretKey factorKey = generateBiometryFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE)) {
+            SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateKnowledgeFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_BIOMETRY)) {
+            SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateBiometryFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        } else if (powerAuthCodeType.equals(PowerAuthCodeType.POSSESSION_KNOWLEDGE_BIOMETRY)) {
+            SecretKey factorKey = generatePossessionFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateKnowledgeFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+            factorKey = generateBiometryFactorKey(keyActivationSecret);
+            factorKeys.add(factorKey);
+        }
+        return factorKeys;
+    }
+
+    /**
+     * Generate a factor key KEY_FACTOR_BIOMETRY from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyAuthenticationCodeBiometry(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of factor key KEY_FACTOR_BIOMETRY.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateBiometryFactorKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyAuthenticationCodeBiometry(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_FACTOR_KNOWLEDGE from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyAuthenticationCodeKnowledge(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of factor key KEY_FACTOR_KNOWLEDGE.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKnowledgeFactorKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyAuthenticationCodeKnowledge(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_FACTOR_POSSESSION from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyAuthenticationCodePossession(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of factor key KEY_FACTOR_POSSESSION.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generatePossessionFactorKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyAuthenticationCodePossession(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_GET_APP_TEMP_KEY from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacGetActTempKey(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_GET_APP_TEMP_KEY.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacGetAppTempKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacGetAppTempKey(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_GET_ACT_TEMP_KEY from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacGetActTempKey(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_GET_ACT_TEMP_KEY.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacGetActTempKey(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacGetActTempKey(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_STATUS from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacStatus(SecretKey) (SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_STATUS.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacStatus(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacStatus(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_MAC_CTR_DATA from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacCtrData(SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_MAC_CTR_DATA.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateKeyMacCtrData(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyMacCtrData(keyActivationSecret);
+    }
+
+    /**
+     * Generate a factor key KEY_E2EE_SHARED_INFO2 from KEY_ACTIVATION_SECRET using KDF.
+     *
+     * @see KeyFactory#deriveKeyMacStatus(SecretKey) (SecretKey)
+     * @param keyActivationSecret Activation secret key KEY_ACTIVATION_SECRET.
+     * @return An instance of key KEY_E2EE_SHARED_INFO2.
+     * @throws GenericCryptoException In case key derivation fails.
+     */
+    public SecretKey generateSharedInfo2Key(SecretKey keyActivationSecret) throws GenericCryptoException {
+        return KeyFactory.deriveKeyE2eeSharedInfo2(keyActivationSecret);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/token/ServerTokenGenerator.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/token/ServerTokenGenerator.java
new file mode 100644
index 00000000..ee561a02
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/token/ServerTokenGenerator.java
@@ -0,0 +1,61 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2018 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 com.wultra.security.powerauth.crypto.server.v4.token;
+
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.TokenUtils;
+
+/**
+ * Server side class used for generating new tokens (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class ServerTokenGenerator {
+
+    private final TokenUtils tokenUtils = new TokenUtils();
+
+    /**
+     * Generate random token ID. Use UUID format.
+     * @return Random token ID.
+     */
+    public String generateTokenId() {
+        return tokenUtils.generateTokenId();
+    }
+
+    /**
+     * Generate random token secret, 16 or 32 random bytes depending on the version.
+     * @param version Protocol version.
+     * @return Random token secret.
+     * @throws CryptoProviderException In case key cryptography provider is incorrectly initialized.
+     * @throws GenericCryptoException In case the protocol version is not supported.
+     */
+    public byte[] generateTokenSecret(String version) throws CryptoProviderException, GenericCryptoException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return tokenUtils.generateTokenSecret(32);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/token/ServerTokenVerifier.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/token/ServerTokenVerifier.java
new file mode 100644
index 00000000..ee36f542
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/token/ServerTokenVerifier.java
@@ -0,0 +1,68 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2018 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 com.wultra.security.powerauth.crypto.server.v4.token;
+
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.TokenUtils;
+
+/**
+ * Class to simplify token verification on the server side (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+public class ServerTokenVerifier {
+
+    private final TokenUtils tokenUtils = new TokenUtils();
+
+    /**
+     * Helper method to convert provided timestamp into bytes (from string representation), for the
+     * purpose of token timestamping.
+     *
+     * @param timestamp Timestamp to be converted.
+     * @return Provided timestamp in milliseconds converted as bytes.
+     */
+    public byte[] convertTokenTimestamp(long timestamp) {
+        return tokenUtils.convertTokenTimestamp(timestamp);
+    }
+
+    /**
+     * Validate provided token digest for given input data and provided token secret.
+     * @param nonce Token nonce, 16 random bytes.
+     * @param timestamp Token timestamp, Unix timestamp format encoded as bytes (from string representation).
+     * @param version Protocol version.
+     * @param tokenSecret Token secret, 16 random bytes.
+     * @param tokenDigest Token digest, 32 bytes to be validated.
+     * @return Token digest computed using provided data bytes with given token secret.
+     * @throws GenericCryptoException In case digest computation fails.
+     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
+     */
+    public boolean validateTokenDigest(byte[] nonce, byte[] timestamp, String version, byte[] tokenSecret, byte[] tokenDigest) throws GenericCryptoException, CryptoProviderException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return tokenUtils.validateTokenDigest(nonce, timestamp, version, tokenSecret, tokenDigest);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/util/DataDigest.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/util/DataDigest.java
new file mode 100644
index 00000000..9c0ce636
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/util/DataDigest.java
@@ -0,0 +1,149 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2018 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 com.wultra.security.powerauth.crypto.server.v4.util;
+
+import com.wultra.security.powerauth.crypto.lib.enums.ProtocolVersion;
+import com.wultra.security.powerauth.crypto.lib.generator.KeyGenerator;
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
+import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
+import com.wultra.security.powerauth.crypto.lib.util.HMACHashUtilities;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.CustomString;
+import com.wultra.security.powerauth.crypto.lib.v4.kdf.Kmac;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * This is a helper class that provides utility methods for computing various data digests (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class DataDigest {
+
+    private static final byte[] KMAC_DATA_DIGEST_CUSTOM_BYTES = CustomString.PA4DIGEST_DATA.value().getBytes(StandardCharsets.UTF_8);
+
+    private static final Logger logger = LoggerFactory.getLogger(DataDigest.class);
+
+    /**
+     * Data digest result.
+     */
+    public static class Result {
+
+        private final String digest;
+        private final byte[] salt;
+
+        Result(String digest, byte[] salt) {
+            this.digest = digest;
+            this.salt = salt;
+        }
+
+        /**
+         * Get data digest.
+         * @return Data digest.
+         */
+        public String getDigest() {
+            return digest;
+        }
+
+        /**
+         * Get salt used for creating the data digest.
+         * @return Salt used for creating the data digest.
+         */
+        public byte[] getSalt() {
+            return salt;
+        }
+
+    }
+
+    private static final int AUTHORIZATION_CODE_LENGTH_MIN = 4;
+    private static final int AUTHORIZATION_CODE_LENGTH_MAX = 9;
+    private static final int AUTHORIZATION_CODE_LENGTH = 8;
+
+    private final HMACHashUtilities hmac = new HMACHashUtilities();
+
+    private final int authorizationCodeLength;
+
+    /**
+     * Initializes the data digest instance that produces digest of length 8.
+     */
+    public DataDigest() {
+        this.authorizationCodeLength = AUTHORIZATION_CODE_LENGTH;
+    }
+
+    /**
+     * Initializes the data digest instance that produces digest of specified length. The length must be between 4 and 9.
+     * The digest cannot be shorter than 4 due to lack of entropy in shorter codes. The digest cannot be longer than 9 digits
+     * since the internal implementation uses <code>int</code>, with a 64 bit range.
+     *
+     * @param length Length of the resulting digest.
+     * @throws GenericCryptoException In case the length is not in the allowed range - between 4 and 9 digits.
+     */
+    public DataDigest(int length) throws GenericCryptoException {
+        if (length < AUTHORIZATION_CODE_LENGTH_MIN || length > AUTHORIZATION_CODE_LENGTH_MAX) {
+            throw new GenericCryptoException("Invalid length of the data digest: " + length);
+        }
+        this.authorizationCodeLength = length;
+    }
+    
+    /**
+     * Data digest of the list with string elements. Data is first normalized (items concatenated
+     * by '&amp;' character), then a random key is generated and hash (HMAC-SHA256) is computed. Finally,
+     * the resulting MAC is decimalized to the signature of a length 8 numeric digits.
+     *
+     * @param version Cryptography protocol version.
+     * @param items Items to be serialized into digest.
+     * @return Digest fo provided data, including seed used to compute that digest.
+     * @throws GenericCryptoException In case cryptography fails.
+     */
+    public Result generateDigest(String version, List<String> items) throws GenericCryptoException {
+        if (version == null) {
+            throw new GenericCryptoException("Missing protocol version when calculating digest");
+        }
+        if (items == null || items.isEmpty()) {
+            throw new GenericCryptoException("Missing data when calculating digest");
+        }
+        try {
+            final byte[] operationData = String.join("&", items).getBytes(StandardCharsets.UTF_8);
+            final byte[] randomKey = new KeyGenerator().generateRandomBytes(16);
+            final byte[] otpHash = generateDigest(version, operationData, randomKey);
+            final BigInteger otp = new BigInteger(otpHash).mod(BigInteger.TEN.pow(authorizationCodeLength));
+            final String digitFormat = "%" + String.format("%02d", authorizationCodeLength) + "d";
+            final String digest = String.format(digitFormat, otp);
+            return new Result(digest, randomKey);
+        } catch (GenericCryptoException | CryptoProviderException ex) {
+            logger.warn(ex.getMessage(), ex);
+            return null;
+        }
+    }
+
+    private byte[] generateDigest(String version, byte[] operationData, byte[] key) throws GenericCryptoException, CryptoProviderException {
+        final ProtocolVersion protocolVersion = ProtocolVersion.fromValue(version);
+        if (protocolVersion.getMajorVersion() != 4) {
+            throw new GenericCryptoException("Unsupported protocol version: " + protocolVersion);
+        }
+        return Kmac.kmac256(key, operationData, KMAC_DATA_DIGEST_CUSTOM_BYTES);
+    }
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/vault/PowerAuthServerVault.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/vault/PowerAuthServerVault.java
new file mode 100644
index 00000000..064a0349
--- /dev/null
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/v4/vault/PowerAuthServerVault.java
@@ -0,0 +1,33 @@
+/*
+ * PowerAuth Crypto Library
+ * Copyright 2018 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 com.wultra.security.powerauth.crypto.server.v4.vault;
+
+/**
+ * Class implementing server-side logic for PowerAuth vault encryption (V4).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>4.0</li>
+ * </ul>
+ *
+ * @author Roman Strobl, roman.strobl@wultra.com
+ */
+public class PowerAuthServerVault {
+
+    // TODO - support for crypto4
+
+}
diff --git a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/vault/PowerAuthServerVault.java b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/vault/PowerAuthServerVault.java
index 221482f0..777e18f8 100644
--- a/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/vault/PowerAuthServerVault.java
+++ b/powerauth-java-crypto/src/main/java/com/wultra/security/powerauth/crypto/server/vault/PowerAuthServerVault.java
@@ -29,7 +29,15 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 
 /**
- * Class implementing server-side logic for PowerAuth vault encryption.
+ * Class implementing server-side logic for PowerAuth vault encryption (V3).
+ *
+ * <p><b>PowerAuth protocol versions:</b>
+ * <ul>
+ *     <li>3.0</li>
+ *     <li>3.1</li>
+ *     <li>3.2</li>
+ *     <li>3.3</li>
+ * </ul>
  *
  * @author Petr Dvorak
  *
@@ -47,6 +55,7 @@ public class PowerAuthServerVault {
      *     <li>3.0</li>
      *     <li>3.1</li>
      *     <li>3.2</li>
+     *     <li>3.3</li>
      * </ul>
      *
      * @param serverPrivateKey Server private key KEY_SERVER_PRIVATE


Test patch:
diff --git a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java
index 174d105e..3bcaaff9 100644
--- a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java
+++ b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java
@@ -164,8 +164,8 @@ public class ActivationStatusBlobInfoTest {
 
     @Test
     public void testActivationStatusBlobV4() throws GenericCryptoException, CryptoProviderException {
-        final PowerAuthServerActivation serverActivation = new PowerAuthServerActivation();
-        final PowerAuthClientActivation clientActivation = new PowerAuthClientActivation();
+        final com.wultra.security.powerauth.crypto.server.v4.activation.PowerAuthServerActivation serverActivation = new com.wultra.security.powerauth.crypto.server.v4.activation.PowerAuthServerActivation();
+        final com.wultra.security.powerauth.crypto.client.v4.activation.PowerAuthClientActivation clientActivation = new com.wultra.security.powerauth.crypto.client.v4.activation.PowerAuthClientActivation();
         // Generate hash based counter
         byte[] ctrDataHash = KEY_GENERATOR.generateRandomBytes(32);
         ActivationStatusBlobInfo serverStatusBlob = new ActivationStatusBlobInfo();
diff --git a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/authentication/PowerAuthAuthenticationCodeTest.java b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/authentication/PowerAuthAuthenticationCodeTest.java
index fa2306b5..b7a26a19 100644
--- a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/authentication/PowerAuthAuthenticationCodeTest.java
+++ b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/authentication/PowerAuthAuthenticationCodeTest.java
@@ -128,7 +128,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientPossessionKey = clientKeyFactory.generateClientPossessionFactorKey(masterClientKey);
                 System.out.println("### Client Authentication Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientPossessionKey)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Collections.singletonList(clientPossessionKey), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Collections.singletonList(clientPossessionKey), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication Code: " + authenticationCode);
 
@@ -143,7 +143,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Possession Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverPossessionKey)));
                 assertEquals(clientPossessionKey, serverPossessionKey);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Collections.singletonList(serverPossessionKey), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Collections.singletonList(serverPossessionKey), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
@@ -169,7 +169,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientKeyKnowledge = clientKeyFactory.generateClientKnowledgeFactorKey(masterClientKey);
                 System.out.println("### Client Factor Key - Knowledge:  " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientKeyKnowledge)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication Code: " + authenticationCode);
 
@@ -187,7 +187,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Knowledge Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverKeyKnowledge)));
                 assertEquals(clientKeyKnowledge, serverKeyKnowledge);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
@@ -264,7 +264,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientPossessionKey = clientKeyFactory.generateClientPossessionFactorKey(masterClientKey);
                 System.out.println("### Client Authentication Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientPossessionKey)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Collections.singletonList(clientPossessionKey), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Collections.singletonList(clientPossessionKey), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication: " + authenticationCode);
 
@@ -279,7 +279,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Possession Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverPossessionKey)));
                 assertEquals(clientPossessionKey, serverPossessionKey);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Collections.singletonList(serverPossessionKey), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Collections.singletonList(serverPossessionKey), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
@@ -305,7 +305,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientKeyKnowledge = clientKeyFactory.generateClientKnowledgeFactorKey(masterClientKey);
                 System.out.println("### Client Key - Knowledge:  " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientKeyKnowledge)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication Code: " + authenticationCode);
 
@@ -323,7 +323,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Knowledge Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverKeyKnowledge)));
                 assertEquals(clientKeyKnowledge, serverKeyKnowledge);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
@@ -399,7 +399,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientKeyPossession = clientKeyFactory.generateClientPossessionFactorKey(masterClientKey);
                 System.out.println("### Client Possession Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientKeyPossession)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Collections.singletonList(clientKeyPossession), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Collections.singletonList(clientKeyPossession), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication Code: " + authenticationCode);
 
@@ -414,7 +414,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Possession Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverKeyPossession)));
                 assertEquals(clientKeyPossession, serverKeyPossession);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Collections.singletonList(serverKeyPossession), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Collections.singletonList(serverKeyPossession), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
@@ -440,7 +440,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientKeyKnowledge = clientKeyFactory.generateClientKnowledgeFactorKey(masterClientKey);
                 System.out.println("### Client Key - Knowledge:  " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientKeyKnowledge)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication Code: " + authenticationCode);
 
@@ -458,7 +458,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Knowledge Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverKeyKnowledge)));
                 assertEquals(clientKeyKnowledge, serverKeyKnowledge);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
@@ -488,7 +488,7 @@ public class PowerAuthAuthenticationCodeTest {
                 SecretKey clientKeyKnowledge = clientKeyFactory.generateClientKnowledgeFactorKey(masterClientKey);
                 System.out.println("### Client Key - Knowledge:  " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(clientKeyKnowledge)));
 
-                String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(clientKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
 
                 System.out.println("## Client Authentication Code: " + authenticationCode);
                 assertEquals(componentLength * 2 + 1, authenticationCode.length());
@@ -507,7 +507,7 @@ public class PowerAuthAuthenticationCodeTest {
                 System.out.println("### Server Knowledge Key: " + Base64.getEncoder().encodeToString(keyConvertor.convertSharedSecretKeyToBytes(serverKeyKnowledge)));
                 assertEquals(clientKeyKnowledge, serverKeyKnowledge);
 
-                boolean isAuthenticationCodeValid = serverAuth.verifyAuthenticationForData(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
+                boolean isAuthenticationCodeValid = serverAuth.validateAuthCode(data, authenticationCode, Arrays.asList(serverKeyPossession, clientKeyKnowledge), ctrData, authenticationCodeConfiguration);
                 System.out.println("## Authentication Code valid: " + (isAuthenticationCodeValid ? "TRUE" : "FALSE"));
                 assertTrue(isAuthenticationCodeValid);
 
diff --git a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/util/GenerateVectorDataTest.java b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/util/GenerateVectorDataTest.java
index 793b7322..8e907c3c 100644
--- a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/util/GenerateVectorDataTest.java
+++ b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/util/GenerateVectorDataTest.java
@@ -412,7 +412,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Collections.singletonList(possessionKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Collections.singletonList(possessionKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -434,7 +434,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(possessionKey, knowledgeKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(possessionKey, knowledgeKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession_knowledge";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -456,7 +456,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(possessionKey, knowledgeKey, biometryKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(possessionKey, knowledgeKey, biometryKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession_knowledge_biometry";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -530,7 +530,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Collections.singletonList(possessionFactorKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Collections.singletonList(possessionFactorKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -552,7 +552,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(possessionFactorKey, knowledgeFactorKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(possessionFactorKey, knowledgeFactorKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession_knowledge";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -574,7 +574,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(possessionFactorKey, knowledgeFactorKey, biometryFactorKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(possessionFactorKey, knowledgeFactorKey, biometryFactorKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession_knowledge_biometry";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -637,7 +637,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Collections.singletonList(possessionFactorKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Collections.singletonList(possessionFactorKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession";
 
                     Map<String, String> input = new LinkedHashMap<>();
@@ -660,7 +660,7 @@ public class GenerateVectorDataTest {
                     // generate random data
                     byte[] data = keyGenerator.generateRandomBytes((int) (Math.random() * dataMax));
 
-                    String authenticationCode = clientAuth.authenticateCodeForData(data, Arrays.asList(possessionFactorKey, knowledgeFactorKey), ctrData, authenticationCodeConfiguration);
+                    String authenticationCode = clientAuth.computeAuthCode(data, Arrays.asList(possessionFactorKey, knowledgeFactorKey), ctrData, authenticationCodeConfiguration);
                     String authenticationCodeType = "possession_knowledge";
 
                     Map<String, String> input = new LinkedHashMap<>();
diff --git a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsaTest.java b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsaTest.java
index 92b3d790..c5677c4d 100644
--- a/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsaTest.java
+++ b/powerauth-java-crypto/src/test/java/com/wultra/security/powerauth/crypto/lib/v4/PqcDsaTest.java
@@ -17,6 +17,7 @@
 
 package com.wultra.security.powerauth.crypto.lib.v4;
 
+import com.wultra.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
 import com.wultra.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.junit.jupiter.api.Test;
@@ -43,7 +44,7 @@ class PqcDsaTest {
      * @throws GenericCryptoException Thrown in case of any cryptography error.
      */
     @Test
-    public void testPqcDsa_Success() throws GenericCryptoException {
+    public void testPqcDsa_Success() throws GenericCryptoException, CryptoProviderException {
         final PqcDsa pqcDsa = new PqcDsa();
         final KeyPair keyPair = pqcDsa.generateKeyPair();
         final byte[] testMessage = "test_message".getBytes(StandardCharsets.UTF_8);
@@ -56,7 +57,7 @@ class PqcDsaTest {
      * @throws GenericCryptoException Thrown in case of any cryptography error.
      */
     @Test
-    public void testPqcDsa_InvalidSignature() throws GenericCryptoException {
+    public void testPqcDsa_InvalidSignature() throws GenericCryptoException, CryptoProviderException {
         final PqcDsa pqcDsa = new PqcDsa();
         final KeyPair keyPair = pqcDsa.generateKeyPair();
         final byte[] testMessage = "test_message".getBytes(StandardCharsets.UTF_8);
@@ -68,7 +69,7 @@ class PqcDsaTest {
      * @throws GenericCryptoException Thrown in case of any cryptography error.
      */
     @Test
-    public void testPqcDsa_InvalidPrivateKey() throws GenericCryptoException {
+    public void testPqcDsa_InvalidPrivateKey() throws GenericCryptoException, CryptoProviderException {
         final PqcDsa pqcDsa = new PqcDsa();
         final KeyPair keyPair = pqcDsa.generateKeyPair();
         final KeyPair keyPair2 = pqcDsa.generateKeyPair();
@@ -82,7 +83,7 @@ class PqcDsaTest {
      * @throws GenericCryptoException Thrown in case of any cryptography error.
      */
     @Test
-    public void testPqcDsa_NullMessage() throws GenericCryptoException {
+    public void testPqcDsa_NullMessage() throws CryptoProviderException {
         final PqcDsa pqcDsa = new PqcDsa();
         final KeyPair keyPair = pqcDsa.generateKeyPair();
         final Exception ex = assertThrows(GenericCryptoException.class, () -> pqcDsa.sign(keyPair.getPrivate(), null));
@@ -94,7 +95,7 @@ class PqcDsaTest {
      * @throws GenericCryptoException Thrown in case of any cryptography error.
      */
     @Test
-    public void testPqcDsa_EmptyMessage() throws GenericCryptoException {
+    public void testPqcDsa_EmptyMessage() throws GenericCryptoException, CryptoProviderException {
         final PqcDsa pqcDsa = new PqcDsa();
         final KeyPair keyPair = pqcDsa.generateKeyPair();
         final byte[] emptyMessage = new byte[0];