Пользователь
0,0
рейтинг
15 января в 21:41

Разработка → Читаем контейнер закрытого ключа КриптоПро средствами OpenSSL

Речь пойдет о файлах primary.key, masks.key и header.key, которые лежат в директории ххххх.000 на флешке. Данные файлы входят в состав криптоконтейнера закрытого ключа электронной подписи криптопровайдера КриптоПро, формат которого нигде не опубликован. Целью данной статьи является чтение контейнера и преобразование закрытого ключа в формат, который может быть прочитан в библиотеке OpenSSL. Долгое время было распространено ошибочное суждение, что достаточно сделать нечто вида (primary_key XOR masks_key) и мы получим закрытый ключ в чистом (raw) виде, однако забегая вперед, можно утверждать, что в КриптоПро было применено более сложное преобразование, в худшем случае состоящее из более чем 2000 (двух тысяч) операций хеширования.

Стоит упомянуть о существовании утилиты P12FromGostCSP которая позволяет конвертировать ключ в формат P12, доступный для работы с OpenSSL, но утилита имеет следующие существенные недостатки:
  • Читает контейнер не напрямую, а через криптопровайдер, поэтому там, где кроме OpenSSL ничего нет, не работает.
  • Если в свойствах ключа не отмечено, что ключ «экспортируемый», то конвертировать его невозможно.
  • В демо версии не формирует файл с ключом, эта возможность присутствует только в платной версии.

Файл primary.key


Содержит 32 байта ключа в формате Asn1. Это только половина ключа, полный ключ получается при делении этого числа по модулю Q на маску. Поле, хранящее модуль Q в библиотеке OpenSSL имеет название order. Маска лежит в файле masks.key:

primary.key

Файл masks.key


Содержит 32 байта маски ключа в формате Asn1, зашифрованного на ключе хранения pwd_key. Далее 12 байт «затравочной» информации для генерации ключа хранения pwd_key, если криптоконтейнер защищен паролем, то пароль также участвует в генерации ключа хранения.

Далее контрольная сумма (имитозащита) 4 байта. Контрольной информацией для простоты мы пользоваться не будем, общий контроль будет осуществляться путем генерации открытого ключа и сравнения первых 8 байт полученного ключа с соответствующим полем из файла header.key:

masks.key

Файл header.key


Из этого файла нам потребуется параметры электронной подписи CryptoProParamSet (подчеркнуто красным).
  • GostR3410_2001_CryptoPro_A_ParamSet — 1.2.643.2.2.35.1
  • GostR3410_2001_CryptoPro_B_ParamSet — 1.2.643.2.2.35.2
  • GostR3410_2001_CryptoPro_C_ParamSet — 1.2.643.2.2.35.3
  • GostR3410_2001_CryptoPro_XchA_ParamSet — 1.2.643.2.2.36.0
  • GostR3410_2001_CryptoPro_XchB_ParamSet — 1.2.643.2.2.36.1

А также первые 8 байт открытого ключа (подчеркнуто) для контроля правильности чтения закрытого.

header.key

Читаем закрытый ключ и конвертируем


Файл privkey.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/pem.h>
#include <openssl/cms.h>
#include <openssl/err.h>
#include "gost_lcl.h"

/* Convert little-endian byte array into bignum */
BIGNUM *reverse32bn(char *b, BN_CTX *ctx)
{
	BIGNUM *res;
	char buf[32];
	BUF_reverse(buf, b, 32);
	res = BN_bin2bn(buf, 32, BN_CTX_get(ctx));
	OPENSSL_cleanse(buf, sizeof(buf));
	return res;
}

void xor_material(char *buf36, char *buf5C, char *src)
{
	int i;
	for(i = 0; i < 32; i++)
	{
		buf36[i] = src[i] ^ 0x36;
		buf5C[i] = src[i] ^ 0x5C;
	}
}

