Проверка подписи открытым ключом

У меня есть внешняя служба, которая перезванивает мне после определенного события и подписывает его запрос своим закрытым ключом.

Я сохранил открытый ключ, который выглядит так:

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

Итак, моя работа заключается в том, чтобы проверить, не было ли изменено содержимое запроса путем проверки подписи.

Вот мой алгоритм:

// 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( ) );

Но на данный момент мой алгоритм остановлен на шаге «PublicKey publicKey = keyFactory.generatePublic(publicKeySpec)» с помощью этого сообщения:

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

Итак, как я могу загрузить свой ключ так, чтобы он был принят java API?


person Manuel Leduc    schedule 27.07.2012    source источник
comment
Знаете ли вы, какой алгоритм шифрования использовала другая сторона для создания своего закрытого ключа? Сообщение об ошибке, похоже, говорит о том, что вы используете неправильный алгоритм шифрования или алгоритм не является частью API. В этом случае вам может понадобиться найти реализацию SHA1 с DSA. Но я думаю, что это уже должно быть там.   -  person Jose    schedule 27.07.2012
comment
В документации говорится о RSA, но, насколько мне известно, ему нельзя доверять, поэтому я пробовал и RSA, и DSA с одинаковым окончательным результатом исключения.   -  person Manuel Leduc    schedule 27.07.2012
comment
Привет, есть прогресс в этом? Я попытался скомпилировать ваш пример выше, но он не скомпилируется в пункте комментария 2. Не удается найти var publicKeyBytes.   -  person Jose    schedule 31.07.2012


Ответы (1)


На самом деле я нашел решение.

Проблема заключалась в том, чтобы правильно загрузить файл открытого ключа.

Я добавил библиотеку bouncycastle в свои зависимости:

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

Он предоставляет PemReader, который позволяет читать и загружать несертифицированные открытые ключи.

Вот мой служебный класс:

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 );
    }
}

Вам просто нужно передать подписанный контент, подпись и путь к ключу методу checkSign, и он сделает всю работу.

person Manuel Leduc    schedule 01.08.2012