All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.getlime.security.powerauth.crypto.lib.util.SignatureUtils Maven / Gradle / Ivy

/*
 * 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 io.getlime.security.powerauth.crypto.lib.util;

import com.google.common.base.Joiner;
import com.google.common.io.BaseEncoding;
import io.getlime.security.powerauth.crypto.lib.config.PowerAuthConfiguration;
import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureFormat;
import io.getlime.security.powerauth.crypto.lib.model.exception.CryptoProviderException;
import io.getlime.security.powerauth.crypto.lib.model.exception.GenericCryptoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKey;
import java.nio.ByteBuffer;
import java.security.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Utility class for signature calculation and validation used both on client and server.
 *
 * @author Petr Dvorak
 *
 */
public class SignatureUtils {

    private static final Logger logger = LoggerFactory.getLogger(SignatureUtils.class);

    /**
     * Compute ECDSA signature of given bytes with a private key.
     *
     * @param bytes Bytes to be signed.
     * @param masterPrivateKey Private key for computing the signature.
     * @return Signature for given data.
     * @throws InvalidKeyException In case invalid key was provided.
     * @throws GenericCryptoException In case signature calculation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    public byte[] computeECDSASignature(byte[] bytes, PrivateKey masterPrivateKey) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
        try {
            Signature ecdsa = Signature.getInstance("SHA256withECDSA", PowerAuthConfiguration.CRYPTO_PROVIDER_NAME);
            ecdsa.initSign(masterPrivateKey);
            ecdsa.update(bytes);
            return ecdsa.sign();
        } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
            logger.warn(ex.getMessage(), ex);
            throw new CryptoProviderException(ex.getMessage(), ex);
        } catch (SignatureException ex) {
            logger.warn(ex.getMessage(), ex);
            throw new GenericCryptoException(ex.getMessage(), ex);
        }
    }

    /**
     * Validate an ECDSA signature against given data using a public key.
     *
     * @param signedBytes Bytes that are signed.
     * @param signature Signature of the bytes.
     * @param masterPublicKey Public key for validating the signature.
     * @return Returns "true" if signature matches, "false" otherwise.
     * @throws InvalidKeyException In case invalid key was provided.
     * @throws GenericCryptoException In case signature calculation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    public boolean validateECDSASignature(byte[] signedBytes, byte[] signature, PublicKey masterPublicKey) throws InvalidKeyException, GenericCryptoException, CryptoProviderException {
        try {
            Signature ecdsa = Signature.getInstance("SHA256withECDSA", PowerAuthConfiguration.CRYPTO_PROVIDER_NAME);
            ecdsa.initVerify(masterPublicKey);
            ecdsa.update(signedBytes);
            return ecdsa.verify(signature);
        } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
            logger.warn(ex.getMessage(), ex);
            throw new CryptoProviderException(ex.getMessage(), ex);
        } catch (SignatureException ex) {
            logger.warn(ex.getMessage(), ex);
            throw new GenericCryptoException(ex.getMessage(), ex);
        }
    }

    /**
     * Compute decimal formatted PowerAuth signature for given data using a secret signature keys and counter byte array.
     *
     * @param data Data to be signed.
     * @param signatureKeys Keys for computing the signature.
     * @param ctrData Counter byte array / derived key index.
     * @return Decimal formatted PowerAuth signature for given data.
     * @throws GenericCryptoException In case signature computation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    private String computePowerAuthDecimalSignature(byte[] data, List signatureKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
        // Prepare holder for signature components
        final String[] signatureStringComponents = new String[signatureKeys.size()];
        // Compute signature components
        final List signatureComponents = computePowerAuthSignatureComponents(data, signatureKeys, ctrData);
        // Convert byte components into decimal signature
        for (int i = 0; i < signatureComponents.size(); i++) {
            final byte[] signatureComponent = signatureComponents.get(i);
            int index = signatureComponent.length - 4;
            int number = (ByteBuffer.wrap(signatureComponent).getInt(index) & 0x7FFFFFFF) % (int) (Math.pow(10, PowerAuthConfiguration.SIGNATURE_DECIMAL_LENGTH));
            signatureStringComponents[i] = String.format("%0" + PowerAuthConfiguration.SIGNATURE_DECIMAL_LENGTH + "d", number);
        }
        // Join components with dash.
        return Joiner.on("-").join(signatureStringComponents);
    }

    /**
     * Compute Base64 formatted PowerAuth signature for given data using a secret signature keys and counter byte array.
     *
     * @param data Data to be signed.
     * @param signatureKeys Keys for computing the signature.
     * @param ctrData Counter byte array / derived key index.
     * @return Base64 formatted PowerAuth signature for given data.
     * @throws GenericCryptoException In case signature computation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    private String computePowerAuthBase64Signature(byte[] data, List signatureKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
        // Prepare array of bytes for a complete signature
        final byte[] signatureBytes = new byte[signatureKeys.size() * PowerAuthConfiguration.SIGNATURE_BINARY_LENGTH];
        // Compute signature components
        final List signatureComponents = computePowerAuthSignatureComponents(data, signatureKeys, ctrData);
        // Convert signature components into one Base64 encoded signature string
        for (int i = 0; i < signatureComponents.size(); i++) {
            final byte[] signatureComponent = signatureComponents.get(i);
            final int sourceOffset = signatureComponent.length - PowerAuthConfiguration.SIGNATURE_BINARY_LENGTH;
            final int destinationOffset = i * PowerAuthConfiguration.SIGNATURE_BINARY_LENGTH;
            System.arraycopy(signatureComponent, sourceOffset, signatureBytes, destinationOffset, PowerAuthConfiguration.SIGNATURE_BINARY_LENGTH);
        }
        // Finally, convert bytes into one Base64 string
        return BaseEncoding.base64().encode(signatureBytes);
    }

    /**
     * Compute PowerAuth signature for given data using a secret signature keys and counter byte array. The signature is returned
     * in form of list of binary components, where each item in returned array contains an appropriate signature factor. The returned
     * array must be then post-processed into the decimal, or Base64 format.
     *
     * @param data Data to be signed.
     * @param signatureKeys Keys for computing the signature.
     * @param ctrData Counter byte array / derived key index.
     * @return List with binary signature components.
     * @throws GenericCryptoException In case signature computation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    private List computePowerAuthSignatureComponents(byte[] data, List signatureKeys, byte[] ctrData) throws GenericCryptoException, CryptoProviderException {
        // Prepare a hash
        final HMACHashUtilities hmac = new HMACHashUtilities();

        // Prepare array for signature binary components.
        final List signatureComponents = new ArrayList<>();

        final KeyConvertor keyConvertor = new KeyConvertor();

        for (int i = 0; i < signatureKeys.size(); i++) {
            final byte[] signatureKey = keyConvertor.convertSharedSecretKeyToBytes(signatureKeys.get(i));
            byte[] derivedKey = hmac.hash(signatureKey, ctrData);

            for (int j = 0; j < i; j++) {
                byte[] signatureKeyInner = keyConvertor.convertSharedSecretKeyToBytes(signatureKeys.get(j + 1));
                byte[] derivedKeyInner = hmac.hash(signatureKeyInner, ctrData);
                derivedKey = hmac.hash(derivedKeyInner, derivedKey);
            }

            final byte[] signatureBytes = hmac.hash(derivedKey, data);
            // Test whether calculated signature has sufficient amount of bytes.
            if (signatureBytes.length < PowerAuthConfiguration.SIGNATURE_BINARY_LENGTH) { // assert
                throw new IndexOutOfBoundsException();
            }
            signatureComponents.add(signatureBytes);
        }

        return signatureComponents;
    }

    /**
     * Compute PowerAuth signature for given data using a secret signature keys and counter byte array.
     *
     * @param data Data to be signed.
     * @param signatureKeys Keys for computing the signature.
     * @param ctrData Counter byte array / derived key index.
     * @param format Format of signature to produce.
     * @return PowerAuth signature for given data.
     * @throws GenericCryptoException In case signature computation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    public String computePowerAuthSignature(byte[] data, List signatureKeys, byte[] ctrData, PowerAuthSignatureFormat format) throws GenericCryptoException, CryptoProviderException {
        if (signatureKeys == null) {
            throw new GenericCryptoException("Missing signatureKeys parameter");
        }
        if (ctrData == null) {
            throw new GenericCryptoException("Missing ctrData parameter");
        }
        if (signatureKeys.isEmpty() || signatureKeys.size() > PowerAuthConfiguration.MAX_SIGNATURE_KEYS_COUNT) {
            throw new GenericCryptoException("Wrong number of signature keys");
        }
        if (ctrData.length != PowerAuthConfiguration.SIGNATURE_COUNTER_LENGTH) {
            throw new GenericCryptoException("Invalid length of signature counter");
        }
        switch (format) {
            case BASE64:
                return computePowerAuthBase64Signature(data, signatureKeys, ctrData);
            case DECIMAL:
                return computePowerAuthDecimalSignature(data, signatureKeys, ctrData);
            default:
                throw new GenericCryptoException("Unsupported format of PowerAuth signature.");
        }
    }

    /**
     * Validate the PowerAuth signature for given data using provided keys.
     *
     * @param data Data that were signed.
     * @param signature Data signature.
     * @param signatureKeys Keys for signature validation.
     * @param ctrData Counter data.
     * @param format Format in which signature will be validated.
     * @return Return "true" if signature matches, "false" otherwise.
     * @throws GenericCryptoException In case signature computation fails.
     * @throws CryptoProviderException In case cryptography provider is incorrectly initialized.
     */
    public boolean validatePowerAuthSignature(byte[] data, String signature, List signatureKeys, byte[] ctrData, PowerAuthSignatureFormat format) throws GenericCryptoException, CryptoProviderException {
        return signature.equals(computePowerAuthSignature(data, signatureKeys, ctrData, format));
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy