Читаем контейнер закрытого ключа КриптоПро средствами 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 

Все работает просто замечательно!
Спасибо за внимание. Это была моя первая статья на хабре.

ссылка на оригинал статьи http://habrahabr.ru/post/275039/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *