Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verifying a signature with a public key

I have an external service which call me back after some defined event, and sign his request with its private key.

I have stored the public key which look like :

-----BEGIN PUBLIC KEY-----
........................................
-----END PUBLIC KEY-----

So my work is to check if request's content has not been alterned by verifying signature.

Here is my algorithm :

// 1 - reading public key :
Scanner scanner = new Scanner( new File( keyPath ) );


//            encodedPublicKey.toString( );
StringBuilder sb = new StringBuilder( );
while ( scanner.hasNextLine( ) )
{
    sb.append( scanner.nextLine( ) );
    sb.append( '\n' );
}

byte[] encodedPublicKey = sb.toString( ).getBytes( "utf-8" );

// 2 - loading public key in a relevant object :
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( publicKeyBytes );

KeyFactory keyFactory = KeyFactory.getInstance( "DSA" );

PublicKey publicKey = keyFactory.generatePublic( publicKeySpec );

// 3 - verifying content with signature and content :
Signature sig = Signature.getInstance( "SHA1withDSA" );
sig.initVerify( publicKey );
sig.update( message.getBytes( ) );
ret = sig.verify( sign.getBytes( ) );

But for now my algorithm is stoped at "PublicKey publicKey = keyFactory.generatePublic( publicKeySpec )" step by this message :

java.security.spec.InvalidKeySpecException: Inappropriate key specification: invalid key format

So how can I load my key in a way that is accepted by java api ?

like image 677
Manuel Leduc Avatar asked Jul 27 '12 15:07

Manuel Leduc


People also ask

Can you verify a signature with a public key?

The public key is associated with an owner and may be made public. In the case of digital signatures, the public key is used to verify a digital signature that was signed using the corresponding private key.

How can I check if a signature is valid?

Signature validity is determined by checking the authenticity of the signature's digital ID certificate status and document integrity: Authenticity verification confirms that the signer's certificate or its parent certificates exist in the validator's list of trusted identities.


1 Answers

Actually I've found the solution.

The problem was to load in the public key file in the right way.

I'va added bouncycastle library to my dependencies :

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15on</artifactId>
  <version>1.47</version>
</dependency>

It provides PemReader which allows to read and load non certificated public keys.

Here is my utility class :

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

import org.bouncycastle.util.io.pem.PemReader;
import org.castor.util.Base64Decoder;

import fr.paris.lutece.portal.service.util.AppLogService;


/**
 * Classe d'aide à l'interfacage avec le service paybox.
 *
 * Toutes les informations parameterables sont sous la forme paybox.*
 */
public final class PayboxUtil
{

    /** The Constant CHARSET. */
    private static final String CHARSET = "utf-8";

    /** The Constant ENCRYPTION_ALGORITHM. */
    private static final String ENCRYPTION_ALGORITHM = "RSA";

    /** The Constant HASH_ENCRIPTION_ALGORITHM. */
    private static final String HASH_ENCRYPTION_ALGORITHM = "SHA1withRSA";

    /**
     * constructeur privé pour classe statique.
     */
    private PayboxUtil(  )
    {
    }

    /**
     * Controle si une signature est bien celle du message à l'aide de la clé
     * publique de l'emmeteur?.
     *
     * @param message le message
     * @param sign la signature
     * @param keyPath le chemin vers la clé publique.
     * @return true si la signature est bien celle du message avec la clé privé
     *         attendue.
     */
    public static boolean checkSign( String message, String sign, String keyPath )
    {
        boolean ret = false;

        try
        {
            ret = PayboxUtil.verify( message, sign, PayboxUtil.getKey( keyPath ) );
        }
        catch ( final FileNotFoundException e )
        {
            AppLogService.error( e );
        }
        catch ( final IOException e )
        {
            AppLogService.error( e );
        }
        catch ( final NoSuchAlgorithmException e )
        {
            AppLogService.error( e );
        }
        catch ( final InvalidKeySpecException e )
        {
            AppLogService.error( e );
        }
        catch ( final InvalidKeyException e )
        {
            AppLogService.error( e );
        }
        catch ( final SignatureException e )
        {
            AppLogService.error( e );
        }

        return ret;
    }


    /**
     * Récupère la clé publique à partir du chemin passé en paramètre.
     *
     * @param keyPath le chemin vers la clé.
     * @return la clé publique
     * @throws NoSuchAlgorithmException the no such algorithm exception
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws InvalidKeySpecException the invalid key spec exception
     */
    private static PublicKey getKey( String keyPath )
        throws NoSuchAlgorithmException, IOException, InvalidKeySpecException
    {
        final KeyFactory keyFactory = KeyFactory.getInstance( PayboxUtil.ENCRYPTION_ALGORITHM );
        final PemReader reader = new PemReader( new FileReader( keyPath ) );
        final byte[] pubKey = reader.readPemObject(  ).getContent(  );
        final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( pubKey );

        return keyFactory.generatePublic( publicKeySpec );
    }

    /**
     * effectue la vérification du message en fonction de la signature et de la
     * clé.
     *
     * @param message le message
     * @param sign la signature
     * @param publicKey la clé publique.
     * @return true, if successful
     * @throws NoSuchAlgorithmException the no such algorithm exception
     * @throws InvalidKeyException the invalid key exception
     * @throws SignatureException the signature exception
     * @throws UnsupportedEncodingException the unsupported encoding exception
     */
    private static boolean verify( String message, String sign, PublicKey publicKey )
        throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException
    {
        final Signature sig = Signature.getInstance( PayboxUtil.HASH_ENCRYPTION_ALGORITHM );
        sig.initVerify( publicKey );
        sig.update( message.getBytes( PayboxUtil.CHARSET ) );

        final byte[] bytes = Base64Decoder.decode( URLDecoder.decode( sign, PayboxUtil.CHARSET ) );

        return sig.verify( bytes );
    }
}

You just have to pass signed content, signature and key path to checkSign method and it does all the work.

like image 187
Manuel Leduc Avatar answered Sep 29 '22 00:09

Manuel Leduc