Ключевое слово в защите информации
КЛЮЧЕВОЕ СЛОВО
в защите информации
Получить ГОСТ TLS-сертификат для домена (SSL-сертификат)
Добро пожаловать, Гость! Чтобы использовать все возможности Вход. Новые регистрации запрещены.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline Mikhail_Arbuzov  
#1 Оставлено : 29 ноября 2024 г. 7:53:51(UTC)
Mikhail_Arbuzov

Статус: Новичок

Группы: Участники
Зарегистрирован: 28.11.2024(UTC)
Сообщений: 6
Российская Федерация
Откуда: Новосибирск

Сказал(а) «Спасибо»: 3 раз
Здравствуйте! Возникает проблема при валидации RAW подписи. В нашем случае есть два модуля для работы с CryptoPro CSP, в одном код на JAVA, во втором код на C++
Подпись, сгенерированная в C++ коде, тут же успешно верифицируется функцией CryptVerifySignature, однако при верификации на JAVA, подпись проверку не проходит.
Подпись, сгенерированная в самом JAVA модуле, верификацию проходит.
Хотел бы попросить оценить, в чем может быть проблема, возможно в данных модулях используются разные подходы/алгоритмы или просто содержится ошибка при генерации подписи в модуле C++. На что стоило бы обратить внимание? Сертификат используется один и тот же, он валидный, не отозван, не просрочен.

В обоих случаях используется алгоритм GOST3410_2012_256

Прилагаю код на JAVA и C++, возможно внесёт какую-то ясность:

--JAVA--

/**
* Проверка сырой подписи
*
* @param request запрос на проверку подписи
*/
public VerifyResult verify(VerifySign request) throws SignerException {
LOGGER.debug("Проверяем сырую подпись");

Map<String, String> additionalParams = request.getParams();
if (additionalParams == null) {
throw new SignerException("Отсутствуют дополнительные параметры");
}

byte[] certificateInBytes = CertificateUtils.extractCertificateFromParams(additionalParams);
if (certificateInBytes == null) {
throw new SignerException("Сертификат не был передан внутри дополнительных параметров");
}

String algorithm = additionalParams.get(ALGORITHM_PARAM_NAME); // здесь algorithm == "GOST3410_2012_256"
if (StringUtils.isBlank(algorithm)) {
throw new SignerException("Алгоритм не был передан внутри дополнительных параметров");
}
String needReverse = additionalParams.get(NEED_SIGNATURE_REVERSE_PARAM_NAME);

//Ниже описана реализация RawSigner
RawSigner verifier = factory.get(getCertificate(certificateInBytes), signatureCertificateChecker);
verifier.setCrlDir(crlDir);
verifier.setKeyStore(keyStore);
verifier.setKeyStorePath(keyStorePath);

byte[] signature = ByteUtil.getBytes(request.getSign(), "signature");
byte[] message = ByteUtil.getBytes(request.getMessage(), "message");

if ("true".equalsIgnoreCase(needReverse)) {
signature = Arrays.reverse(signature);
}
verifier.verify(algorithm, signature, message);
LOGGER.debug("Сырая подпись проверена");
return VerifyResult.builder()
.errorCode(0)
.build();
}

public class VerifySign {

private SignType type;

private String message;

private String sign;

private Map<String, String> params;
}

