Manually verifying a digital signature of SSL cert in hex format - php

I'm creating a program that, given a purely hexidecimal format of a cert, will extract and verify the digital signature.
I have the following example cert in hex format:
308206cd308205b5a003020102021001c3f8388b669f492cfac8731937fc8e300d06092a864886f70d01010b0500304f310b300906035504061302555331153013060355040a130c446967694365727420496e633129302706035504031320446967694365727420544c532052534120534841323536203230323020434131301e170d3232303132313030303030305a170d3233303230353233353935395a3079310b3009060355040613025553311330110603550408130a43616c69666f726e6961310f300d06035504071306497276696e6531253023060355040a131c426c697a7a61726420456e7465727461696e6d656e742c20496e632e311d301b0603550403131475732e61637475616c2e626174746c652e6e657430820122300d06092a864886f70d01010105000382010f003082010a0282010100b3838051fd24a13681e97e557476f22891b05355157c9ded1c7ca610f07f4d5a5c987e88318c213a62481fc0fd43ece409170cef6ac95ab106dd4cd046e4c3660097d2aeefffc7fe470307b17c9febd3799352a1971b0303360bd9c028c1939d9fbd0148eb0d4dbebfad23e5e3ab3b94943351cd8fe11556bf1ec7370a615ed74c5f5d66d62799f66bb1d1f3a4b661173cafed4b722b03f046572e73eae216b8e35e73add6cb86243fe4b457ffb8e6baf50af563110da121255c676248e8976ab1fe8c221a964cc550901b5191730aa6d616dc6aa9ee87fcaf7ac56e48f1592eeeb836065dd6c1078e724db7607677298169d9225eac5fdc974a21565621d4fb0203010001a382037930820375301f0603551d23041830168014b76ba2eaa8aa848c79eab4da0f98b2c59576b9f4301d0603551d0e04160414402287da2dd56eddfa85ccda1f946e493a08a99b301f0603551d1104183016821475732e61637475616c2e626174746c652e6e6574300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b0601050507030230818f0603551d1f0481873081843040a03ea03c863a687474703a2f2f63726c332e64696769636572742e636f6d2f4469676943657274544c53525341534841323536323032304341312d342e63726c3040a03ea03c863a687474703a2f2f63726c342e64696769636572742e636f6d2f4469676943657274544c53525341534841323536323032304341312d342e63726c303e0603551d20043730353033060667810c0102023029302706082b06010505070201161b687474703a2f2f7777772e64696769636572742e636f6d2f435053307f06082b0601050507010104733071302406082b060105050730018618687474703a2f2f6f6373702e64696769636572742e636f6d304906082b06010505073002863d687474703a2f2f636163657274732e64696769636572742e636f6d2f4469676943657274544c53525341534841323536323032304341312d312e637274300c0603551d130101ff0402300030820180060a2b06010401d679020402048201700482016c016a007700e83ed0da3ef5063532e75728bc896bc903d3cbd1116beceb69e1777d6d06bd6e0000017e7eaf5af30000040300483046022100ce2d33ce8de3764ccc9d8ac03b936612fd10a4fc1815b3e092352643aa8d07e9022100e2a741a4c3ef5c90882f66951075be12ad54bdc573bd3ae3b17c662f63c6e29400760035cf191bbfb16c57bf0fad4c6d42cbbbb627202651ea3fe12aefa803c33bd64c0000017e7eaf5b14000004030047304502201ef42884353339d4921bf2e47af540b15af6bab94de74c3e3a1224418e9bc4ef0221008846deaa381d08b65607dc3290ba152f1c5aab5c7d03cf116682da8a45922f18007700b3737707e18450f86386d605a9dc11094a792db1670c0b87dcf0030e7936a59a0000017e7eaf5b4400000403004830460221008dc3b79af62dce53af04b8959afcb1f858bea16872eba97b5cc2c8f308b32d490221009b658042c5a0841d670ac1303c06b42e6a494596cb5e333fbdeddc9248e05dcd300d06092a864886f70d01010b0500038201010010170d137389daa010c477c0dde1af6529725489ad07822ace988cb78969683e1686a9fcde2d166b2c7ae5774e782ce7270904fb5abfdc0de25123d7cbcbd855598d35a027d1f5bdf3bd754eb3c6c9b7cc74de740b0b576c629dfd9ff5cca4d773f8bb499cc6f0aa39f269d219f019e62cbb354f32fa171226f58a4582b711e779268baa5d4bcc44f9dda3f2b867344ea29ecb6e28f4f818ef3594da16dc882cfd65cd2875a50d9ee9dff5d297135b5890ce4f583ab770c1d79bd5e7c4b2e1ae2ca0425cf63e12f151e8b6c6228ee53be299ee5ef09c85df0ad24b66e32b37f93193c2495f2de78d28126b3aa8a406b44d8469292fc242c5f39db44be56f4d91
From here, I am able to successfully derive the digital signature (this is located on the end of the cert hex above)
10170d137389daa010c477c0dde1af6529725489ad07822ace988cb78969683e1686a9fcde2d166b2c7ae5774e782ce7270904fb5abfdc0de25123d7cbcbd855598d35a027d1f5bdf3bd754eb3c6c9b7cc74de740b0b576c629dfd9ff5cca4d773f8bb499cc6f0aa39f269d219f019e62cbb354f32fa171226f58a4582b711e779268baa5d4bcc44f9dda3f2b867344ea29ecb6e28f4f818ef3594da16dc882cfd65cd2875a50d9ee9dff5d297135b5890ce4f583ab770c1d79bd5e7c4b2e1ae2ca0425cf63e12f151e8b6c6228ee53be299ee5ef09c85df0ad24b66e32b37f93193c2495f2de78d28126b3aa8a406b44d8469292fc242c5f39db44be56f4d91
I then take the public key that issued this cert, and use it to decrypt the Digital Signature Digest, which looks like this:
3031300d0609608648016503040201050004200bf3dcf2340b972e97fe3c8493e11eeee01f298939734690d0b4e79e1f5701b4
At this point is where I am trying to verify the Digital Signature by creating a hash of the entire SSL cert (this cert uses SHA256 Hash for digest):
hash('sha256', hex2bin($CertWithoutSignature))
Where $CertWithoutSignature is the same hex above (1st hex string above) WITHOUT the digital signature (2nd hex string above).
At this point I'm a bit confused, because not only does the length of the sha256 hash not match, neither does the data. I know I am decrypting the digest correctly, because otherwise I would get an error if the key were invalid, etc. But that extracted value is 102 characters long, and does not match what I will ever get from sha256 since the length of the string is totally different. Basically I know it is valid if I can get them to match because that means the document is the exact same and therefore will get the same hash. Any help is appreciated. Thanks.