int make_pwd_key(char *result_key, char *start12, int start12_len, char *passw)
{
	int result;
	int i;
	char pincode4[1024];
	int pin_len;
	char current[32];
	char material36[32];
	char material5C[32];
	char hash_result[32];
	gost_hash_ctx ctx;
	init_gost_hash_ctx(&ctx, &GostR3411_94_CryptoProParamSet);
	memset(pincode4, 0, sizeof(pincode4));
	pin_len = strlen(passw);
	if (pin_len*4 > sizeof(pincode4)) {	result = 1;	goto err; }
	for(i = 0; i < pin_len; i++)
		pincode4[i*4] = passw[i];

	start_hash(&ctx);
	hash_block(&ctx, start12, start12_len);
	if (pin_len) 
		hash_block(&ctx, pincode4, pin_len * 4);
	finish_hash(&ctx, hash_result);

	memcpy(current, (char*)"DENEFH028.760246785.IUEFHWUIO.EF", 32);

	for(i = 0; i < (pin_len?2000:2); i++)
	{
		xor_material(material36, material5C, current);
		start_hash(&ctx);
		hash_block(&ctx, material36, 32);
		hash_block(&ctx, hash_result, 32);
		hash_block(&ctx, material5C, 32);
		hash_block(&ctx, hash_result, 32);
		finish_hash(&ctx, current);
	}

	xor_material(material36, material5C, current);

	start_hash(&ctx);
	hash_block(&ctx, material36, 32);
	hash_block(&ctx, start12, start12_len);
	hash_block(&ctx, material5C, 32);
	if (pin_len) 
		hash_block(&ctx, pincode4, pin_len * 4);
	finish_hash(&ctx, current);

	start_hash(&ctx);
	hash_block(&ctx, current, 32);
	finish_hash(&ctx, result_key);

	result = 0; //ok
err:
	return result;
}

BIGNUM *decode_primary_key(char *pwd_key, char *primary_key, BN_CTX *bn_ctx)
{
	BIGNUM *res;
	char buf[32];
	gost_ctx ctx;
	gost_init(&ctx, gost_cipher_list->sblock);
	gost_key(&ctx, pwd_key);
	gost_dec(&ctx, primary_key, buf, 4);
	res = reverse32bn(buf, bn_ctx);
	OPENSSL_cleanse(buf, sizeof(buf));
	return res;
}

BIGNUM *remove_mask_and_check_public(char *oid_param_set8, BIGNUM *key_with_mask, BIGNUM *mask, char *public8, BN_CTX *ctx)
{
	int result;
	EC_KEY *eckey = NULL;
	const EC_POINT *pubkey;
	const EC_GROUP *group;
	BIGNUM *X, *Y, *order, *raw_secret, *mask_inv;
	char outbuf[32], public_X[32];
	ASN1_OBJECT *obj;
	int nid;

	order = BN_CTX_get(ctx);
	mask_inv = BN_CTX_get(ctx);
	raw_secret = BN_CTX_get(ctx);
	X = BN_CTX_get(ctx);
	Y = BN_CTX_get(ctx);
	if (!order || !mask_inv || !raw_secret || !X || !Y) { result = 1; goto err; }

	obj = ASN1_OBJECT_create(0, oid_param_set8+1, *oid_param_set8, NULL, NULL);
	nid = OBJ_obj2nid(obj);
	ASN1_OBJECT_free(obj);

	if (!(eckey = EC_KEY_new())) { result = 1; goto err; }
	if (!fill_GOST2001_params(eckey, nid)) { result = 1; goto err; }
	if (!(group = EC_KEY_get0_group(eckey))) { result = 1; goto err; }
	if (!EC_GROUP_get_order(group, order, ctx)) { result = 1; goto err; }

	if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; }
	if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; }

	if (!EC_KEY_set_private_key(eckey, raw_secret)) { result = 1; goto err; }
	if (!gost2001_compute_public(eckey)) { result = 1; goto err; }
	if (!(pubkey = EC_KEY_get0_public_key(eckey))) { result = 1; goto err; }
	if (!EC_POINT_get_affine_coordinates_GFp(group, pubkey, X, Y, ctx)) { result = 1; goto err; }

	store_bignum(X, outbuf, sizeof(outbuf));
	BUF_reverse(public_X, outbuf, sizeof(outbuf));
	if (memcmp(public_X, public8, 8) != 0) { result = 1; goto err; }

	result = 0; //ok
err:
	if (eckey) EC_KEY_free(eckey);
	if (result == 0) return raw_secret;
	return NULL;
}

int file_length(char *fname)
{
	int len;
	FILE *f = fopen(fname, "rb");
	if (f == NULL) return -1;
	fseek(f, 0, SEEK_END);
	len = ftell(f);
	fclose(f);
	return len;
}

int read_file(char *fname, int start_pos, char *buf, int len)
{
	int read_len;
	FILE *f = fopen(fname, "rb");
	if (f == NULL) return 1;
	if (start_pos) fseek(f, start_pos, SEEK_SET);
	read_len = fread(buf, 1, len, f);
	fclose(f);
	if (read_len != len) return 1;
	return 0; //ok
}

