Проверка подписи по ГОСТ Р 34.10-2001. Java.

Добрый день!

Имеется - ЭЦП по ГОСТ Р 34.10-2001 созданной "Рутокеном ЭЦП", открытый ключ, подписываемый хеш.
Требутеся - Исходя из имеющихся данных проверить ЭЦП.  Платформа Java.

Для реализации задачи использовалась библиотека Bouncy Castle. Данные для создания нужной элиптической кривой взяты из примеров, написанных на php и c#, Bouncy Castle предоставляет нужную кривую - "gostR3410_2001_CryptoPro_A". Данные для примера работы проверки подписи взяты из примера написанного на php.
Сам код:

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;

public class RuTokenVerificationTest {
    public static void main(final String[] args) throws NoSuchProviderException, NoSuchAlgorithmException,
            InvalidKeySpecException, InvalidKeyException, SignatureException, DecoderException {

        final String publicKey = "16E3585053A4BE8546FB3475F1CBDD7FF1A2C9BC886BD8C1E9214C2C2A4681226BFBA33C9F50F8F952091306C5BE17E5447D82F8EFBC0784E10234E7D7CA71A0";
        final String hash = "5D5FE1DD044A577C8B6580F49394CF4B4EF2D617C60C9AB6CDF2AC14BAB359C7";
        final String sign = "1B432A390D2871EEF2A4F4A5A607938DC4EBE6D2871A18133578F701851F37C22BE1AFE68F9FE586F36C626FABF9DFC316491742EC793388EFADDE81FE34F3DC";

        final BigInteger x = new BigInteger(publicKey.substring(0, 64), 16);
        final BigInteger y = new BigInteger(publicKey.substring(64), 16);

        Security.addProvider(new BouncyCastleProvider());

        final Signature sgr = Signature.getInstance("ECGOST3410", "BC");
        final KeyFactory f = KeyFactory.getInstance("ECGOST3410", "BC");

        final ECDomainParameters parameters = ECGOST3410NamedCurves.getByOID(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_A);
        final ECCurve curve = parameters.getCurve();

        final ECParameterSpec spec = new ECParameterSpec(curve, parameters.getG(), parameters.getN());
        final ECPublicKeySpec pubKey = new ECPublicKeySpec(curve.createPoint(x, y, false), spec);
        final PublicKey pk = f.generatePublic(pubKey);

        sgr.initVerify(pk);

        sgr.update(hash.getBytes());

        System.out.println(sgr.verify(Hex.decodeHex(sign.toCharArray())));
    }
}

Результат работы - false. Т.е. проверка не проходит. Никак не могу разобраться почему... Пробовал и реальные данные, созданные рутокеном, - тот же результат.
Может быть что-то в форматах данных, в частности, хеша, я верно делаю когда просто беру байтовый массив хеша и передаю его на проверку? Может его нужно представить в каком-то другом виде? Тоже самое с подписью. Помогите понять в чем ошибка.

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Добрый день! Попробуйте перед проверкой изменить порядок байт подписи на обратный.

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Не помогло.

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Подскажите, с помощью какого ПО сгенерирован и прочитан ключ, а также вычислены хэш и подпись?

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Те данные, что указаны в коде в первом сообщении темы - это данные из исходников тестовой площадки на PHP, файл token.php, в частности:

function token_test() {
        $Hash = '5D5FE1DD044A577C8B6580F49394CF4B4EF2D617C60C9AB6CDF2AC14BAB359C7';
        $Qx = '16E3585053A4BE8546FB3475F1CBDD7FF1A2C9BC886BD8C1E9214C2C2A468122';
        $Qy = '6BFBA33C9F50F8F952091306C5BE17E5447D82F8EFBC0784E10234E7D7CA71A0';
        $R = '1B432A390D2871EEF2A4F4A5A607938DC4EBE6D2871A18133578F701851F37C2';
        $S = '2BE1AFE68F9FE586F36C626FABF9DFC316491742EC793388EFADDE81FE34F3DC';
        if (token_verify($Hash, $Qx, $Qy, $R, $S)){
            echo "Sign valid\n";
        }else{
            echo "Sign not valid\n";
        }
}

Так же пробовал с данными, которые можно сгенерировать на тестовой площадке рутокен web, тут: http://php.rutokenweb.ru/api.php
Результат одинаков.

P.s. Если ренерить ключи с помощью того же Bouncy Castle для кривой gostR3410_2001_CryptoPro_A и работать с ними, то все хорошо, подпись и проверка проходят.

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Dzhinn пишет:

Не помогло.

То есть Вы пробовали передавать в качестве подписи 

final String sign = "DCF334FE81DEADEF883379EC42174916C3DFF9AB6F626CF386E59F8FE6AFE12BC2371F8501F7783513181A87D2E6EBC48D9307A6A5F4A4F2EE71280D392A431B"

, и проверка подписи не прошла?

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Алексей Караваев пишет:

То есть Вы пробовали передавать в качестве подписи 

final String sign = "DCF334FE81DEADEF883379EC42174916C3DFF9AB6F626CF386E59F8FE6AFE12BC2371F8501F7783513181A87D2E6EBC48D9307A6A5F4A4F2EE71280D392A431B"

, и проверка подписи не прошла?

Нет, я просто инвертировал последовательность байтов, проверка не прошла.
Данное значение

final String sign = "DCF334FE81DEADEF883379EC42174916C3DFF9AB6F626CF386E59F8FE6AFE12BC2371F8501F7783513181A87D2E6EBC48D9307A6A5F4A4F2EE71280D392A431B"

тоже не проходит проверку.

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Поскольку байт кодируется, вообще говоря, двумя шестнадцатиричными цифрами, то подпись с инвертированным порядоком байт должна иметь вид DCF334...2A431B.
Не могли бы Вы прислать пример данных (хэш, публичный ключ и подпись), для которых успешно проходит проверка подписи в используемой Вами библиотеке?

Re: Проверка подписи по ГОСТ Р 34.10-2001. Java.

Проблема решилась.
Дела были следующие:
1. Bouncy Castle создает и считывает публичный ключ в порядке "sr", а не "rs":

...
if (s[0] != 0)
{
    System.arraycopy(s, 0, sigBytes, 32 - s.length, s.length);
}
else
{
    System.arraycopy(s, 1, sigBytes, 32 - (s.length - 1), s.length - 1);
}

if (r[0] != 0)
{
    System.arraycopy(r, 0, sigBytes, 64 - r.length, r.length);
}
else
{
    System.arraycopy(r, 1, sigBytes, 64 - (r.length - 1), r.length - 1);
}
...

2. Перед процессами подписи и проверки подписываемые и проверяемые данные шифруются по стандарту ГОСТ 28147-89:

... 
// Encrypt gost28147-ECB
E(P(W), S, 0, H, 0); // s0 = EK0 [h0]
...

Поэтому-то проверки подписей созданных рутокеном не проходили.

Убрал из последовательности проверки подписи шифрование и инвертирование публичного ключа - заработало.

Если кому интересно, то вот код работающей программы:

import java.math.BigInteger;
import java.security.Security;

import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;

public class RuTokenVerificationTest {
    public static void main(final String[] args) {

        final String publicKey = "16E3585053A4BE8546FB3475F1CBDD7FF1A2C9BC886BD8C1E9214C2C2A4681226BFBA33C9F50F8F952091306C5BE17E5447D82F8EFBC0784E10234E7D7CA71A0";
        final String hash = "5D5FE1DD044A577C8B6580F49394CF4B4EF2D617C60C9AB6CDF2AC14BAB359C7";
        final String sign = "1B432A390D2871EEF2A4F4A5A607938DC4EBE6D2871A18133578F701851F37C22BE1AFE68F9FE586F36C626FABF9DFC316491742EC793388EFADDE81FE34F3DC";

        final BigInteger x = new BigInteger(publicKey.substring(0, 64), 16);
        final BigInteger y = new BigInteger(publicKey.substring(64), 16);
        final BigInteger r = new BigInteger(sign.substring(0, 64), 16);
        final BigInteger s = new BigInteger(sign.substring(64), 16);

        Security.addProvider(new BouncyCastleProvider());

        final ECDomainParameters parameters = ECGOST3410NamedCurves.getByOID(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_A);
        final ECCurve curve = parameters.getCurve();

        final ECParameterSpec spec = new ECParameterSpec(curve, parameters.getG(), parameters.getN());
        final ECPublicKeySpec pubKey = new ECPublicKeySpec(curve.createPoint(x, y, false), spec);
        final BigInteger n = parameters.getN();
        final BigInteger aprE = new BigInteger(hash, 16).mod(n);
        final BigInteger e = aprE.compareTo(ECConstants.ZERO) == -1 ? aprE.add(n) : aprE;

        if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) {
            System.out.println(false);
            return;
        }

        if (s.compareTo(ECConstants.ONE) < 0 || s.compareTo(n) >= 0) {
            System.out.println(false);
            return;
        }

        final BigInteger v = e.modInverse(n);
        final BigInteger z1 = s.multiply(v).mod(n);
        final BigInteger z2 = (n.subtract(r)).multiply(v).mod(n);
        final ECPoint G = parameters.getG();
        final ECPoint Q = pubKey.getQ();
        final ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2);

        if (point.isInfinity()) {
            System.out.println(false);
            return;
        }

        final BigInteger R = point.getX().toBigInteger().mod(n);

        System.out.println(r.equals(R));
    }
}

Спасибо за ответы.