You are forgetting that the hash is encoded and then padded in the signature, assuming that you are using PKCS#1 v1.5 padding within the certificate.
Hashing everything up to the signature is not correct either, you need to hash the TBSCertificate, where TBS means To Be Signed.
For more information, I would strongly recommend to read the X.509 specifications in RFC 5280. And, since ASN.1 encoders / decoders are rather complex you might want to use a library function instead of programming it yourself.

You're going to need to use an actual SSL library in order to validate the signature properly, and it's not simply comparing a couple hashes. You're going to also need the signing cert at the least.
To get you started, you're going to want to convert that cert to PEM format so that OpenSSL will like it.
// hex-encoded binary format
$cert_hex = '308206cd...';
// convert to PEM format
$cert_pem = sprintf(
"-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n",
implode("\n", str_split(base64_encode(hex2bin($cert_hex)), 64))
);
// read in as an OpenSSL resource
$cert = openssl_x509_read($cert_pem);
// check the fingerprint just to verify it loaded
$fp = openssl_x509_fingerprint($cert, 'sha256');
var_dump($fp);
Output:
string(64) "d74157547fb287694b95b2533588c71f8706b0960e023fc4317f4f9a49ad2721"
After this you're likely going to want to load the signing cert in the same way and feed them both into openssl_x509_verify(), eg:
openssl_x509_verify($cert, $signing_cert);

Related

How to sign ECDSA signature and encode to DER format in hex in php?

