Здравствуйте. Я создаю этот вопрос, чтобы в интернете появилась информация о том, как подписывать документы открепленной подписью для Честного Знака на Python. На Хабре уже имеется статья о подписании прикрепленной подписью, но я не смог найти информацию об открепленной. Буду рад, если это кому-то поможет, и было бы замечательно, если кто-то распространит эти сведения в нужных сообществах.
Итак, я работаю с API Честного Знака СУЗ и мне нужно создать заказ на эмиссию кодов. В заголовок запроса необходимо добавить параметр X-Signature - открепленная подпись в кодировке Base64.
Однако я столкнулся с проблемами: каждый раз приходит ответ с кодом 200, а статус заказа показывает "Подпись не прошла проверку". В итоге мне удалось решить проблему, и я собираюсь выложить код в его текущем виде. Вы можете критиковать его стиль, но главное — это работает!
Вот код:
```python
import win32com.client
import base64
import pythoncom
import json
import requests
def signed_data(_data):
# Функция подписывает данные открепленной подписью
# Возвращает подписанные данные и открепленную подпись
CADES_BES = 1
CAPICOM_ENCODE_BASE64 = 0
CAPICOM_CURRENT_USER_STORE = 2
CAPICOM_MY_STORE = "My"
CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED = 2
sSerialNumber = "SERIAL_NUMBER" # ID ключа, берется из крипто-про
# Инструменты КриптоПро -> контейнеры -> Найти нужный ключ (не просроченный, видно при тестировании контейнера) -> Протестировать контейнер -> серийный номер
# Ищем сертификат в хранилище
oCert = None
oStore = win32com.client.Dispatch("CAdESCOM.Store", pythoncom.CoInitialize())
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STOR
```
Для оформления открепленной подписи к заказу контрольного знака в системе "Честный Знак" (СУЗ) с использованием Python, необходимо правильно сформировать и подписать данные. Ваш код уже имеет хорошую основу, и я постараюсь дополнить его и предоставить окончательную версию.
Вот шаги, которые необходимо выполнить для корректного создания открепленной подписи:
1. Убедиться, что у вас установлены необходимые библиотеки (например, `pywin32` для работы с компонентами Windows и `requests` для отправки HTTP-запросов).
2. Сформировать JSON-данные для запроса.
3. Подписать данные с помощью открепленной подписи.
4. Отправить запрос с подписью на сервер.
Вот полный пример кода, который демонстрирует вышеуказанные шаги:
```python
import win32com.client
import base64
import pythoncom
import json
import requests
def signed_data(data):
# Функция подписывает данные открепленной подписью
# Возвращает подписанные данные и открепленную подпись
CADES_BES = 1
CAPICOM_ENCODE_BASE64 = 0
CAPICOM_CURRENT_USER_STORE = 2
CAPICOM_MY_STORE = "My"
sSerialNumber = "YOUR_SERIAL_NUMBER" # Замените на реальный серийный номер
# Ищем сертификат в хранилище
oStore = win32com.client.Dispatch("CAdESCOM.Store", pythoncom.CoInitialize())
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE)
oCert = None
for cert in oStore.Certificates:
if cert.SerialNumber == sSerialNumber:
oCert = cert
break
if oCert is None:
raise ValueError("Certificate not found.")
# Создаем объект для подписи
oSigner = win32com.client.Dispatch("CAdESCOM.Signer", pythoncom.CoInitialize())
oSigner.Certificate = oCert
oCades = win32com.client.Dispatch("CAdESCOM.CadesSignature", pythoncom.CoInitialize())
oCades.SetContent(data)
oSignature = oCades.Sign(oSigner, CADES_BES)
# Получаем открепленную подпись
detached_signature = base64.b64encode(oSignature).decode('utf-8')
return detached_signature
# Основная функция для заказа контрольного знака
def create_order(data):
# JSON данные для запроса
json_data = json.dumps(data)
# Подписываем данные
signature = signed_data(json_data)
# URL для API Честного Знака
url = "https://api.chestnyznak.ru/orders"
# Заголовки запроса
headers = {
"Content-Type": "application/json",
"X-Signature": signature,
}
# Отправка запроса
response = requests.post(url, headers=headers, data=json_data)
# Проверка ответа
if response.status_code == 200:
print("Заказ успешно создан:", response.json())
else:
print("Ошибка при создании заказа:", response.status_code, response.text)
# Пример использования
if __name__ == "__main__":
order_data = {
"order": {
"items": [
{
"gtin": "1234567890123",
"quantity": 1,
# Добавьте остальные параметры заказа
}
]
}
}
create_order(order_data)
```
### Объяснение работы кода:
1. **Подписывание данных**: Используется метод `sign_data`, чтобы создать открепленную подпись для переданных данных.
2. **Поиск сертификата**: Код ищет сертификат в хранилище Windows по серийному номеру. Убедитесь, что серийный номер правильный.
3. **Отправка запроса**: Данные и подпись отправляются на сервер API Честного Знака через HTTP POST запрос.
### Замечания:
- Убедитесь, что у вас есть доступ к Windows API и установлен криптографический компонент КриптоПро.
- Если у вас возникают ошибки, проверьте правильность серийного номера и настройки получаемых данных для подписи.
- Не забудьте заполнить `YOUR_SERIAL_NUMBER` правильным серийным номером ключа.
- Убедитесь, что ваш сертификат не просрочен.
Надеюсь, это поможет вам и другим пользователям, которые сталкиваются с аналогичными задачами.
Код который у меня работает! Пользуйтесь на здоровье! <br/> <br/> <pre><code class="python">import win32com.client
import base64
import pythoncom
import json
import requests
def signed_data(_data):
# Функция подписывает данные открепленной подписью
# Возращает обратно данные которые подписаны, и открепленную подпись
CADES_BES = 1
CAPICOM_ENCODE_BASE64 = 0
CAPICOM_CURRENT_USER_STORE = 2
CAPICOM_MY_STORE = "My"
CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED = 2
sSerialNumber = "SERIAL_NUMBER" # ID ключа, берется из крипто-про
# Инструменты криптопро -> контейнеры -> Найти нужный ключ (не просроченный, видно при тестировании контейнера) -> Протестировать контейнер -> серийный номер
# Ищем сертификат в хранилище
oCert = None
oStore = win32com.client.Dispatch("CAdESCOM.Store", pythoncom.CoInitialize())
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED)
for val in oStore.Certificates:
if val.SerialNumber.upper() == sSerialNumber.upper():
oCert = val
oStore.Close
if not oCert:
print("Не найден сертификат в хранилище")
return None
oSigner = win32com.client.Dispatch("CAdESCOM.CPSigner", pythoncom.CoInitialize())
oSigner.Certificate = oCert
# Строка JSON БЕЗ ПРОБЕЛОВ
message = _data
message_bytes = message.encode()
base64_bytes = base64.b64encode(message_bytes)
base64_message = base64_bytes.decode()
signedData = win32com.client.Dispatch("CAdESCOM.CadesSignedData", pythoncom.CoInitialize())
signedData.ContentEncoding = 1
signedData.Content = base64_message
sSignedData = signedData.SignCades(oSigner, CADES_BES, True, CAPICOM_ENCODE_BASE64)
# Удаляем из подписи символы переноса строки, иначе не вставить в заголовок запроса
sSignedData = sSignedData.replace('\r', '')
sSignedData = sSignedData.replace('\n', '')
return _data, sSignedData
# token выдается при авторизации по URL <url стенда>/auth/simpleSignIn/{omsConnection}
# Статья есть на хабре.
headers = {"clientToken": "token", "Accept": "application/json", 'Content-type': 'application/json'}
# oms_id - Находим в личном кабинете - управление заказами - устройства - OMS ID:
query_params = {"omsId": "oms_id"}
# Заполнение поля products я пропущу, оно хорошо описано в документации к СУЗ
body = {
"productGroup": "lp",
"attributes": {
"releaseMethodType": "PRODUCTION",
"createMethodType": "SELF_MADE",
},
"products": [],
}
# Переводим словарь в JSON БЕЗ ПРОБЕЛОВ!
json_str = json.dumps(body, separators=(',', ':'))
sData, signed = signed_data(json_str) # получаем подпись
headers["X-Signature"] = signed
response = requests.post(f"https://suz.sandbox.crptech.ru/api/v3/order", params=query_params, headers=headers, data=sData)
if response.status_code == 200:
response.json()</code></pre>