int get_asn1_len(unsigned char *buf, int *size_hdr)
{
	int n, i, res;
	int pos = 0;
	if ((buf[pos]&0x80) == 0) {
		*size_hdr = 1;
		return buf[pos];
	}
	n = buf[pos++]&0x7f;
	res = 0;
	for(i = 0; i < n; i++) {
		res = res*256 + buf[pos++];
	}
	*size_hdr = n+1;
	return res;
}

#define MAX_HEADER 20000
int read_container(char *fpath, int flag2, char *salt12, char *primary_key, char *masks_key, char *public8, char *oid_param_set8)
{
	int result;
	char primary_path[1024+30];
	char masks_path[1024+30];
	char header_path[1024+30];
	char header_buf[MAX_HEADER];
	int header_len;
	int i, len, pos, size_hdr;

	if (strlen(fpath)>1024) { result = 1; goto err; }

	sprintf(header_path, "%s/header.key", fpath);
	if (flag2 == 0)
	{
		sprintf(primary_path, "%s/primary.key", fpath);
		sprintf(masks_path, "%s/masks.key", fpath);
	}
	else
	{
		sprintf(primary_path, "%s/primary2.key", fpath);
		sprintf(masks_path, "%s/masks2.key", fpath);
	}

	if (read_file(primary_path, 4, primary_key, 32)) { result = 1; goto err; }
	if (read_file(masks_path, 4, masks_key, 32)) { result = 1; goto err; }
	if (read_file(masks_path, 0x26, salt12, 12)) { result = 1; goto err; }

	header_len = file_length(header_path);
	if (header_len < 0x42 || header_len > MAX_HEADER) { result = 1; goto err; }
	if (read_file(header_path, 0, header_buf, header_len)) { result = 1; goto err; }

//------------- skip certificate ---------------------------
	pos = 0;
	for(i = 0; i < 2; i++)
	{
		get_asn1_len(header_buf+pos+1, &size_hdr);
		pos += size_hdr+1;
		if (pos > header_len-8) { result = 2; goto err; }
	}

//------------------ get oid_param_set8 -----------------------
#define PARAM_SET_POS 34
	if (memcmp(header_buf+pos+PARAM_SET_POS, "\x6\x7", 2) != 0) { result = 2; goto err; }
	memcpy(oid_param_set8, header_buf+pos+PARAM_SET_POS+1, 8);

//------------------ get public8 -----------------------
	result = 2; //not found
	pos += 52;
	for(i = 0; i < 3; i++)
	{
		len = get_asn1_len(header_buf+pos+1, &size_hdr);
		if (len == 8 && memcmp(header_buf+pos, "\x8a\x8", 2) == 0)
		{
			memcpy(public8,header_buf+pos+2,8);
			result = 0; //ok
			break;
		}
		pos += len+size_hdr+1;
		if (pos > header_len-8) { result = 2; goto err; }
	}
err:
	OPENSSL_cleanse(header_buf, sizeof(header_buf));
	return result;
}

#define START_OID 0x12
#define START_KEY 0x28
unsigned char asn1_private_key[72] = {
	0x30,0x46,2,1,0,0x30,0x1c,6,6,0x2a,0x85,3,2,2,0x13,0x30,0x12,6,7,0x11,
	0x11,0x11,0x11,0x11,0x11,0x11,6,7,0x2a,0x85,3,2,2,0x1e,1,4,0x23,2,0x21,0
};

