【问题标题】:Avoid SHA1 hashing in openssl_sign / sign given hash避免在 openssl_sign / 给定哈希符号中使用 SHA1 哈希
【发布时间】:2015-03-23 14:21:35
【问题描述】:

我正在努力替换一个旧系统,该系统(除其他外)接收任意文件的 SHA1 哈希,并使用带有简单 PHP Web 服务的私钥对其进行签名。

它应该看起来像这样:

$providedInput = '13A0227580C5DE137C2EBB2907A3F2D7F00CA71D'; 
// pseudo "= sha1(somefile.txt); file not available server side!
$expectedOutput = 'DBC9CC4CB0BECEE313BB100DD1AD39AEC045714D72767211FD574E3E3546EB55E77D2EBFE33BA2974BB74CE051608BFF45A73A52612C5FC418DD3A76CAC0AE0C8FB3FC6CE4F7A516013A9743A36424DDACFE889B3D45E86E6853FD9A55B5B4F0F0D8A574A0B244C0946A99B81CCBD1A7AF7C11072745B11C06AD680BE8AC4CB4'; 
// pseudo: "= openssl_sign(file_get_contents(somefile.txt), signature, privateKeID);

为了简单起见,我使用 PHP 内置的 openssl 扩展。我遇到的问题是 openssl_sign 似乎根据openssl_sign 上的这个德国手册条目再次在内部对输入数据进行 SHA1 哈希处理。由于某种原因,英文条目缺少该信息。

这会产生预期的输出...

$privateKeyID = openssl_get_privatekey(file_get_contents($privateKey));
openssl_sign(file_get_contents("x.txt"), $signature, $privateKeyID);
var_dump(bin2hex($signature));

...但是由于我无法访问服务器端的实际输入文件,所以它不是很有帮助。

在没有 3rd 方库的情况下,有没有办法绕过额外的散列?我已经尝试简单地加密收到的哈希,但从How to compute RSA-SHA1(sha1WithRSAEncryption) value 我了解到加密和签名会产生不同的输出。

更新以使事情更加清晰:

我收到一个 SHA1 哈希作为输入,服务必须将其转换为有效签名(使用私钥),可以使用 openssl_verify 简单地进行验证。客户端遥不可及,因此无法更改其实现。

来自How to compute RSA-SHA1(sha1WithRSAEncryption) value

如果您重现此 EM 并使用 RSA_private_encrypt,那么您将获得正确的 PKCS#1 v1.5 签名编码,与使用通用 EVP_PKEY_sign 获得的 RSA_sign 相同甚至更好。

我想我可以根据this specification 自己简单地实现 DER 编码,但结果 (EM) 似乎太长而无法用我的密钥加密

// 1. Apply the hash function to the message M to produce a hash value H
$H     = hex2bin($input); // web service receives sha1 hash of an arbitrary file as input
$emLen = 128;             // 1024 rsa key

// 2. Encode the algorithm ID for the hash function and the hash value into 
// an ASN.1 value of type DigestInfo
$algorithmIdentifier = pack('H*', '3021300906052b0e03021a05000414');
$digest              = $H; 
$digestInfo          = $algorithmIdentifier.$digest;
$tLen                = strlen($digestInfo);

// 3. error checks omitted ...

// 4. Generate an octet string PS consisting of emLen - tLen - 3 octets
//    with hexadecimal value 0xff.  The length of PS will be at least 8
//    octets.
$ps   = str_repeat(chr(0xFF), $emLen - $tLen - 3);

//5. Concatenate PS, the DER encoding T, and other padding to form the
//   encoded message EM as
$em = "\0\1$ps\0$digestInfo";

if(!openssl_private_encrypt($em, $signature, $privateKeyID)) {
    echo openssl_error_string();
 }
else {
    echo bin2hex($signature);
}

输出:

错误:0406C06E:rsa 例程:RSA_padding_add_PKCS1_type_1:数据对于密钥大小来说太大

有什么提示吗?

【问题讨论】:

    标签: php openssl


    【解决方案1】:

    更新

    您可以在下面的代码中看到 openssl_verify 为 openssl_sign 的结果甚至是 openssl_private_encrypt 结果返回 1。我在我的机器上测试了它。此解决方案仅在使用数字签名中的 sha1 摘要时才有效。

    // Content of file
    $data = 'content of file somewhere far away';
    
    // SHA1 hash from file - input data
    $digest = hash('sha1', $data);
    
    // private and public keys used for signing
    $private_key = openssl_pkey_get_private('file://mykey.pem');
    $public_key = openssl_pkey_get_public('file://mykey.pub');
    
    // Encoded ASN1 structure for encryption
    $der = pack('H*', '3021300906052b0e03021a05000414') . pack('H*', $digest);
    
    // Signature without openssl_sign()
    openssl_private_encrypt($der, $signature, $private_key);
    
    // Signature with openssl_sign (from original data)
    openssl_sign($data, $opensslSignature, $private_key);
    
    // Verifying - both should return 1
    var_dump(openssl_verify($data, $signature, $public_key));
    var_dump(openssl_verify($data, $opensslSignature, $public_key));
    

    我刚刚通过解密 openssl_sign() 结果捕获了 DER 编码结构。

    原始答案

    openssl_sign() 从数据创建摘要,因为这是数字签名的工作方式。数字签名始终是数据的加密摘要。

    您可以在您的 sha1 摘要上使用 openssl_private_encrypt() 和 openssl_public_decrypt() 而不必担心。一般来说,这是同一件事,但是是的,有区别。如果您自己加密某些内容,则加密过程不关心数据,只是加密它们。您需要知道稍后您将解密的是某些数据的 sha1 摘要。其实只是用私钥对数据进行加密,并不是真正的数字签名。

    openssl_sign() 从数据创建摘要并加密有关摘要类型和摘要本身的信息(这是来自您的链接的 ASN.1 DER 结构)。这是因为 openssl_verify() 需要知道签名时使用了什么样的摘要。

    【讨论】:

    • 遗憾的是我无法更改客户端的实现,因此服务器必须将输入哈希“转换”为有效签名。我已经更新了我的初始帖子以进行澄清。
    • 我了解您的情况,但不太了解您的 PHP 代码。事情更简单。看看我更新的答案。
    • 非常感谢!其他算法见tools.ietf.org/html/rfc3447#page-43(我需要SHA256)。
    【解决方案2】:

    根据openssl_sign的英文页面:

    bool openssl_sign ( string $data , string &$signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )
    

    我认为显而易见的建议是使用OPENSSL_ALGO_SHA256。有关支持的算法列表,请参阅 openssl_get_md_methods

    【讨论】:

    • 谢谢,但这不是我想要的。我已经更新了我的初始帖子以进行澄清。
    • 真的好用! (嗯,至少在 PHP 5.6.27 中 - 'PKCS#1 v1.5' 是 SHA256RSA 签名的默认格式)。我在我的程序中使用了这个调用: openssl_sign($binary_data, $binary_signature, $private_key_pem, OPENSSL_ALGO_SHA256); //github.com/Overtonesinger/pkp_demo
    猜你喜欢
    • 2012-09-16
    • 1970-01-01
    • 2017-10-30
    • 1970-01-01
    • 1970-01-01
    • 2011-07-24
    • 1970-01-01
    • 2018-11-26
    • 1970-01-01
    相关资源
    最近更新 更多