How to sign the Tx data including ETH Keccak256 hashed addresses signature with secp256k1 (ECDSA) keys and encode to DER format in hex and verify in php?
I want to use only php libraries come with, openssl and others, to make secp256k1 private and public keys, sign, and convert to DER format in hex for making a sample of my cryptcoin.
Ethereum addresses are made by secp256k1 EDSA keys, hash the 256 bits/64 character pubic key made from the private key with Keccak256 (sha-3-256) hash algorithm to calculate hash, then take last 40 characters from the hash string in hex, add checksum, and add x0 prefix to the hashed string.
The example is shown in github.com/simplito/elliptic-php repo as toDER() function in elliptic-php/lib/EC/Signature.php. The library is using BN-php, but I want to use only php library, openssl and others.
// https://github.com/simplito/elliptic-php ECDSA sample
<?php
use Elliptic\EC;
// Create and initialize EC context
// (better do it once and reuse it)
$ec = new EC('secp256k1');
// Generate keys
$key = $ec->genKeyPair();
// Sign message (can be hex sequence or array)
$msg = 'ab4c3451';
$signature = $key->sign($msg);
// Export DER encoded signature to hex string
$derSign = $signature->toDER('hex');
openssl_pkey_new() and openssl_sign() functions can make new secp256k1 private and public keys and sign, but can not convert the public key to DER format in hex to make address by hashing keccak256 algorithm .
Signing Tx hash requires from/to ETH addresses in JSON string Tx data {address:{to:address1,from:address2},amount:0,timestamp:timestamp tx, hash:sha256hash, previousHash:hash, difficulty:2...} and openssl_pkey_new() openssl_pkey_export() made private and public keys are needed to make ETH address.
Since, openssl_pkey_new() openssl_pkey_export() for OPENSSL_KEYTYPE_EC return DER bin keys, I need to convert them to hex.
I have tried openssl_pkey_new() and openssl_sign() functions but I can not convert to DER format in hex with simple function.
openssl_pkey_new() function to make new secp256k1 key pairs, just
shown in Generating the 256bit ECDSA private key question.
$config = [
"config" => getenv('OPENSSL_CONF'),
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'secp256k1'
];
$res = openssl_pkey_new($config);
if (!$res) {
echo 'ERROR: Fail to generate private key. -> ' . openssl_error_string();
exit;
}
// Generate Private Key
openssl_pkey_export($res, $priv_key, NULL, $config);
// Get The Public Key
$key_detail = openssl_pkey_get_details($res);
$pub_key = $key_detail["key"];
echo "priv_key:<br>".$priv_key;
echo "<br><br>pub_key:<br>".$pub_key;
Link
And sign with the key.
openssl_sign($hashdata, $signature, $private_key);
Is there any way, with php libraries, to convert the keys to DER in hex?
Okay, now I know what you want I've looked into this, and unfortunately I think you're out of luck. First to clarify, for Ethereum you want the signature in DER and the key format can vary depending on your software, but for an Ethereum account address you do NOT want the key in DER. Specifically an address is computed by:
represent the publickey point as raw X,Y coordinates, with no metadata like DER or even a single byte like X9.62 and no encoding like base64 or hex
take a Keccak256 hash and take the last 20 bytes, which can be represented (e.g. displayed) in hex with 0x prefixed for a total of 42 characters
For step 1, the keys used by OpenSSL, and thus by php's builtin module, are PEM format, as you see in your example, which consists of an ASN.1 (DER or sometimes BER) body encoded in base64 with linebreaks and header/trailer lines. Specifically the publickey is in the format defined by RFC7468 section 13 whose content is the SubjectPublicKeyInfo structure defined by X.509 and PKIX i.e. RFC5280 4.1.2.7 whose contents for a particular algorithm is defined in other standards; the case here, X9-style EC, is defined in RFC3279 2.3.5 and simplified by RFC5480 2.2 which reference X9.62 and SEC1. (Whew!) While these standards allow several options, OpenSSL in PHP always uses X9.62-uncompressed point format and DER encoding, so for this curve the data you need for Ethereum is simply the last 64 bytes of the DER decoded from the PEM:
$der=base64_decode(str_replace(array("-----BEGIN PUBLIC KEY-----\n","-----END PUBLIC KEY-----\n"),"",$pub_key));
$raw=substr($der,-64);
But step 2 is a problem. Although SHA3 and SHAKE as standardized in FIPS202 are instances of Keccak, the hash Ethereum uses is a different and nonstandard instance of Keccak, which OpenSSL does not implement, therefore neither does php by itself (without adding a library).