int main(int argc, char **argv)
{
	int result;
	char *container_path;
	char *passw;
	char salt12[12];
	char primary_key[32];
	char masks_key[32];
	char public8[8];
	char oid_param_set8[8];
	BN_CTX *ctx;
	BIGNUM *key_with_mask;
	BIGNUM *mask;
	BIGNUM *raw_key;
	char pwd_key[32];
	char outbuf[32];

	ctx = BN_CTX_new();

	if (argc == 2)
	{
		container_path = argv[1];
		passw = "";
	}
	else
	if (argc == 3)
	{
		container_path = argv[1];
		passw = argv[2];
	}
	else
	{
		printf("get_private container_path [passw]\n");
		result = 1;
		goto err;
	}

	if (read_container(container_path, 0, salt12, primary_key, masks_key, public8, oid_param_set8) != 0 &&
		read_container(container_path, 1, salt12, primary_key, masks_key, public8, oid_param_set8) != 0)
	{
		printf("can not read container from %s\n", container_path);
		result = 2;
		goto err;
	}

	make_pwd_key(pwd_key, salt12, 12, passw);
	key_with_mask = decode_primary_key(pwd_key, primary_key, ctx);
	OPENSSL_cleanse(pwd_key, sizeof(pwd_key));
	mask = reverse32bn(masks_key, ctx);
	raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx);

	if (raw_key)
	{
		BIO *bio;
		store_bignum(raw_key, outbuf, sizeof(outbuf));
		memcpy(asn1_private_key+START_OID, oid_param_set8, 8);
		memcpy(asn1_private_key+START_KEY, outbuf, 32);
		//bio = BIO_new_file("private.key", "w");
		bio = BIO_new_fp(stdout, BIO_NOCLOSE | BIO_FP_TEXT);
		PEM_write_bio(bio, "PRIVATE KEY", "", asn1_private_key, sizeof(asn1_private_key));
		BIO_free(bio);
		OPENSSL_cleanse(outbuf, sizeof(outbuf));
		OPENSSL_cleanse(asn1_private_key, sizeof(asn1_private_key));
		result = 0; //ok
	}
	else
	{
		printf("Error check public key\n");
		result = 3;
	}

err:
	BN_CTX_free(ctx);
	OPENSSL_cleanse(salt12, sizeof(salt12));
	OPENSSL_cleanse(primary_key, sizeof(primary_key));
	OPENSSL_cleanse(masks_key, sizeof(masks_key));
	return result;
}


Небольшой комментарий.

Основную работу выполняют следующие 3 функции:

1. Создаем ключ хранения исходя из 12-ти байтовой «соли» и пароля.
make_pwd_key(pwd_key, salt12, 12, passw);

2. Расшифровываем основной ключ на ключе хранения.
key_with_mask = decode_primary_key(pwd_key, primary_key, ctx);

3. Делим ключ с маской на маску.
raw_key = remove_mask_and_check_public(oid_param_set8, key_with_mask, mask, public8, ctx);

Но так как в библиотеке OpenSLL операция деления по модулю традиционно отсутствует, пользуемся операцией взятия обратного числа и умножением.
if (!BN_mod_inverse(mask_inv, mask, order, ctx)) { result = 1; goto err; }
if (!BN_mod_mul(raw_secret, key_with_mask, mask_inv, order, ctx)) { result = 1; goto err; }


Сборка утилиты конвертирования ключа


Далее сборка исходников описана для Linux версии.

Версию для Windows можно скачать отсюда там же есть сертификаты и закрытый ключ для тестирования, для сборки потребуется бесплатный компилятор Borland C++ 5.5

Компиляция OpenSSL библиотеки


После скачивания и распаковки исходных текстов openssl в целевой директории выполняем команды:

./config
make

Получаем готовую библиотеку libcrypto.a в текущей директории.
Также потребуются заголовочные файлы из директорий engines/ccgost и include.

Компиляция privkey.c


gcc -o privkey -Iengines/ccgost -Iinclude privkey.c libcrypto.a -pthread -ldl

Формирование файла закрытого ключа private.key


./privkey /mnt/usbflash/lp-9a0fe.000

Тестовый закрытый ключ в криптоконтейнере lp-9a0fe.000, сертификат открытого ключа signer.cer и другие файлы для тестирования можно взять отсюда

Получаем результат работы:
-----BEGIN PRIVATE KEY-----
MEYCAQAwHAYGKoUDAgITMBIGByqFAwICJAAGByqFAwICHgEEIwIhAKzsrv/l1Uwk
uzph/LQN9mux0Jz0yaW21kOYEFv0Xyut
-----END PRIVATE KEY-----

Cохраняем в private.key

Пользуемся закрытым ключом private.key для подписывания файла file.txt


openssl cms -sign -inkey private.key -in file.txt -CAfile CA.cer -signer signer.cer -engine gost -out test.sign -outform DER -noattr -binary

Проверяем подпись


openssl cms -verify -content file.txt -in test.sign -CAfile CA.cer -signer signer.cer -engine gost -inform DER -noattr -binary

Все работает просто замечательно!

