Проверка подписанного лицензионного ключа с открытым ключом RSA в Python

Отказ от ответственности: я ужасен в криптографии и очень мало понимаю о RSA или криптографической проверке.

Мне нужно проверить подписанную лицензию в Python. Я использовал "RSA PKCS1 v1.5 padding с дайджестом SHA256". Справочная информация о том, что я пытаюсь распространять программное обеспечение с https://keygen.sh/.

По сути, двоичное программное обеспечение установлено на сервере и проверяет подлинность файла лицензии, хранящегося на сервере. Для этого у программного обеспечения есть доступ к моему открытому ключу RSA. У меня есть фрагмент кода, работающий в Node.js, взятый из официальной документации keygen, и я пытаюсь перенести его на Python, но безуспешно. Я решил использовать библиотеку cryptodome, но я открыт для других вариантов.

Вот рабочий фрагмент кода в Node.js:

const crypto = require('crypto')

const public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmwlw+mDo2ZVBlRXa7Em\ncj7cVrlwnwrIPC+Ij5KpltadJfwvRFvCr37USJvkc+FIND2dKk2mmbY32cvtxl3F\nYLpjRwwlFuajbP8ZEdJl1YJyJDnLlKHWEfTSvTzZhpT939yjuBKoZ9A+wiIQ9tzY\nF/ytb9zwPkOF7/XmPAaukah5xRgwsb3fo7E0CsBQuHZxFX83+nfdZ/60MWpSCWL6\nAjNWDEmoLFEHVRm69+lwXTW51wojfurZy/wUw42sciHLV5A8mz7gJJGO5y+sGzzD\nM5VxtmLz51Fl1Rl3fMzUAjPK77i9UDWo11EuNPrzMAgjmuuMLfpIDMlMR3n/ZsW7\nXwIDAQAB\n-----END PUBLIC KEY-----\n"
const key = 'somerandomkey'
const encodedSignature = "oMTrvIz3IX4kre5UTzvkzCn712wulPvl9knSYBduYcGsX2W703zWMC9ZVepDytxLdpUIiCUtx6wx5OzmLx3rTzgaKqptrbf2wYHrCIPBgrhcHdJ3fLJRh8ASC_NdLK6i1jC_bEAq84d7QNLlTPC20aCmNLdxEJFy-DValGG0iFdxx6n6-Vp5oL8jSyWubAvBSqEQ4ubptcYirxpbDdC4DRpNzBuA48DGxWg6Pxq5HdGZWKS05iohNlrFkW-K8NJYHuLKszT0FN5UWcghx1oklagCm72aDvXm3CzKL2id7yL78X_V69JYsExx3fjRsU0pUe-f5lzKLB_HLTAdc0e1gQ=="

const verifier = crypto.createVerify('sha256')
verifier.write(key)
verifier.end()

const ok = verifier.verify(public_key, encodedSignature, 'base64')
if (ok) {
  console.log('License key is valid!')
} else {
  console.log('License key is invalid!')
}

Запуск этого куска кода (nodejs verify.js) печатает License key is valid!,

И вот код ошибки в Python:

import base64

# pip install cryptodome
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
import Crypto.Signature.pkcs1_15
import Crypto.Util.Padding

public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmwlw+mDo2ZVBlRXa7Em\ncj7cVrlwnwrIPC+Ij5KpltadJfwvRFvCr37USJvkc+FIND2dKk2mmbY32cvtxl3F\nYLpjRwwlFuajbP8ZEdJl1YJyJDnLlKHWEfTSvTzZhpT939yjuBKoZ9A+wiIQ9tzY\nF/ytb9zwPkOF7/XmPAaukah5xRgwsb3fo7E0CsBQuHZxFX83+nfdZ/60MWpSCWL6\nAjNWDEmoLFEHVRm69+lwXTW51wojfurZy/wUw42sciHLV5A8mz7gJJGO5y+sGzzD\nM5VxtmLz51Fl1Rl3fMzUAjPK77i9UDWo11EuNPrzMAgjmuuMLfpIDMlMR3n/ZsW7\nXwIDAQAB\n-----END PUBLIC KEY-----\n"
license_key = b'somerandomkey'

encoded_license_signature = """oMTrvIz3IX4kre5UTzvkzCn712wulPvl9knSYBduYcGsX2W703zWMC9ZVepDytxLdpUIiCUtx6wx5OzmLx3rTzgaKqptrbf2wYHrCIPBgrhcHdJ3fLJRh8ASC_NdLK6i1jC_bEAq84d7QNLlTPC20aCmNLdxEJFy-DValGG0iFdxx6n6-Vp5oL8jSyWubAvBSqEQ4ubptcYirxpbDdC4DRpNzBuA48DGxWg6Pxq5HdGZWKS05iohNlrFkW-K8NJYHuLKszT0FN5UWcghx1oklagCm72aDvXm3CzKL2id7yL78X_V69JYsExx3fjRsU0pUe-f5lzKLB_HLTAdc0e1gQ=="""
license_signature = base64.b64decode(encoded_license_signature)
# Padding: none of these solutions work
# license_signature = Crypto.Util.Padding.pad(license_signature, 8, style='pkcs7')
# license_signature = Crypto.Util.Padding.pad(license_signature, 8, style='iso7816')
# license_signature = Crypto.Util.Padding.pad(license_signature, 8, style='x923')
# Custom zero-padding (doesn't work either)
#license_signature = (8 - len(license_signature) % 8)*bytes([0]) + license_signature
#license_signature = license_signature + (8 - len(license_signature) % 8)*bytes([0])

