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

Using Security Transforms to verify an RSA signature created with Ruby/OpenSSL

我正在尝试为我的应用实施一个简单的许可证密钥方案,但我 运行 遇到了重大障碍。我正在按照 OpenSSL for License Keys.

中的示例进行操作

自从该博客 post 写于 2004 年并且 OpenSSL 已在 OS X 上弃用,我正在尝试使用安全转换 API 来完成许可证密钥验证的OpenSSL。但是,我正在使用 OpenSSL 生成私有密钥和 public 密钥; Ruby 网络应用使用 Ruby OpenSSL 包装器库从购买者电子邮件地址的 SHA-256 摘要中使用私钥生成许可证密钥。

问题是,我所做的任何事情似乎都无法使用安全转换 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. 文档(您的许可证 data/email)使用 摘要 (SHA256)
  2. 进行哈希处理
  3. 私钥加密散列。这是二进制 signature.
  4. 二进制 signature 需要编码成便于传输的格式,通常是 base64 或类似的文本。
  5. 编码的签名文档一起传递给验证方(您的objc应用程序)进行验证
  6. Document(您的许可证)再次使用相同的 digest (SHA256)
  7. 进行哈希处理
  8. 编码签名被解码回二进制
  9. Public 密钥解密 signature 揭示原始散列
  10. 将解密后的哈希值与计算出的哈希值进行比较,如果它们匹配 文档 则进行验证。

在 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)和public密钥和私钥是否兼容(匹配RSA模数和public 指数),以及用于来回传递的 binary 签名数据的相同编码(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. */
}