python string format pkcs8 RSA signature

I need to use pkcs8 private key in my project as example below, I can't found any library or method relate to pkcs8 signature only pkcs1 are available for the Crypto.Signature.
My python code example:
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
privatekey='''-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOuxJs5RD6s2ECEV
C38Jfj6xBBrcX6W1Qy+GLiXhh+wKj650BPSerQNQI+XPx/+uUemk/dJM3RMm75Di
mXJlp7/5LY11VyKSqS+x6ELIvWz8lo2OhHW6/wU4jiQ1MlburYbn/pRRClGGA8i4
FjHMvMu9/QCNPlNTlf7qJDO6lDVlAgMBAAECgYB0iP76/DGXIgAPm2w3v+Xf8X5q
GZRhRqKVmO6wZDbkisRIKa1ZlitNfA6DzpzA2tw9fgrSNJcKpTHGnYPpgEHUQrcw
t471yuAvzAOBffaqVjIt7vKyL3QnkNfxWenliasacX3olwBpKPrkvG75H2KBe9S0
WKpju1Cm6MebAJ3z8QJBAP8gSx3l9nNTqJcBBmP+TGDcTL9Sif2nBZrU/pWnzzop
HolaFIQTYXPIj8rMoVT/c0gLo1gcdHoSUJTbB0WO7V8CQQDsf9FAWCI3fkWgS/fW
21ApO6B07/388CpNHOTjvfsdlent4yXLL+Guh5apMH4Oq0tkGkEij5YPG1EZSYXi
48+7AkEAiX3m3ZMMMXTZe5/CyOrIUL8I4WbjFP8JJzs4hICuTmLQoScZvWAQeeyR
ibKkE4GjqCUVf6u+Hfd20/ICRjtTswJANCjEh8JoWYDZ7k6S7KoV9eIWs3OyurRl
P/idarUdyxqjKzorvbJjvdBdpBbz1lxlFkDMGMk+OTq3GjKi+rVvvQJAGhBieR2Q
69JNeoOg7J8Xk7evNBJHwtJfRDQXKJpN5YZsKclf6oRaYFEQOM6ThuNoYHISfGKw
UbDoUsPZoQFPtQ==
-----END PRIVATE KEY-----'''
signstr = testdata123
key = RSA.importKey(privatekey)
h = SHA.new(signstr)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
sign = base64.b64encode(signature)
The signature generated by code above do not match the public key in the api system I am using.
For PHP side I am able to sign correctly. The working code for PHP as below:
$private_key= openssl_get_privatekey($private_key);
openssl_sign($signStr,$sign_info,$private_key,OPENSSL_ALGO_MD5);
$sign = base64_encode($sign_info);
Any advice on how to sign with RSA pkcs8 format private key using python is much appreciated.
Hash algorithm
Of course, it must be different, because you're using different hash algorithms.
Your Python code uses SHA-1 and your PHP code uses MD5. The default hash algorithm in PHP is OPENSSL_ALGO_SHA1 as the documentation describes:
if (!openssl_sign($signStr, $sign_info, $private_key)) {
echo "Failed to sign";
}
Key encoding
There are many ways of encoding a private key. Two of the most common ways are PKCS#1 and PKCS#8 encodings. See more information on that here.
The key encoding is not a concern here, because both pycrypto and openssl use the correct one here.
Padding
There are two common padding schemes that are used for signing: PKCS#1 v1.5 padding and RSA-PSS (technically RSASSA-PKCS1-V1_5 and RSASSA-PSS). Both of them are defined in PKCS#1, but the latter was introduced in PKCS#1 v2.x.
Pycrypto supports both padding schemes, but openssl only supports PKCS#1 v1.5 in PHP.
Compatibility
If you want to check for compatibility of the same algorithm between different languages, you need to sign in one using the private key and verify the signature in the other using the public key. After you've done that, you can try the other way around if you also need the other direction.
This is only relevant for RSA-PSS, because that is randomized.
If you're using PKCS#1 v1.5 padding which is not randomized, you should be able to create a signature in both Python and PHP and compare them directly to check for compatibility.

