ruby - 使用安全转换验证使用 Ruby/OpenSSL 创建的 RSA 签名

标签 ruby cocoa openssl licensing rsa

我正在尝试为我的应用程序实现一个简单的许可 key 方案,但我遇到了重大障碍。我正在按照 OpenSSL for License Keys 中的示例进行操作.

自从该博文写于 2004 年并且 OpenSSL 在 OS X 上已被弃用后,我尝试使用 Security Transforms API 代替 OpenSSL 来完成许可证 key 验证。但是,我正在使用 OpenSSL 生成私钥和公钥;许可证 key 由 Ruby 网络应用程序使用私钥生成,该应用程序使用来自购买者电子邮件地址的 SHA-256 摘要的 Ruby OpenSSL 包装器库。

问题是,我所做的任何事情似乎都无法使用安全转换 API 验证的 OpenSSL 从 Ruby 生成签名。

我正在处理的 Ruby 代码是:

require('openssl')

# The email address used as the content of the license key.
license = 'test@example.com'

# Generate the public/private keypair.
`openssl genrsa -out private_key.pem 2048`
`openssl rsa -in conductor.pem -out public_key.data -pubout`

# Get the private key and a hash of the license.
private_key = OpenSSL::PKey::RSA.new(File.read('private_key.pem'))
signature   = OpenSSL::Digest::SHA256.digest(license)

# The signature passed to SecVerifyTransformCreate in the OS X app. I'm not sure which of these SecVerifyTransformCreate is expecting (the binary digest, a hex representation of the digest, or the original un-digested content), but none of them work.
signature_out = signature
#signature_out = OpenSSL::Digest::SHA256.hexdigest(license)
#signature_out = license

File.write('signature.data', signature_out)

# Sign the email address to generate the license key. Using the OpenSSL::PKey::PKey#sign method produces a license key that can only be verified on the command line by running:
#
#   echo -n test@example.com | openssl dgst -sha256 -sign test.pem
#
# while using the #private_encrypt method produces a key that can only be verified on the command line by running:
#
#   echo -n test@example.com | openssl dgst -sha256 -binary | openssl rsautl -sign -inkey test.pem
#
# I'm not sure what the exact difference between the two commands above is and why they correspond to the two different Ruby signing methods below. Neither approach produces something that SecVerifyTransformCreate will verify, however.
File.write('license_key.data',
           private_key.sign(OpenSSL::Digest::SHA256.new, license))
#           private_key.private_encrypt(signature))

以及Objective-C中对应的验证码:

// Get the data.
NSData *publicKeyData  = [NSData dataWithContentsOfFile:@"public_key.data"];
NSData *signatureData  = [NSData dataWithContentsOfFile:@"signature.data"];
NSData *licenseKeyData = [NSData dataWithContentsOfFile:@"license_key.data"];

// Import the public key.
SecItemImportExportKeyParameters keyParameters = {};
SecExternalFormat format = kSecFormatOpenSSL;
SecExternalItemType type = kSecItemTypePublicKey;
CFArrayRef publicKeys;

SecItemImport((__bridge CFDataRef)publicKeyData,
              NULL,
              &format,
              &type,
              0,
              &keyParameters,
              NULL,
              &publicKeys);

NSArray *publicKeysArray = (__bridge_transfer NSArray *)publicKeys;
SecKeyRef publicKey = (__bridge SecKeyRef)publicKeysArray[0]; // TODO: How do we need to bridge this return value?

CFErrorRef error = NULL;

SecTransformRef verifier = SecVerifyTransformCreate(publicKey, (__bridge CFDataRef)signatureData, &error);

SecTransformSetAttribute(verifier, kSecTransformDebugAttributeName, kCFBooleanTrue, &error);
SecTransformSetAttribute(verifier, kSecTransformInputAttributeName, (__bridge CFDataRef)licenseKeyData, &error);
SecTransformSetAttribute(verifier, kSecDigestTypeAttribute, kSecDigestSHA2, &error);
SecTransformSetAttribute(verifier, kSecDigestLengthAttribute, (__bridge CFNumberRef)@256, &error);

// I'm not sure if one of these transform attributes is necessary, but neither of them produces a verified result anyways.
//  SecTransformSetAttribute(verifier, kSecInputIsAttributeName, kSecInputIsDigest, &error);
//  SecTransformSetAttribute(verifier, kSecInputIsAttributeName, kSecInputIsRaw, &error);

NSNumber *result = (__bridge NSNumber *)SecTransformExecute(verifier, &error);

