我正在努力替换一个遗留系统(其中包括)接收任意文件的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 似乎是SHA1根据openssl_sign上的德语手册条目在内部再次对输入数据进行哈希处理。由于某种原因,英文条目缺少该信息。
这会产生预期的输出......
$privateKeyID = openssl_get_privatekey(file_get_contents($privateKey));
openssl_sign(file_get_contents("x.txt"), $signature, $privateKeyID);
var_dump(bin2hex($signature));
...但由于我无法访问服务器端的实际输入文件,因此无法提供帮助。
有没有办法解决没有第三方库的额外散列问题?我已经尝试简单地加密收到的哈希值,但是从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:数据对于密钥大小而言太大
任何提示?
答案 0 :(得分:3)
<强>更新强>
正如您在下面的代码中看到的,openssl_verify返回1表示openssl_sign的结果,甚至是openssl_private_encrypt结果。我在我的机器上测试过它。只有在使用数字签名中的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()需要知道签名时使用了哪种摘要。
答案 1 :(得分:1)
根据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。