Avoid SHA1 hashing in openssl_sign / sign given hash

I'm working on replacing a legacy system that (among other things) receives SHA1 hashes of arbitrary files and signs them using a private key with a simple PHP web service.
It should look something like that:
$providedInput = '13A0227580C5DE137C2EBB2907A3F2D7F00CA71D';
// pseudo "= sha1(somefile.txt); file not available server side!
$expectedOutput = 'DBC9CC4CB0BECEE313BB100DD1AD39AEC045714D72767211FD574E3E3546EB55E77D2EBFE33BA2974BB74CE051608BFF45A73A52612C5FC418DD3A76CAC0AE0C8FB3FC6CE4F7A516013A9743A36424DDACFE889B3D45E86E6853FD9A55B5B4F0F0D8A574A0B244C0946A99B81CCBD1A7AF7C11072745B11C06AD680BE8AC4CB4';
// pseudo: "= openssl_sign(file_get_contents(somefile.txt), signature, privateKeID);
For the sake of simplicity I'm using PHP's built in openssl extention. The problem I'm running into is that openssl_sign seems to SHA1 hash the input data again internally according to this German manual entry on openssl_sign. The English entry is missing that info for some reason.
This produces the expected output ...
$privateKeyID = openssl_get_privatekey(file_get_contents($privateKey));
openssl_sign(file_get_contents("x.txt"), $signature, $privateKeyID);
var_dump(bin2hex($signature));
... but since I don't have access to the actual input files on server side it's not very helpful.
Is there a way around the additional hashing without 3rd party libs? I already tried to simply encrypt the hash received, but from How to compute RSA-SHA1(sha1WithRSAEncryption) value I understand encrypting and signing produce different output.
Update to make things more clear:
I'm recieving an SHA1 hash as input and the service has to convert it to a valid signature (using a private key) that can simply be verified using openssl_verify. The clients are out of reach, so changing their implementation is not possible.
From How to compute RSA-SHA1(sha1WithRSAEncryption) value:
If you reproduce this EM and use RSA_private_encrypt, then you will get the correct PKCS#1 v1.5 signature encoding, the same you would get with RSA_sign or even better, using the generic EVP_PKEY_sign.
I figured I could simply implement the DER encoding myself according to this specification, but the result (EM) seems too long to be encrypted with my key
// 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);
}
Output:
Error:0406C06E:rsa routines:RSA_padding_add_PKCS1_type_1:data too large for key size
Any hints?
UPDATE
As you can see in code below openssl_verify return 1 for result of openssl_sign and even for openssl_private_encrypt result. I tested it on my machine. This solution will work only if sha1 digest in digital signature is used.
// 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));
I just captured DER encoded structure by decrypting openssl_sign() result.
ORIGINAL ANSWER
openssl_sign() creates digest from data because this is how digital signature works. Digital signature is always encrypted digest from data.
You can use openssl_private_encrypt() and openssl_public_decrypt() on your sha1 digest with no fear. In general, it is the same thing but yes, there is a difference. If you encrypt something on your own, the encryption process does not care about data and just encrypts them. It is on you to know that what you will decrypt later is sha1 digest for some data. In fact, it is just data encryption with private key, not true digital signature.
openssl_sign() creates digest from data and encrypts information about kind of digest and digest itself (this is ASN.1 DER structure from your link). This is because openssl_verify() needs to know what kind of digest was used when signing.
According to the English page of openssl_sign:
bool openssl_sign ( string $data , string &$signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )
I think the obvious suggestion is to use OPENSSL_ALGO_SHA256. See openssl_get_md_methods for a list of the supported algorithms.

Verify and decrypt signature generated with a pvk.key and BouncyCastle crypto apis