Спасибо за внимание. Это была моя первая статья на хабре.
Михаил Куликов @shukan
карма
23,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (41)

  • +1
    очень круто!
  • +3
    Достойный ответ проприентарному криптопровайдеру!
  • +1
    Но остаётся открытым вопрос о том, как без «проприетарного криптопровайдера» (как сказали выше) вытащить со «свистка» криптоконтейнер (потому что далеко не все из предоставляемых «свистков» (если честно, я вообще ни с одним таким не сталкивался) монтируются как флешки, чтобы была возможность просто взять и добраться до /mnt/bla/*.000
    • 0
      Вообще в нормальном криптотокене ключи лежат в защищённом хранилище, специально спроектированном так, чтобы вытащить их было нельзя, а все криптооперации выполняются самим токеном. Если у вас такой — за разумные деньги никак не вытащить.
      • –4
        Позвольте не согласиться: вытащить можно и даже нужно (ну хотя бы для резервной копии). Делается это на машине с установленным КриптоПро CSP + драйвера токена:
        запускаем КриптоПро CSP (в Windows cpconfig.cpl);
        на вкладке Сервис выбираем кнопку Скопировать...;
        источник токен, приемник флешка (могут быть проблемы, если не установлен считыватель «Все съемные диски»).
        С флешки можно спокойно пересылать контейнер хоть по мылу.
        • +4
          Если используется реальный токен (хоть тот же рутокен эцп или etoken), а не поделие на основе флешки, то вытащить оттуда приватный ключ — крайне сложная деструктивная операция (микросхема, на которой хранится ключ, будет физически разрушена).
          • –6
            Боюсь, что вы плохо представляете, что любое обращение к ключу — это чтение даже из самого супер защищенного хранилища, операция копирования требует только чтения. Дальше додумаетесь сами?
            • +5
              Криптотокен часто не предоставляет операции чтения. Он предоставляет операции типа хэширования, подписи и дешифрования (всё это выполняется на токене). Плюс управление приватными ключами и сертификатами, которое в случае ключей часто сводится к «сгенерировать ключ», «записать ключ» (с хоста на токен). Посмотрите на интерфейс pkcs11 или pcsc.
            • +3
              Я разрабатывал подобные «крипто-свистки» и архитектура была спроектирована таким образом, что исключалась возможность чтения ключей ЭП даже инвазивными методами, не говоря уж о программных. Это закладывалось в топологию микроконтроллера. Вот) И запросы библиотеки криптоинтерфейсов анализировались строжайшим образом перед их выполнением) Компания «Крипто-Про» и ее партнеры заботятся о своих ключах не хуже (в серьезных продуктах, конечно). Так что, на мой взгляд, легче «купить» владельца или прибегнуть к «термо-криптоанализу», чем пытаться вытащить ключ из серьезного крипто-свистка.
              • 0
                исключается возможность или делается очень дорогой?
                • 0
                  делает возможность настолько дорогой, что «овчинка выделки не стоит»)
                  • 0
                    Неужели нет секретной команды «выдай мне все ключи»? Неужели ФСБ такое сертифицирует? )
            • +3
              Боюсь, что вы плохо разбираетесь в предмете и осмеливаетесь других в этом обвинять. Вам не приходило в голову, что можно закачать в токен информацию, которую надо зашифровать или подписать, и получить из токена зашифрованную инфу или подпись? и тогда ключу нет нужды покидать пределы носителя. Так работают нормальные токены и смарт-карты. Потому они и смарт.
        • 0
          Существуют разные способы хранения ключей на токене (в том числе и среди продуктов КриптоПро). В предыдущем комментарии речь шла о неизвлекаемых ключах.
        • 0
          там еще есть галочка, при создании ключа «возможность тиражирования» или как то так. Если правильно помню — на то, чтобы поставить такую галочку при генерации ЭЦП для электронных торгов с меня хотели еще +1000 рублей. Без этой галочки ключ с рутокена просто не копируется.
    • +2
      Прошу прощения, а зачем? Токены, в принципе, для того и существуют, чтобы из них ничего нельзя было вытащить.
      • –1
        Зачем? Дерзните сделать гостовую подпись в браузере под линуксом с помощью такого хардварного токена.
        • +2
          Токена у меня нет, но вроде как на форуме вполне себе инструкции приводятся.
          • 0
            Есть некоторая беда с этими токенами. Заключается она в том, что поддержка этих токенов должна быть сделана на сайте с помощью плагина, коих сейчас уже небольшой зоопарк. Сделать функционал ЭП по ГОСТу с помощью стандартных механизмов браузера у таких токенов увы нельзя. С плагинами есть другая беда: большая часть плагинов используют NP API, который выпилен из Хрома и вот-вот выпилят из Firefox.

            Под виндой работа с данными токенами тоже не самый удобный вариант, так как при работе с ними через Crypto Pro или другое популярное СКЗИ данный токен превращается в тыкву флэшку. Работа Microsoft Crypto API поддержана только для RSA части этого токена. А вот функционал ГОСТовских функций по аппаратной подписи надо тянуть через PKSC#11, но программ, которые поддерживают такое под виндой в дополнение к MS Crypto API днём с огнём.
    • 0
      Как флешка монтируется обычная флешка с контейнером в виде отдельной папки. В общем-то это самый популярный и самый доступный тип токена в случае с Крипто-ПРО. И с него-то вытащить ключи сложнее всего, потому что формат хранения -проприетарный.

      С настоящими токенами гораздо легче — есть pcsk11-tool, есть, в конце концов, утилиты от производителей токенов. Если ключ сгенерирован на токене как неэкспортируемый, то, естественно, его ничем не вытащишь, но, как правило, ключ делают выгружаемым, чтобы можно было сделать копию, потому что вероятность использования ключа злоумышленниками намного ниже, чем вероятность его, например, утраты или поломки.

      Так что автору — респект.
      • +2
        как правило, ключ делают выгружаемым, чтобы можно было сделать копию
        Ужас какой. Кто и где делает ключ выгружаемым? В нормальных компаниях с «утратой или поломкой» борются просто: упрощением регистрации нового ключа. В идеале — это должен иметь возможность сделать сам клиент без участия службы поддержки — примерно так.

        Самое дебильное — что ведь кто-то что-то там ведь проверяет, сертифицирует, разбирает. Куча денег вертится. Но при этом самое элементарное — похоже никто не проверяет. Зачем тогда всё это? То есть я понимаю, что для большинства людей «безопасность — это такая галочка, без которой клиенты не придут/договор не подпишут», а как оно там реально защищает ваши данные — неважно, но, чёрт побери, всему же есть предел!

        Обнаружение экспортируемого ключа в продакшене — это же ЧП. Кто-то что-то настолько не понимает, что «с дуба рухнул» и решил дискредитировать всю идею аппаратных токенов… А тут оказыватся, что «как правило ключ делают выгружаемым»… грустно это…
        • 0
          это вы просто с работой со СМЭВ не сталкивались…
          • +5
            Не сталкивался. И проводку у себя в доме не я монтировал. И даже систему гидроусиления руля я не чинил. Но что это, собственно, меняет?..

            Обратная сторона российской «смекалки»: если кто-то разработал систему, в которой что-то делать неудобно, элементарная операция регистрации дополнительного ключа (которая должна быть элементарной и бесплатной) требует кучу времени и сил и/или денег, то мы это как-нибудь обойдём. «Жучок» поставим чтобы пробки не горели, датчик кувалдой забьём, если он на свое место не лезет, ключи экспортируемыми оставим. Всё — из самых лучших побуждений.

            А потом — дома горяд, ракеты падают, деньги пропадают.

            Смысл существования криптотокена — в неизвлекаемости помещённых в него ключей. Это, собственно, единственное оправдание его сущесвования. Использовать экспортируемые ключи вне разработки — должно быть как минимум поводом для увольнения за халатность, а как максимум — и для привлечения к отвественности (в зависимости от того кому и сколько таких ключей было выдано).

            А то над паролями, записанными на бумажечке и приклеенными к монитору — все смеются. А когда ключи выдаются с PINом 12345678 и с экспортируемыми ключами — это как будто «так и надо»…
        • +1
          Я вам даже больше скажу. На сегодняшний день большая часть УЦ использует в работе софт КриптоПро, который позволяет создавать пары ключей только на убогих токенах. Более того, подавляющая часть УЦ не даёт (своим регламентом) клиентам возможности получить сертификат по запросу сертификата.

          Таким образом, всё гораздо хуже, потому что приватный ключ генерирует УЦ. Причем он может его скопировать себе в этот момент.

          Если вы действительно знаете, что такое безопасность, то, конечно, потратите время и найдете УЦ, который работает с запросами. Но большинство УЦ, а стало быть и их клиентов живут вот так вот.
      • +1
        Для чего вообще нужно платить деньги за такой ключ? Купить самую дешёвую флешку и всё. В Казахстане, например, так и есть. Ты получаешь сертификат от государства для получения госуслуг. При получении можно либо принести свою флешку, либо дать удостоверение, в котором встроен криптотокен (но нужен специальный ридер), либо заплатить деньги и получить USB-токен. Покупать втридорога флешку — странно ведь. Или это обусловлено этим софтом, который читает ключи только с этой брендовой флешки?
        • 0
          Это обусловлено требованиями к безопасности. Приватный ключ с криптоключа вынуть нельзя (теоретически — даже с использованием электронного микроскопа нельзя, практически — как минимум нельзя это сделать с помощью «подручных» средств).

          У меня, скажем, подобный ключик вставлен раз и навсегда в ноут, в котором и ездит. Пока он находится в моём распоряжении — всё, что с его помощью производится — это моя ответственность и мои проблемы (если я его утеряю или, скажем, таможенники заберут ноут и отправят «на проверку» — я должен немедленно об этом сообщить, чтобы ключ аннулировали).

          Главное: так как ключ скопировать нельзя, то никакие «отмазки» и «происки конкурентов» во внимание не принимаются: ключ был у меня в руках — значит я за то, что произошло с его помощью и отвечаю.

          Какой смысл класть на такой ключ экспортируемый сертификат — науке, понятно, неведомо. В этом смысле он работает как обычная флешка и никакого преимущества над оной не имеет.
          • 0
            У меня, скажем, подобный ключик вставлен раз и навсегда в ноут, в котором и ездит.
            Я такого избегаю, вставляю только когда надо что-нибудь подписать. У yubikey был security bug в openpgp-апплете, который позволял производить операции без пина.
            • +1
              Этот security bug можно поиспользовать только «позаимствовав» мой ноут на какое-то время и разблокировав его. То есть это могут сделать только «свои» (если у меня украдут ноут и я ключ аннулирую, то дальше уже неважно — знает ли кто-то PIN или нет). А со «своими» бороться сложнее — они могут и троян поставить, если я куда-нибудь отойду и забуду ноут заблокировать.

              Так что да — вынимать ключик безопаснее, но это уже «o малое». В любом случае нужно у меня своровать некопируемый ключ. А вот если ключ из токена экспортируется — тогда вообще неясно зачем он нужен.
              • 0

                Ну да, это требует компрометации системы. И вариант с трояном принципиального отличия не имеет. Разве что в нормальном варианте необходимо ещё перехватить ещё PIN (проанализировать выхлоп кейлоггера), а при использовании соответствующего бага это не нужно. Но и тот, и другой риск стоит рассматривать при угрозе таргетированной атаки, когда эта разница в стоимости атаки уже несущественна.


                Так что да — вынимать ключик безопаснее, но это уже «o малое». В любом случае нужно у меня своровать некопируемый ключ. А вот если ключ из токена экспортируется — тогда вообще неясно зачем он нужен.

                Этого я тоже не понимаю. Выглядит ещё более дико, нежели ключи на флешке, которые просто от бедности и нежелания осуществлять поддержку более сложной системы с криптопровайдером.

                • 0
                  Этого я тоже не понимаю. Выглядит ещё более дико, нежели ключи на флешке, которые просто от бедности и нежелания осуществлять поддержку более сложной системы с криптопровайдером.
                  Если бы. Вы, конечно, шутите, Мистер Фейнман! — читали? Вспомните про историю с Капитаном (ниже — маленькая цитата):
                  Кабинет Капитана помещался на третьем этаже одного из тех собранных на скорую руку деревянных зданий, в которых у всех нас были кабинеты, а заказанный им сейф был железный и тяжелый. Такелажникам пришлось делать деревянные помосты и использовать специальные тали, чтобы поднять его по лестнице. Так как развлечений у нас было немного, все мы собрались поглазеть на великие усилия, с которыми сейф тащили в кабинет Капитана, и похихикать насчет секретов, которые он будет хранить в этом сейфе. Кто-то даже предлагал махнуться сейфами. Словом, об этом сейфе знали все.



                  – Но ты же открыл его! Значит, ты знаешь, как вскрывать сейфы.

                  – О да. Я знаю, что замки приходят с завода установленными на 25–0–25 или на 50–25–50, и я подумал: «Чем черт не шутит. Может этот олух не потрудился сменить комбинацию», и вторая комбинация открыла замок.

                  Итак, кое-что я от него все же узнал, – он открывал сейфы тем же чудодейственным способом, что и я. Но занятнее всего все же было то, что этот индюк Капитан получил супер-суперсейф, не постеснялся заставить пыхтеть кучу народа, которая тащила его сейф наверх, а потом даже не позаботился установить свою комбинацию.

                  Я прошелся по кабинетам своего здания, пробуя эти две заводские комбинации, и открыл каждый пятый сейф.
                  То же самое и здесь: люди думают, что использование «настоящего криптотокена» каким-то чудодейственным образом улучшает безопасность — даже если не использовать фичи, ради которых, собственно, криптотокен и существует.

                  Это — безумно грустно, но, увы, люди всегда так были устроены…
                  • 0
                    Я тоже никогда не понимал, зачем нужен криптотокен, если любой мало-мальски продвинутый троян при первом же доступе к токену со стороны легального софта, со вводом пина, может послать токену команду сделать левую ЭП?

                    Тогда сделали бы разработчики хотя бы кнопку — на токене загорается красная лампочка, что значит большой компьютер просит вычислить ЭП, пользователь видит лампочку и нажимает кнопку, соглашаясь произвести подпись. При этом должна быть сделана только одна ЭП, нужно исключить дребезг кнопки и т.д.

                    А если в дополнение к кнопке сделать небольшой экран, где будут подсвечены реквизиты документа, хотя бы минимально получатель платежа и сумма — это будет просто замечательно, тогда троянец сможет надеяться только на невнимательность пользователя.
                    • +1
                      Тогда сделали бы разработчики хотя бы кнопку — на токене загорается красная лампочка, что значит большой компьютер просит вычислить ЭП, пользователь видит лампочку и нажимает кнопку, соглашаясь произвести подпись. При этом должна быть сделана только одна ЭП, нужно исключить дребезг кнопки и т.д.
                      Так и сделано — посмотрите на вторую фотку. Лампочка, правда, зелёная и кнопки нет (поднесение пальца определяется за счёт изменения индуктивности при касании), а так — всё как вы просили.

                      А если в дополнение к кнопке сделать небольшой экран, где будут подсвечены реквизиты документа, хотя бы минимально получатель платежа и сумма — это будет просто замечательно, тогда троянец сможет надеяться только на невнимательность пользователя.
                      Такое тоже делается, но обычно только для перевода крупных сумм. Есть токены, вообще электрически не связанные с компьютером (данные передаются через пару светодиодов, подтверждение появляется на экранчике, четыре цифры человек потом вбивает в формочку). Любой каприз за ваши деньги — вопрос только в том, как убедить людей этим пользоваться.
  • 0
    возможен ли обратный процесс? Можно ли сделать 6 .key файлов из одного?
    • 0
      Да, возможен, для этого надо уметь рассчитывать контрольные суммы, которые проверяются при зачитывании ключевых файлов в Крипто-Про.
      • 0
        Это вы о том, что в начале статьи упомянуто как «в худшем случае состоящее из более чем 2000 (двух тысяч) операций хеширования»?
        • 0
          Нет, это так пароль «перемешивается» по 2000 раз, наверное, чтоб брутфорсить пароль было более трудоемко.

          Я имел в виду другие контрольные суммы, которые есть в ключевом файле, но которые не являются необходимыми для проверки правильности хранения секретного ключа. Потому что главный критерий — это когда мы, в самом конце, вычисляем публичный ключ «на лету» и сравниваем первые 8 байт с теми, что хранятся в ключевом файле. Зачем же еще что-то дополнительно контролировать?

          В моем примере необязательные контрольные суммы игнорируются, но в обратном процессе их игнорировать нельзя — Крипто-Про тогда не прочитает ключевой файл.
  • 0
    Стоит упомянуть о существовании утилиты P12FromGostCSP которая позволяет конвертировать ключ в формат P12, доступный для работы с OpenSSL, но утилита имеет следующие существенные недостатки:

    Читает контейнер не напрямую, а через криптопровайдер, поэтому там, где кроме OpenSSL ничего нет, не работает.
    Если в свойствах ключа не отмечено, что ключ «экспортируемый», то конвертировать его невозможно.


    Так то оно так, но как вы вытащите закрытый ключ, если он хранится не на флешке, а на токене. Здесь не идет речь о ГОСТ-овых токенах PKCS#11. Как вы получите доступ к необходимым объектам для получения закрытого ключа?
    • 0
      Думаю, через opensc можно получить доступ к любому токену, поддерживающему pkcs11 интерфейс и вынуть ключ, если в свойствах ключа стоит «экспортируемый».

      Но пока задачи такой не стояло, потому что ни один уважающий себя токен не делает ключи с такими свойствами, иначе смысл токена теряется.
      • 0
        Если экспортируемый — то да!

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.