Приветствую! *** Гневашев Дмитрий [2017-11-30 19:11]: >У меня проблема в том, что принимающая сторона отклонила запрос на сертификат >с такой формулировкой: > >"чтобы получить CMS формат, нужно использовать функцию CryptSignMessage  или >функцию CryptSignHash , но инвертировать байты самостоятельно" Тут видимо они намекают на какой-то продукт/решение/программу -- тут уж ничего не могу сказать. >Есть ли в pygost возможность подписывать документы в формате CMS? Вы делаете документ CMS (видимо, SignedData) и вопрос может ли pygost сделать подпись которая окажется внутри SignerInfo этого CMS? Если так, то он из коробки делает подпись которая без проблем засовывается в SignerInfo как есть. Возможно только одно но: хэш, скорее всего, нужно будет перевернуть перед подписью. Например: hasher = GOST34112012(digest_size=32) hasher.update(b'data to be signed') curve = gost3410.GOST3410Curve(*gost3410.CURVE_PARAMS['GostR3410_2012_TC26_ParamSetA']) prv_key = gost3410.prv_unmarshal(urandom(32)) signature = gost3410.sign(curve, prv_key, hasher.digest()[::-1], mode=2012) Если же речь вообще про создание CMS, то pygost этого не умеет и вряд ли будет, так как это уже не касается криптографических алгоритмов как таковых, а форматов представления данных. CMS это RFC 5652 на 56 страниц (плюс ещё ряд RFC касающихся использования ГОСТ алгоритмов в контексте CMS) -- формат для описания того как подписывать, шифровать, аутентифицировать и проверять целостность документов, используя ASN.1 DER кодек. Примерно, в общих чертах, CMS с подписанными данными делается как-то так ниже. Наверняка он не валиден (я версию выставил наобум) и что-то сделал не так, но общий смысл создания примерно описан ниже. Использую библиотеку PyDERASN (http://pyderasn.cypherpunks.ru/) для работы с ASN.1. Идентификатор публичного ключа подписанта я делаю urandom(20) -- а должен браться из расширения X.509 сертификата. ------------------------ >8 ------------------------ from base64 import b64encode from os import urandom from pyderasn import Any from pyderasn import Choice from pyderasn import Integer from pyderasn import Null from pyderasn import ObjectIdentifier from pyderasn import OctetString from pyderasn import Sequence from pyderasn import SetOf from pyderasn import tag_ctxc from pyderasn import tag_ctxp from pygost import gost3410 from pygost.gost34112012 import GOST34112012 class KeyIdentifier(OctetString): pass class SubjectKeyIdentifier(KeyIdentifier): pass class SignerIdentifier(Choice): schema = ( ('subjectKeyIdentifier', SubjectKeyIdentifier(impl=tag_ctxp(0))), ) class SignatureValue(OctetString): pass class AttributeValue(Any): pass class AttributeValues(SetOf): schema = AttributeValue() class Attribute(Sequence): schema = ( ('attrType', ObjectIdentifier()), ('attrValues', AttributeValues()), ) class SignedAttributes(SetOf): schema = Attribute() bounds = (1, 32) class CMSVersion(Integer): schema = ( ('v0', 0), ) class AlgorithmIdentifier(Sequence): schema = ( ('algorithm', ObjectIdentifier()), ('parameters', Any(optional=True)), ) class SignatureAlgorithmIdentifier(AlgorithmIdentifier): pass class DigestAlgorithmIdentifier(AlgorithmIdentifier): pass class SignerInfo(Sequence): schema = ( ('version', CMSVersion()), ('sid', SignerIdentifier()), ('digestAlgorithm', DigestAlgorithmIdentifier()), ('signedAttrs', SignedAttributes(impl=tag_ctxc(0), optional=True)), ('signatureAlgorithm', SignatureAlgorithmIdentifier()), ('signature', SignatureValue()), ) class SignerInfos(SetOf): schema = SignerInfo() class DigestAlgorithmIdentifiers(SetOf): schema = AlgorithmIdentifier() class ContentType(ObjectIdentifier): pass class EncapsulatedContentInfo(Sequence): schema = ( ('eContentType', ContentType()), ('eContent', OctetString(expl=tag_ctxc(0), optional=True)), ) class SignedData(Sequence): schema = ( ('version', CMSVersion()), ('digestAlgorithms', DigestAlgorithmIdentifiers()), ('encapContentInfo', EncapsulatedContentInfo()), ('signerInfos', SignerInfos()), ) class ContentInfo(Sequence): schema = ( ('contentType', ContentType()), ('content', Any(expl=tag_ctxc(0))), ) id_contentType = ObjectIdentifier('1.2.840.113549.1.9.3') id_messageDigest = ObjectIdentifier('1.2.840.113549.1.9.4') id_data = ObjectIdentifier('1.2.840.113549.1.7.1') id_signedData = ObjectIdentifier('1.2.840.113549.1.7.2') id_tc26_signwithdigest_gost3410_2012_256 = ObjectIdentifier('1.2.643.7.1.1.3.2') id_tc26_gost3411_2012_256 = ObjectIdentifier('1.2.643.7.1.1.2.2') ai_tc26_signwithdigest_gost3410_2012_256 = AlgorithmIdentifier() ai_tc26_signwithdigest_gost3410_2012_256['algorithm'] = id_tc26_signwithdigest_gost3410_2012_256 ai_tc26_gost3411_2012_256 = AlgorithmIdentifier() ai_tc26_gost3411_2012_256['algorithm'] = id_tc26_gost3411_2012_256 ai_tc26_gost3411_2012_256['parameters'] = Any(Null()) eci = EncapsulatedContentInfo() eci['eContentType'] = ContentType(id_data) eci['eContent'] = OctetString(b'data to be signed') hasher = GOST34112012(digest_size=32) hasher.update(bytes(eci['eContent'])) signed_attrs = SignedAttributes() for t, v in ( (id_contentType, eci['eContentType']), (id_messageDigest, OctetString(hasher.digest())), ): signed_attr = Attribute() signed_attr['attrType'] = t signed_attr['attrValues'] = AttributeValues((AttributeValue(v),)) prv_key = gost3410.prv_unmarshal(urandom(32)) curve = gost3410.GOST3410Curve( *gost3410.CURVE_PARAMS['GostR3410_2001_CryptoPro_A_ParamSet'] ) hasher = GOST34112012(digest_size=32) hasher.update(signed_attrs.encode()) signature = gost3410.sign(curve, prv_key, hasher.digest()[::-1], mode=2001) si = SignerInfo() si['version'] = CMSVersion('v0') si['sid'] = SignerIdentifier(('subjectKeyIdentifier', SubjectKeyIdentifier(urandom(20)))) si['digestAlgorithm'] = DigestAlgorithmIdentifier(ai_tc26_gost3411_2012_256) si['signatureAlgorithm'] = SignatureAlgorithmIdentifier( ai_tc26_signwithdigest_gost3410_2012_256 ) si['signature'] = SignatureValue(signature) sd = SignedData() sd['version'] = CMSVersion('v0') sd['digestAlgorithms'] = DigestAlgorithmIdentifiers((ai_tc26_gost3411_2012_256,)) sd['encapContentInfo'] = eci sd['signerInfos'] = SignerInfos((si,)) ci = ContentInfo() ci['contentType'] = ContentType(id_signedData) ci['content'] = Any(sd) print(b64encode(ci.encode())) ------------------------ >8 ------------------------ В итоге получится например вот такой вот CMS: MIG/BgkqhkiG9w0BBwKggbEwga4CAQAxDjAMBggqhQMHAQECAgUAMCAGCSqGSIb3DQEHAaAT BBFkYXRhIHRvIGJlIHNpZ25lZDF3MHUCAQCAFGXAA/03TMbk3IX+JpPXm7BchKefMAwGCCqF AwcBAQICBQAwCgYIKoUDBwEBAwIEQO+QDSE4Ayt0YzGnTw178X0ruXlM6FDWGq2tJXpWwGnB TAYTi4AGwbIMfCzeanbfMWwv6lvTDc7XTUGZ/UjcxYY= внутренности которого выглядят как-то так: ------------------------ >8 ------------------------ 0 [1,2, 191] ContentInfo SEQUENCE 3 [1,1, 9] . contentType: ContentType OBJECT IDENTIFIER id_signedData (1.2.840.113549.1.7.2) 17-3 [0,0, 177] . content: [0] EXPLICIT [UNIV 16] ANY . . 30:81:AE:02:01:00:31:0E:30:0C:06:08:2A:85:03:07 . . 01:01:02:02:05:00:30:20:06:09:2A:86:48:86:F7:0D . . 01:07:01:A0:13:04:11:64:61:74:61:20:74:6F:20:62 . . 65:20:73:69:67:6E:65:64:31:77:30:75:02:01:00:80 . . 14:65:C0:03:FD:37:4C:C6:E4:DC:85:FE:26:93:D7:9B . . B0:5C:84:A7:9F:30:0C:06:08:2A:85:03:07:01:01:02 . . 02:05:00:30:0A:06:08:2A:85:03:07:01:01:03:02:04 . . 40:EF:90:0D:21:38:03:2B:74:63:31:A7:4F:0D:7B:F1 . . 7D:2B:B9:79:4C:E8:50:D6:1A:AD:AD:25:7A:56:C0:69 . . C1:4C:06:13:8B:80:06:C1:B2:0C:7C:2C:DE:6A:76:DF . . 31:6C:2F:EA:5B:D3:0D:CE:D7:4D:41:99:FD:48:DC:C5 . . 86 14 [1,2, 174] . . DEFINED BY (1.2.840.113549.1.7.2): SignedData SEQUENCE 17 [1,1, 1] . . . version: CMSVersion INTEGER v0 20 [1,1, 14] . . . digestAlgorithms: DigestAlgorithmIdentifiers SET OF 22 [1,1, 12] . . . . 0: AlgorithmIdentifier SEQUENCE 24 [1,1, 8] . . . . . algorithm: OBJECT IDENTIFIER id_tc26_gost3411_2012_256 (1.2.643.7.1.1.2.2) 34 [0,0, 2] . . . . . parameters: [UNIV 5] ANY OPTIONAL . . . . . . 05:00 36 [1,1, 32] . . . encapContentInfo: EncapsulatedContentInfo SEQUENCE 38 [1,1, 9] . . . . eContentType: ContentType OBJECT IDENTIFIER id_data (1.2.840.113549.1.7.1) 51-2 [1,1, 17] . . . . eContent: [0] EXPLICIT OCTET STRING 17 bytes OPTIONAL . . . . . 64:61:74:61:20:74:6F:20:62:65:20:73:69:67:6E:65 . . . . . 64 70 [1,1, 119] . . . signerInfos: SignerInfos SET OF 72 [1,1, 117] . . . . 0: SignerInfo SEQUENCE 74 [1,1, 1] . . . . . version: CMSVersion INTEGER v0 77 [0,0, 22] . . . . . sid: SignerIdentifier CHOICE subjectKeyIdentifier 77 [1,1, 20] . . . . . . subjectKeyIdentifier: [0] SubjectKeyIdentifier OCTET STRING 20 bytes . . . . . . . 65:C0:03:FD:37:4C:C6:E4:DC:85:FE:26:93:D7:9B:B0 . . . . . . . 5C:84:A7:9F 99 [1,1, 12] . . . . . digestAlgorithm: DigestAlgorithmIdentifier SEQUENCE 101 [1,1, 8] . . . . . . algorithm: OBJECT IDENTIFIER id_tc26_gost3411_2012_256 (1.2.643.7.1.1.2.2) 111 [0,0, 2] . . . . . . parameters: [UNIV 5] ANY OPTIONAL . . . . . . . 05:00 113 [1,1, 10] . . . . . signatureAlgorithm: SignatureAlgorithmIdentifier SEQUENCE 115 [1,1, 8] . . . . . . algorithm: OBJECT IDENTIFIER id_tc26_signwithdigest_gost3410_2012_256 (1.2.643.7.1.1.3.2) 125 [1,1, 64] . . . . . signature: SignatureValue OCTET STRING 64 bytes . . . . . . EF:90:0D:21:38:03:2B:74:63:31:A7:4F:0D:7B:F1:7D . . . . . . 2B:B9:79:4C:E8:50:D6:1A:AD:AD:25:7A:56:C0:69:C1 . . . . . . 4C:06:13:8B:80:06:C1:B2:0C:7C:2C:DE:6A:76:DF:31 . . . . . . 6C:2F:EA:5B:D3:0D:CE:D7:4D:41:99:FD:48:DC:C5:86 ------------------------ >8 ------------------------ Это всего-лишь подписанная 34.10-2012 256-бит с 34.11-2012 256-бит строчка "data to be signed". CMS это очень не тривиально, плюс оно разных версий бывает (CMSVersion). >Какой смысл в функции prv_unmarshal? Она инвертирует байты ключа? Для чего >она нужна? Если честно, то уже смутно помню, но вроде бы сделана потому что видел что приватные 34.10 ключи хранят в виде little-endian значений и эта функция преобразует набор байт в little-endian число (bytes2long это big-endian -- поэтому предварительно делается кручение). -- Sergey Matveev (http://www.stargrave.org/) OpenPGP: CF60 E89A 5923 1E76 E263 6422 AE1A 8109 E498 57EF