public final class RawSigner extends AbstractSigner {

private static final int SERIAL_NUMBER_RADIX = 16;

private RawSigner(X509Certificate certificate, X509CertificateChecker signatureCertificateChecker) {
this.signatureCertificateChecker = signatureCertificateChecker;
this.certificate = certificate;
}

/**
* Проверка сырой подписи
*
* @param algorithm алгоритм подписи
* @param sign закодированная в base64 подпись
* @param message закодированное в base64 сообщение
* @throws SignerException ошибка при проверке подписи, либо подпись не прошла проверку
*/
public void verify(String algorithm, byte[] sign, byte[] message) throws SignerException {
try {
checkCertificate(certificate);
Signature signature = getSignature(AlgorithmUtils.getRawSignatureName(algorithm));
byte[] digest = SignatureUtil.digest(
message,
AlgorithmUtils.getDigestName(algorithm)
);
boolean verify = SignatureUtil.verify(certificate.getPublicKey(), signature, sign, digest);
if (!verify) {
String errorMessage = String.format(
"Подпись не прошла проверку. Полученный сертификат: %s, серийным номером: %s, издателем: %s",
certificate.getSubjectDN().getName(),
certificate.getSerialNumber().toString(SERIAL_NUMBER_RADIX),
certificate.getIssuerDN().getName()
);
throw new SignerException(errorMessage);
}
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Алгоритм {} не поддерживается", algorithm, e);
throw new SignerException(e);
}
}

public class SignatureUtil {

/**
* Метод вычисляющий дайджест по заданному алгоритму
*
* @param data данные из которых необходимо вычислить дайджест
* @param algorithm алгорим дайджеста
* @return вычисленный дайджест
* @throws SignerException ошибка, возникающая если переданный алгоритм не будет найден
*/
public byte[] digest(byte[] data, String algorithm) throws SignerException {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
digest.update(data);
return digest.digest();
} catch (NoSuchAlgorithmException ex) {
throw new SignerException(ex);
}
}

/**
* Проверка подписи
*
* @param publicKey публичный ключ
* @param signature подпись
* @param dataToVerify данные, которые необходимо проверить
* @param trustedData данные, на основании которых осуществляется проверка
* @return {@code true} если подпись прошла проверку успешно, {@code false} если подпись не прошла проверку
* @throws SignerException если при проверке или инициализации появилась ошибка
*/
public boolean verify(
PublicKey publicKey,
Signature signature,
byte[] dataToVerify,
byte[] trustedData
) throws SignerException {
try {
signature.initVerify(publicKey);
signature.update(trustedData);
return signature.verify(dataToVerify);
} catch (GeneralSecurityException ex) {
throw new SignerException("Ошибка проверки подписи: " + ex.getMessage(), ex);
}
}

-- C++ --

std::unique_ptr<RawSigner> RawSigner::MakeSigner(const ContainerParams& containerParams) {
std::unique_ptr<RawSigner> signer{new RawSigner(containerParams.alias, containerParams.password)};
BOOST_LOG_TRIVIAL(info) << "Получение контекста ключа для алиаса " << containerParams.alias;
signer->ConnectToContainer();
signer->ProcessKeys();
BOOST_LOG_TRIVIAL(info) << "Создание хеш объекта";
signer->CreateHashObject();
BOOST_LOG_TRIVIAL(info) << "Хеш объект создан";
return signer;
}

void RawSigner::ConnectToContainer() {
auto keyAliasBytes = this->keyAlias.c_str();
LPCSTR pszProvider = nullptr;
bool cryptAcquireContext = CryptAcquireContext(
&(this->hProv),
keyAliasBytes,
pszProvider,
PROV_GOST_2012_256,
CRYPT_SILENT);
ExceptionUtils::ThrowIfFalse(cryptAcquireContext, "Ошибка CryptAcquireContext");
BOOST_LOG_TRIVIAL(info) << "Контекст ключа получен";

BOOST_LOG_TRIVIAL(info) << "Установка пароля контейнера";
bool cryptSetProvParam = CryptSetProvParam(
this->hProv,
PP_KEYEXCHANGE_PIN,
(BYTE*) this->password.c_str(),
0);
ExceptionUtils::ThrowIfFalse(cryptSetProvParam, "Ошибка CryptSetProvParam");
BOOST_LOG_TRIVIAL(info) << "Установка пароля контейнера завершена";

}

void RawSigner::ProcessKeys() {
bool cryptGetUserKey = CryptGetUserKey(
this->hProv,
AT_SIGNATURE,
&this->hKey);
ExceptionUtils::ThrowIfFalse(cryptGetUserKey, "Ошибка CryptGetUserKey");
BOOST_LOG_TRIVIAL(info) << "Определение размера BLOB для публичного ключа";
DWORD dwBlobLen;
if (CryptExportKey(
this->hKey,
0,
PUBLICKEYBLOB,
0,
nullptr,
&dwBlobLen)) {
printf("Size of the BLOB for the public key determined. \n");
} else {
if (GetLastError() == (DWORD) SCARD_W_WRONG_CHV) {
throw DomainException("Неправильный пароль. CryptExportKey");
} else {
throw DomainException("Ошибка при вычислении размера BLOB. CryptExportKey");
}
}
this->pbKeyBlob = (BYTE*) malloc(dwBlobLen);
if (!this->pbKeyBlob)
throw DomainException("Out of memory. ExportKey. pbKeyBlob");

if (CryptExportKey(
this->hKey,
0,
PUBLICKEYBLOB,
0,
this->pbKeyBlob,
&dwBlobLen)) {
} else {
if (GetLastError() == (DWORD) SCARD_W_WRONG_CHV) {
throw DomainException("Неправильный пароль. CryptExportKey");
} else {
throw DomainException("Ошибка при вычислении размера BLOB. CryptExportKey");
}
}
BOOST_LOG_TRIVIAL(info) << "Размер BLOB для публичного ключа определён";
}

void RawSigner::CreateHashObject() {
DWORD cbHash;
bool cryptCreateHash = CryptCreateHash(
this->hProv,
CALG_GR3411_2012_256,
0,
0,
&this->hHash);
ExceptionUtils::ThrowIfFalse(cryptCreateHash, "Ошибка CryptCreateHash");

bool cryptGetHashParam = CryptGetHashParam(
this->hHash,
HP_OID,
nullptr,
&cbHash,
0
);
ExceptionUtils::ThrowIfFalse(cryptGetHashParam, "Ошибка CryptGetHashParam");

this->pbHash = (BYTE*) malloc(cbHash);
if (!this->pbHash)
throw DomainException("Out of memory. GetHashParam. pbHash");

cryptGetHashParam = CryptGetHashParam(
this->hHash,
HP_OID,
this->pbHash,
&cbHash,
0
);
ExceptionUtils::ThrowIfFalse(cryptGetHashParam, "Ошибка CryptGetHashParam");
}

SignResult RawSigner::Sign(const std::string& data) {
BYTE* pbBuffer = (BYTE*) &data[0];
DWORD dwSigLen = 0;
auto dwBufferLen = (DWORD) (strlen((char*) pbBuffer) + 1);

bool cryptHashData = CryptHashData(
this->hHash,
pbBuffer,
dwBufferLen,
0);
ExceptionUtils::ThrowIfFalse(cryptHashData, "Ошибка CryptHashData");

bool cryptSignHash = CryptSignHash(
this->hHash,
AT_SIGNATURE,
nullptr,
0,
nullptr,
&dwSigLen);
ExceptionUtils::ThrowIfFalse(cryptSignHash, "Ошибка вычисления размера dwSigLen CryptSignHash");

this->pbSignature = (BYTE*) malloc(dwSigLen);

cryptSignHash = CryptSignHash(
this->hHash,
AT_SIGNATURE,
nullptr,
0,
this->pbSignature,
&dwSigLen);

ExceptionUtils::ThrowIfFalse(cryptSignHash, "Ошибка CryptSignHash");

auto signature = base64_encode(this->pbSignature, dwSigLen);

bool result = CryptVerifySignature(hHash, this->pbSignature, dwSigLen, hKey, NULL, 0);
if (!result) {
throw DomainException{"Подпись не прошла валидацию"};
}
return SignResult::Success(signature);
}

RawSigner::~RawSigner() {
free(this->pbHash);
free(this->pbKeyBlob);
free(this->pbSignature);

if (this->hHash)
CryptDestroyHash(this->hHash);

if (this->hKey)
CryptDestroyKey(hKey);

if (this->hProv)
CryptReleaseContext(this->hProv, 0);
}

Offline Андрей *  
#2 Оставлено : 29 ноября 2024 г. 10:45:42(UTC)
Андрей *

Статус: Сотрудник

Группы: Участники
Зарегистрирован: 26.07.2011(UTC)
Сообщений: 13,743
Мужчина
Российская Федерация

Сказал «Спасибо»: 575 раз
Поблагодарили: 2305 раз в 1806 постах
Здравствуйте.

Приложите файл, сертификат и raw (файлами).

Сертификат можно получить тестовый,
http://testgost2012.cryptopro.ru/certsrv/


p.s.
инвертировать последовательность байт в ЭП пробовали?
Техническую поддержку оказываем тут
Наша база знаний
Offline Mikhail_Arbuzov  
#3 Оставлено : 2 декабря 2024 г. 7:52:26(UTC)
Mikhail_Arbuzov

Статус: Новичок

Группы: Участники
Зарегистрирован: 28.11.2024(UTC)
Сообщений: 6
Российская Федерация
Откуда: Новосибирск

Сказал(а) «Спасибо»: 3 раз
Сертификат: https://dropmefiles.com/j6N1c

Данные и подпись у нас в виде строк.

Данные: "SGVsbG9fd29ybGQ=",
Подпись: "SjZQmlnWa0Xw5Jqzsk0j2qjMygz+Zw2kpCH7EZZx9+6PeIokb+MwngXTNGvzFVWaZuuHZ922kcn1XvJE/LWHyg=="

Инвертировать байты пробовали, не помогло
Offline Санчир Момолдаев  
#4 Оставлено : 2 декабря 2024 г. 15:56:34(UTC)
Санчир Момолдаев

Статус: Сотрудник

Группы: Модератор, Участники
Зарегистрирован: 03.12.2018(UTC)
Сообщений: 1,225
Российская Федерация

Сказал(а) «Спасибо»: 101 раз
Поблагодарили: 291 раз в 271 постах
и с переворот и без подпись у меня не прошла проверку в java.

смотрите какие точно вы алгоритмы используете.

может быть такая ситуация: вы посчитали хэш
а потом использовали алгоритм вида HashAlgWithSignAlg
если используете хэши для создания подписи, то используйте алгоритмы вида RawWithSignAlg

p.s. в JCP алгоритмы GOST* вернут подпись в BigEndian
алгоритмы CryptoPro* вернут подпись в LittleEndian
в CSP всегда LittleEndian
Техническую поддержку оказываем тут
Наша база знаний
Offline Mikhail_Arbuzov  
#5 Оставлено : 3 декабря 2024 г. 11:13:20(UTC)
Mikhail_Arbuzov

Статус: Новичок

Группы: Участники
Зарегистрирован: 28.11.2024(UTC)
Сообщений: 6
Российская Федерация
Откуда: Новосибирск

Сказал(а) «Спасибо»: 3 раз
Подскажите, а есть ли примеры использования алгоритма вида RawWithSignAlg? Судя по коду, который я предоставил выше, это HashAlgWithSignAlg
Offline Санчир Момолдаев  
#6 Оставлено : 3 декабря 2024 г. 11:25:45(UTC)
Санчир Момолдаев

Статус: Сотрудник

Группы: Модератор, Участники
Зарегистрирован: 03.12.2018(UTC)
Сообщений: 1,225
Российская Федерация

Сказал(а) «Спасибо»: 101 раз
Поблагодарили: 291 раз в 271 постах
если считаете дайджест отдельно. то считаете с нужным алгоритмом.

потом массив байт хэша надо передать в метода update класса Signature. который был инстансцирован с каким-нибудь из Raw алгоритмов
https://docs.cryptopro.r...u/CryptoPro/JCP/JCP.html
ищите поиском Raw_

главное помнить что алгоритм хэширования должен соответствовать алгоритму подписи.
т.е. если хэширование GOST_DIGEST_2012_256_NAME
то подпись должна быть RAW_CRYPTOPRO_SIGN_2012_256_NAME или RAW_GOST_SIGN_2012_256_NAME
Техническую поддержку оказываем тут
Наша база знаний
Offline Санчир Момолдаев  
#7 Оставлено : 3 декабря 2024 г. 11:28:20(UTC)
Санчир Момолдаев

Статус: Сотрудник

Группы: Модератор, Участники
Зарегистрирован: 03.12.2018(UTC)
Сообщений: 1,225
Российская Федерация

Сказал(а) «Спасибо»: 101 раз
Поблагодарили: 291 раз в 271 постах
либо если вы все равно хэшируете исходный блоб данных
можете сразу передавать данные в update, только алгоритм должен быть ХЭШwithПОДПИСЬ.
пример можно тут посмотреть https://github.com/msham.../service/RawService.java
Техническую поддержку оказываем тут
Наша база знаний
Offline Mikhail_Arbuzov  
#8 Оставлено : 3 декабря 2024 г. 12:16:59(UTC)
Mikhail_Arbuzov

Статус: Новичок

Группы: Участники
Зарегистрирован: 28.11.2024(UTC)
Сообщений: 6
Российская Федерация
Откуда: Новосибирск

Сказал(а) «Спасибо»: 3 раз
Прошу прощения, не уточнил. А есть ли примеры RawWithSignAlg на С/С++ (CSP) ?

Отредактировано пользователем 3 декабря 2024 г. 12:51:51(UTC)  | Причина: Не указана

Offline Санчир Момолдаев  
#9 Оставлено : 3 декабря 2024 г. 14:27:12(UTC)
Санчир Момолдаев

Статус: Сотрудник

Группы: Модератор, Участники
Зарегистрирован: 03.12.2018(UTC)
Сообщений: 1,225
Российская Федерация

Сказал(а) «Спасибо»: 101 раз
Поблагодарили: 291 раз в 271 постах
в Java JCA/JCE.
в CSP CryptoAPI.
там просто указываете алгоритм хэширования и алгоритм подписи. никаких with нет

пример посмотрите в sdk csp
на линуксе это файл /opt/cprocsp/src/samples/CSP/SigningHash/SigningHash.c

по сути вызовы CryptHashData & CryptSignHash
Техническую поддержку оказываем тут
Наша база знаний
thanks 1 пользователь поблагодарил Санчир Момолдаев за этот пост.
Mikhail_Arbuzov оставлено 03.12.2024(UTC)
Offline Кирилл Соболев  
#10 Оставлено : 5 декабря 2024 г. 10:52:25(UTC)
Кирилл Соболев

Статус: Сотрудник

Группы: Участники
Зарегистрирован: 25.12.2007(UTC)
Сообщений: 1,733
Мужчина
Откуда: КРИПТО-ПРО

Поблагодарили: 177 раз в 168 постах
Кроме проблем, связанных с платформами (порядок байт и т.п), есть еще одна популярная причина таких ошибок – это передача разных исходных данных в функции подписи и проверки.
Навскидку – в С++ подписывается null-terminated строка (dwBufferLen = (DWORD) (strlen((char*) pbBuffer) + 1), а в java такого нет в принципе.
Так же непонятно как эти строки получаются, может быть проблема с кодировкой – например в C++ это ASCII, а в java UTF-8 и т.п.
Что можно сделать – сохраните подписываемые данные в файл в бинарном виде непосредственно перед подписью (pbBuffer с dwBufferLen в C++, message в java) и сравните, что получается.

Отредактировано пользователем 5 декабря 2024 г. 11:33:03(UTC)  | Причина: Не указана

Техническую поддержку оказываем тут
Наша база знаний
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Guest (2)
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.