rsa_public_key = RSA.import_key(public_key)
signature = Crypto.Signature.pkcs1_15.new(rsa_public_key)

license_hash = SHA256.new(data=license_key)
print(signature.verify(license_hash, license_signature))

Запуск этого куска кода (python3 verify.py) выдает ошибку:

Traceback (most recent call last):
  File "verify.py", line 30, in <module>
    print(signature.verify(license_hash, license_signature))
  File "/home/user/venvs/tutor/lib/python3.6/site-packages/Crypto/Signature/pkcs1_15.py", line 111, in verify
    raise ValueError("Invalid signature")
ValueError: Invalid signature

Это соответствует ошибке длины подписи:

# Step 1
if len(signature) != k:
    raise ValueError("Invalid signature")

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

Traceback (most recent call last):
  File "verify.py", line 30, in <module>
    print(signature.verify(license_hash, license_signature))
  File "/home/user/venvs/tutor/lib/python3.6/site-packages/Crypto/Signature/pkcs1_15.py", line 137, in verify
    raise ValueError("Invalid signature")
ValueError: Invalid signature

Есть идеи, как это решить? Я готов использовать что-то отличное от криптодома, если это необходимо.

2 ответа

Решение

Ваша подпись в base64url вместо стандартной base64. Я предполагаю, что последний является виновником. Так что вам нужно заменить - с + а также _ с /, Или, конечно, используйте декодер base64url.

Я основатель Keygen. Это отсутствие надлежащей документации с моей стороны, которую я исправлю. Как упоминал Мартен, подписанное содержимое лицензионного ключа (который состоит из 2 частей: полезной нагрузки ключа и его подписи) кодируется base64url с использованием RFC 4648, URL-безопасной версии base64, которая поддерживается в большинстве языков программирования (но не всегда), Эта схема кодирования base64url немного отличается от обычной кодировки base64, как указано в ответе Мартена.

Большинство языков программирования будут иметь отдельную функцию для декодирования закодированных значений URL base64, но если нет, то, как подчеркивает Мартен в своем ответе, вы можете просто заменить все "-"base64 chars with"+", и все "_"Чарс с"/".

Вот полный пример проверки лицензионного ключа, подписанного с помощью RSA-SHA256, с использованием дополнения PKCS1 v1.5:

from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
import base64

# This should be your Keygen account's public key
PUBLIC_KEY = \
"""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmwlw+mDo2ZVBlRXa7Em
cj7cVrlwnwrIPC+Ij5KpltadJfwvRFvCr37USJvkc+FIND2dKk2mmbY32cvtxl3F
YLpjRwwlFuajbP8ZEdJl1YJyJDnLlKHWEfTSvTzZhpT939yjuBKoZ9A+wiIQ9tzY
F/ytb9zwPkOF7/XmPAaukah5xRgwsb3fo7E0CsBQuHZxFX83+nfdZ/60MWpSCWL6
AjNWDEmoLFEHVRm69+lwXTW51wojfurZy/wUw42sciHLV5A8mz7gJJGO5y+sGzzD
M5VxtmLz51Fl1Rl3fMzUAjPK77i9UDWo11EuNPrzMAgjmuuMLfpIDMlMR3n/ZsW7
XwIDAQAB
-----END PUBLIC KEY-----"""

# This should be the license key that you're cryptographically verifying
LICENSE_KEY = \
"""c29tZXJhbmRvbWtleQ==.oMTrvIz3IX4kre5UTzvkzCn712wulPvl9knSYBduYcGsX2W703zWMC9ZVepDytxLdpUIiCUtx6wx5OzmLx3rTzgaKqptrbf2wYHrCIPBgrhcHdJ3fLJRh8ASC_NdLK6i1jC_bEAq84d7QNLlTPC20aCmNLdxEJFy-DValGG0iFdxx6n6-Vp5oL8jSyWubAvBSqEQ4ubptcYirxpbDdC4DRpNzBuA48DGxWg6Pxq5HdGZWKS05iohNlrFkW-K8NJYHuLKszT0FN5UWcghx1oklagCm72aDvXm3CzKL2id7yL78X_V69JYsExx3fjRsU0pUe-f5lzKLB_HLTAdc0e1gQ=="""

# Split license key to obtain key and signature, then decode urlbase64 encoded values
enc_key, enc_sig = LICENSE_KEY.split(".")
key = base64.urlsafe_b64decode(enc_key)
sig = base64.urlsafe_b64decode(enc_sig)

# Verify the key's signature
pub_key = RSA.importKey(PUBLIC_KEY)
verifier = PKCS1_v1_5.new(pub_key)
digest = SHA256.new(data=key)

print(
  verifier.verify(digest, sig)
)

Я обновил документацию Keygen, чтобы прояснить все эти вещи, а также предоставить лучшие примеры того, как криптографически проверять лицензию.

Другие вопросы по тегам