I have an applet that uses a "foo.key" file and a string password to generate a privateKey object, using BouncyCastle apis. Then a signature is created using this privateKey object and a string such as "MYNAMEHERE". All I know is that the algorythm used to generate this signature is RSA.
What I want to do is to decrypt (or verify) this signature in PHP. I have both files publicKey (file.cer) and privateKey (file.key) that where used to generate the privateKey object in Java.
Im trying using the openssl_verify functions in PHP, passing the values:
openssl_verify("MYNAMEHERE", $signature, "file.cer"), where $signature contains the String representation of the signature object generated in Java: new String (signature).
I dont know if this process is correct to verify the signature, or what kind of encoding/decoding process i have to do before using this function.
I hope somebody points me the right direction!
Thanks in advance!
You haven't given enough information, such as the actual signature or how it is encoded. Normally RSA means RSA in PKCS#1 1.5 mode using SHA-1 (Google it) which is more or less the default signature generation/verification algorithm in use today. In that case, the verify should proceed as you've described. The password is not needed anymore, it might just be used to decrypt the private key. You can still use the private key to see if an sign in PHP/openssl does create the same data. If not, a different hash or PKCS#1 v2.1 signature may have been used.

using phpseclib's Crypt_RSA to encrypt verification code for a bank

I am required to send a query to the bank which contains a verification code $vk_mac in a specified string format. The code has to be a SHA1 hash and RSA encrypted with my public key and presented in base64 format. Unfortunately, so far, I have been unsuccessful - the bank gives me "Wrong signature" and that all the info I'm getting.
What I have is this:
$rsa = new Crypt_RSA();
$rsa->loadKey(file_get_contents("private_key.pem"));
$rsa->loadKey($rsa->getPublicKey());
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$encrypted = $rsa->encrypt(sha1($vk_mac));
$vk_mac = base64_encode($encrypted);
private_key.pem here is my private key in plain text. I tried setting the encryption mode to CRYPT_RSA_ENCRYPTION_OAEP without luck. I am 99.9% sure, that the starting $vk_mac string is formatted correctly and contains all the required details.
Does anybody have any idea what can I be doing wrong? Thank you.
Edit:
I've changed the code to this (where vk_mac is the starting formatted string that needs to be signed and private_key.pem is my the decoded private key):
$rsa = new Crypt_RSA();
$rsa->loadKey(file_get_contents("private_key.pem"));
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$hashed = $rsa->hash->hash($vk_mac);
$encrypted = $rsa->sign($hashed);
$signature = base64_encode($encrypted);
I can tell the generated signature is correct, since when I do this:
$rsa->loadKey($rsa->getPublicKey());
$verified = $rsa->verify($hashed, base64_decode($signature));
$verified returns TRUE.
The bank though, responds "Incorrect signature". Any more ideas?
Edit:
Specification
VK_MAC control code calculation
VK_MAC, for electronic signature, using in the request, for checking and confirming used version of the algorithm, indicated in the parameter VK_VERSION. In this time version 008 is used. VK_MAC is presented as a request parameter in BASE64 coding.
Version 008
The value of the MAC008 function is calculated using the public key algorithm RSA. Values of empty fields are taken into account as well – “000”.
MAC008(x1,x2,…,xn) := RSA(SHA-1(p(x1)|| x1|| p(x2 )|| x2 || … ||p(xn)||xn),d,n)
Where:
|| is an operation of adding the string
x1, x2, …, xn are the query parameters
p is a function of the parameter length. The length is a number in the form of a three-digit string
d is the RSA secret exponent
n is the RSA modulus
The signature is calculated in accordance with the PKCS1 standard (RFC 2437).
What if you try $rsa->sign()? PKCS#1 doesn't do signing by simply encrypting the hash and if your bank is using an interoperable RSA solution they're probably not doing that either.
The code was almost correct - I did not need to hash it again though (thanks #Accipitridae).
The solution was that the merchant's ID had to be uppercase, and not lowercase as provided. It does not say anywhere in the spec that it has to be uppercase as well. Nice.
As mentioned above you can do this easily with openssl. Below is how I would do so.
$hashed = sha1($vk_mac);
openssl_public_encrypt($vk_mac, $encrypted, ($pubkey));
$vk_mac = base6$_encode($encrypted);
Read the documentation on openssl_public_encrypt for more.

Categories