【问题标题】:PHP's openssl_sign generates different signature than SSCrypto's signPHP openssl_sign 生成的签名与 SSCrypto 的签名不同
【发布时间】:2010-04-23 14:29:11
【问题描述】:

我正在为一个用 PHP 编写的软件编写一个 OS X 客户端。该软件使用简单的 RPC 接口来接收和执行命令。 RPC 客户端必须对他发送的命令进行签名,以确保任何 MITM 都无法修改它们。

但是,由于服务器不接受我从 OS X 客户端发送的签名,我开始调查并发现 PHP 的 openssl_sign 函数为给定的私钥/数据组合生成的签名与 Objective-C @987654321 不同@ 框架(它只是 openssl 库的包装器):

SSCrypto *crypto = [[SSCrypto alloc] initWithPrivateKey:self.localPrivKey];
NSData *shaed = [self sha1:@"hello"];
[crypto setClearTextWithData:shaed];
NSData *data = [crypto sign];

生成一个签名,如CtbkSxvqNZ+mAN...

PHP 代码

openssl_sign("hello", $signature, $privateKey);

生成像6u0d2qjFiMbZ+这样的签名... (当然是我的某个key。base64编码)

我不太清楚为什么会发生这种情况,并且我尝试了不同的哈希算法但没有成功。正如 PHP 文档所述,默认使用 SHA1。

那么为什么这两个函数会生成不同的签名,我如何让我的 Objective-C 部分生成 PHP 的 openssl_verify 将接受的签名?

注意:我仔细检查了密钥和数据是否正确!

【问题讨论】:

    标签: php objective-c security openssl rsa


    【解决方案1】:

    好的,花了好几个小时。以下是正在发生的事情:

    当您调用 openssl_sign 函数时,PHP 在内部使用由 openssl 库提供的 EVP API。 EVP 是RSA_private_encrypt 等底层函数的“高级”API。因此,当您调用 base64_encode(openssl_sign('hello', $signature, $privKey)) 时,类似于使用 openssl 二进制文件在命令行上执行类似操作:

    echo -n "hello"| openssl dgst -sha1 -sign priv.key | openssl enc -base64
    

    echo -n "hello" | openssl dgst -sha1 | openssl rsautl -encrypt priv.key | openssl enc -base64
    

    我不知道为什么这会产生不同的输出,但确实如此。如果有人知道他们为什么不同:请分享!
    但是,当我使用 SSCrypto 框架时,我使用 EVP 调用(摘要)重写了 -sign(和 -verify)函数:

    OpenSSL_add_all_digests();
    EVP_MD_CTX_init(&md_ctx);
    EVP_SignInit(&md_ctx, mdtype);
    EVP_SignUpdate(&md_ctx, input, inlen);
    if (EVP_SignFinal(&md_ctx, (unsigned char*) outbuf, (unsigned int *)&outlen, pkey)) {
        NSLog(@"signed successfully.");
    }
    

    瞧:我得到的签名与从 PHP 得到的签名相同。哦,记录一下:PHP 使用 PKCS 填充。

    感谢你们为我指明了正确的方向!

    【讨论】:

    • 这对我有用——但我还必须在初始化 EVP 上下文之前调用 OpenSSL_add_all_digests()。
    • openssl_sign() 返回布尔值 true 表示成功 false 表示失败),而不是签名,因此 base64_encode(openssl_sign('hello', $signature, $privKey)) 只会对该布尔值进行编码。
    • 是的,这更像是伪代码。此外(关于 openssl 示例),在我写这篇文章的时候,我没有意识到签名与使用私钥加密并不完全相同。签名只会加密数据的哈希值,而不是数据本身。
    • 这是因为从技术上讲,签名返回的字节格式与使用私钥加密不同(参见 RFC 2315,PKCS #7)。请参阅与您的问题相关的答案:stackoverflow.com/a/10999044/92517。请注意,这不仅限于 PHP。
    【解决方案2】:

    你的代码 sn-p 帮了大忙,我也遇到了同样的问题,我根据你的台词想出了以下方法,如果这对遇到同样问题的人有帮助的话。

    - (NSData *)getSignature {
    NSString * signString = [self getSignatureText];
    NSData * privateKeyData = [self getPrivateKey];
    
    BIO *publicBIO = NULL;
    EVP_PKEY *privateKey = NULL;
    
    if (!(publicBIO = BIO_new_mem_buf((unsigned char *)[privateKeyData bytes], [privateKeyData length]))) {
        NSLog(@"BIO_new_mem_buf() failed!");
        return nil;
    }
    
    if (!PEM_read_bio_PrivateKey(publicBIO, &privateKey, NULL, NULL)) {
        NSLog(@"PEM_read_bio_PrivateKey() failed!");
        return nil;
    }   
    
    const char * data = [signString cStringUsingEncoding:NSUTF8StringEncoding];
    unsigned int length = [signString length];
    int outlen;
    unsigned char * outbuf[EVP_MAX_MD_SIZE];
    const EVP_MD * digest = EVP_md5();
    EVP_MD_CTX md_ctx;
    
    EVP_MD_CTX_init(&md_ctx);
    EVP_SignInit(&md_ctx, digest);
    EVP_SignUpdate(&md_ctx, data, length);
    if (EVP_SignFinal(&md_ctx, (unsigned char*) outbuf, (unsigned int *) &outlen, privateKey)) {
        NSLog(@"Signed successfully.");
    }
    EVP_MD_CTX_cleanup(&md_ctx);
    EVP_PKEY_free(privateKey);
    
    NSData * signature = [NSData dataWithBytes:outbuf length:outlen];
    
    return signature;
    

    }

    【讨论】:

      【解决方案3】:

      在我看来,您的 PHP 正在签署 "hello",而您的 Objective-C 正在签署 sha1("hello")。也就是说,按照我阅读文档的方式,PHP 的openssl_sign 在签名的内部机制中默认使用sha1,而不是在签名之前将sha1 应用于数据。

      【讨论】:

      • 我在我的 Objective-C 代码中删除了 sha1,但我仍然得到不同的(第三个)签名:iaQ+f1PJi... 注意:使用 SSCrypto (Objective-C) 生成的签名匹配与我可以直接在命令行上使用 openssl 生成的那些。
      • openssl_verify() 在新签名上仍然失败吗?另外(基于SSCrypto 的示例代码)您是否使用[signedData encodeBase64] 之类的东西?
      • 是的,PHP 的 openssl_verify() 仍然失败(返回 0)。我只将[signedData encodeBase64] 用于人类可读的输出(例如NSLog()),在其他任何地方我使用NSDataNSString。我认为问题出在 PHP 方面,因为 SSCrypto 会生成与普通 openssl 二进制文件一起使用的有效签名。
      • 如果任何一方在字符串上做一些填充,他们可能会用不同的字符填充,从而抛出散列。您可以尝试一次简单地扩展字符串长度一个字符,看看它们是否在某个点匹配,这就是您的块大小。
      【解决方案4】:

      不是它修复了,但您对 rsautl 的使用似乎不正确。您可能应该使用 rsautl -sign 而不是 rsautl -encrypt

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-11-27
        • 1970-01-01
        • 1970-01-01
        • 2012-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多