NSLog(@"Result: %@", result);

有谁知道我该怎么做?我真的花了几天时间才达到现在的状态,我已经用尽了进一步调试它的能力,所以如果有人有任何见解,我将不胜感激!

最佳答案

简而言之,您混淆了一些关键概念。这是有关其工作原理的快速入门。

  1. 文档(您的许可数据/电子邮件)使用摘要 (SHA256) 进行哈希处理
  2. 私钥加密散列。这是二进制签名
  3. 二进制签名需要编码成便于传输的格式,通常是使用 base64 或类似格式的文本。
  4. 编码后的签名文档一起传递给验证方(您的objc应用程序)进行验证
  5. 文档(您的许可证)再次使用相同的摘要 (SHA256) 进行哈希处理
  6. 编码的签名被解码回二进制
  7. 公钥解密签名,揭示原始散列
  8. 将解密后的哈希值与计算出的哈希值进行比较,如果它们匹配文档则进行验证。

在 ruby​​ 方面,您混淆了签名和文档。您需要使用 SHA256 对许可证进行哈希处理,然后使用私钥对其进行加密以生成签名。您只是将文档的哈希值保存为签名。在 ruby 方面试试这个:

require 'openssl'
require 'base64'

license = 'test@example.com'
private_key = OpenSSL::PKey::RSA.new(File.read('private_key.pem'))
digest  = OpenSSL::Digest::SHA256.new
signature = private_key.sign digest, license
signature_out = Base64.encode64(signature)

File.write('signature.data', signature_out)
File.write('license_key.data', license) # no hash, no signing

可以找到与此相关的 ruby​​ 文档 here .

我不是很熟悉你在 Objective-C 方面使用的库,但这里的技巧是确保你在两端使用相同的摘要算法进行哈希处理(SHA256 ),检查相同的加密算法 (RSA) 以及公钥和私钥是否兼容(匹配 RSA 模数和公共(public)指数),以及 二进制 的相同编码> 来回传递的签名数据(base64hex 等)

在 ruby​​ 端,您正在使用 SHA256 生成签名,而在 objective-c 上,您似乎正在使用大小为 256 的 SHA-2 验证它,所以看起来没问题。

解码签名(如果你从 ruby​​ 写二进制你可以跳过这个)

SecTransformRef decoder = SecDecodeTransformCreate(kSecBase64Encoding, &error);
if (error) { CFShow(error); exit(-1); }

SecTransformSetAttribute(decoder, 
                         kSecTransformInputAttributeName,
                         signatureData, 
                         &error);
if (error) { CFShow(error); exit(-1); }

CFDataRef signature = SecTransformExecute(decoder, &error);
if (error) { CFShow(error); exit(-1); }

为了验证你想要这样的东西,从 here 中闪现:

verifier = SecVerifyTransformCreate(publicKey, signature, &error);
if (error) { CFShow(error); exit(-1); } // show your errors!

SecTransformSetAttribute(verifier,
                         kSecTransformInputAttributeName,
                         cfLicense,  // Converted from NSData
                         &error);
if (error) { CFShow(error); exit(-1); }

SecTransformSetAttribute(verifier, 
                         kSecDigestTypeAttribute, 
                         kSecDigestSHA2, 
                         &error);
if (error) { CFShow(error); exit(-1); }

SecTransformSetAttribute(verifier, 
                         kSecDigestLengthAttribute, 
                         (__bridge CFNumberRef)@256, 
                         &error);
if (error) { CFShow(error); exit(-1); }

result = SecTransformExecute(verifier, &error);
if (error) { CFShow(error); exit(-1); }

if (result == kCFBooleanTrue) {
  /* Signature was valid. */
} else {
  /* Signature was invalid. */
}

关于ruby - 使用安全转换验证使用 Ruby/OpenSSL 创建的 RSA 签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29887606/

相关文章:

ruby-on-rails - rails 中的路径解析

ruby-on-rails - 更改 ruby​​ on rails 中的默认日期格式?

objective-c - 无法在应用程序启动时隐藏 NSFontPanel

objective-c - 防止调整显示为工作表的 View Controller 的大小?

c# - OpenSSL .NET c# 包装器 X509 认证

ruby-on-rails - 使用 DataMapper get 在 Rails3 Controller 中处理 404 的最佳方法

ruby - 为什么这段代码没有达到我的预期

cocoa - 强制 NSDocument 创建后保存

linux - SSL/TLS 原始字节转储

authentication - 使用 OPENSSL 的 PLAIN 访问的 Office 365 IMAP 身份验证失败