使用 Python 中的 RSA public 密钥验证签名许可证密钥

Signed license key verification with RSA public key in Python

免责声明:我不擅长密码学,对 RSA 或密码验证知之甚少。

我需要验证 Python 中的签名许可证。我用了"RSA PKCS1 v1.5 padding, with a SHA256 digest"。背景信息是我正在尝试使用 https://keygen.sh.

分发软件

基本上,二进制软件安装在服务器上,并验证存储在服务器上的许可证文件是否真实。为此,该软件可以访问我的 RSA public 密钥。我有一段代码在 Node.js 中工作,来自 the official keygen documentation,我试图将它移植到 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 pycryptodome
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

这对应一个signature length error:

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

我假设这是由不正确的填充引起的,所以我对签名填充进行了不同的尝试,您可以在我的代码段中看到,none 其中有效。但至少,它们让我超越了签名验证的第一步。我现在卡在 fourth and final step:

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

知道如何解决这个问题吗?如有必要,我准备使用不同于 cryptodome 的东西。

您的签名使用的是 base64url,而不是标准的 base64。我认为后者是罪魁祸首。因此,您需要将 - 替换为 +,将 _ 替换为 /。或者当然使用 base64url 解码器。

我是注册机的创始人。这是我这边缺少适当的文档,我会更正。像 , the signed contents of the license key (which contains 2 parts: the key payload and its signature) are base64url encoded using RFC 4648,一个 URL 安全版本的 base64 被大多数编程语言支持(但不总是)。这种 base64url 编码方案与正常的 base64 编码略有不同,如 Maarten 的回答所述。

大多数编程语言都有一个单独的函数来解码 URL base64 编码值, 但如果不是,就像 Maarten 在他的回答中概述的那样,您可以简单地将所有“-”base64 字符替换为“+”,将所有“_”字符替换为“[=14=” ]".

下面是使用 PKCS1 v1.5 填充验证使用 RSA-SHA256 签名的许可证密钥的完整示例:

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 的文档以阐明所有这些内容,并提供有关如何以加密方式验证许可证的更好示例。