question

martin7 avatar image
martin7 asked finacussolutions answered

Card Encryption for getting card token with PHP

Hello,

I am trying to encrypt the card info for getting Card Token, but I have no idea why the encryption is wrong.

This is what I am doing:

$pa_key = bin2hex( base64_decode( TA_PUBLIC_KEY_DEV ) ) // The key is from the CDN

// According to JAVA example the first 256 characters are modulus and second 256 characters are the exponent. (Unless I read something wrong)

$modulus  = substr( $pa_key, 0, 256 );
$exponent = substr( $pa_key, 256, 256 );

// I use latest version of phpseclib to generate the public key
// https://phpseclib.com/docs/rsa#raw-rsa-public-keys

$rsa_public_key = PublicKeyLoader::load(
   array(
     'e' => new BigInteger( $exponent, 256 ), // I tried also with 16 instead 256
     'n' => new BigInteger( $modulus, 256 ), // I tried also with 16 instead 256
   )
);

// According to JAVA example the prefix is 00000000

$encrypted = $rsa_public_key->encrypt( '00000000' . $card_number );

$pan = base64_encode( $encrypted );


Note that I also tried getting modulus and exponent from the pay/key endpoing and sending a transarmor_key_id in the request for the token, but still got same error response.
I even tried with getting the public key from the PEM key returned form that same endpoint and encrypt the card with it and got the same error again.

The data is the following:

CDN TA_PUBLIC_KEY_DEV:
vyYRQA3cS4wV9yk+6bFzA7KLDmE+D/SOP+Q5bNOPG9nUDkAPalRBz12KA5SDxTw2vO1BIeSFUQlYTpzEDb/XkfNNm5e6nqf12M4kdHP1F2EXW4WArilUZegAVw/Y7FvAkA8PQFbfgmBirSa5GS/fuAHjemqEf0DxIgq552IDeFw3nB0vccK6ePue5sVB9Sm2vWpKm/lj2UE4P6z2ngZr5V31cSAVN08USxHvz+MEnoUBKt6aKvfRUAp4iFyIpxlp4eylxY8zizPekS29lcRMsI9hGug2CoKFhhUJ1gD8G280zIoWCxysNvl40k/l8OTtPKrnlhAzQcIyy/RB0lwb6QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVTU=
Our decoded value of the key above:
bf2611400ddc4b8c15f7293ee9b17303b28b0e613e0ff48e3fe4396cd38f1bd9d40e400f6a5441cf5d8a039483c53c36bced4121e4855109584e9cc40dbfd791f34d9b97ba9ea7f5d8ce247473f51761175b8580ae295465e800570fd8ec5bc0900f0f4056df826062ad26b9192fdfb801e37a6a847f40f1220ab9e76203785c379c1d2f71c2ba78fb9ee6c541f529b6bd6a4a9bf963d941383facf69e066be55df5712015374f144b11efcfe3049e85012ade9a2af7d1500a78885c88a71969e1eca5c58f338b33de912dbd95c44cb08f611ae8360a8285861509d600fc1b6f34cc8a160b1cac36f978d24fe5f0e4ed3caae796103341c232cbf441d25c1be900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065535
Modulus value from the above (the first 256 characters of the string above)
bf2611400ddc4b8c15f7293ee9b17303b28b0e613e0ff48e3fe4396cd38f1bd9d40e400f6a5441cf5d8a039483c53c36bced4121e4855109584e9cc40dbfd791f34d9b97ba9ea7f5d8ce247473f51761175b8580ae295465e800570fd8ec5bc0900f0f4056df826062ad26b9192fdfb801e37a6a847f40f1220ab9e76203785c

Exponent value from the decoded key ( the second 256 charters of the string above, starting 256 position to 512 )
379c1d2f71c2ba78fb9ee6c541f529b6bd6a4a9bf963d941383facf69e066be55df5712015374f144b11efcfe3049e85012ade9a2af7d1500a78885c88a71969e1eca5c58f338b33de912dbd95c44cb08f611ae8360a8285861509d600fc1b6f34cc8a160b1cac36f978d24fe5f0e4ed3caae796103341c232cbf441d25c1be9
Generated RSA Public Key using the modulus and exponent
-----BEGIN PUBLIC KEY-----
MIICIDANBgkqhkiG9w0BAQEFAAOCAg0AMIICCAKCAQBiZjI2MTE0MDBkZGM0Yjhj
MTVmNzI5M2VlOWIxNzMwM2IyOGIwZTYxM2UwZmY0OGUzZmU0Mzk2Y2QzOGYxYmQ5
ZDQwZTQwMGY2YTU0NDFjZjVkOGEwMzk0ODNjNTNjMzZiY2VkNDEyMWU0ODU1MTA5
NTg0ZTljYzQwZGJmZDc5MWYzNGQ5Yjk3YmE5ZWE3ZjVkOGNlMjQ3NDczZjUxNzYx
MTc1Yjg1ODBhZTI5NTQ2NWU4MDA1NzBmZDhlYzViYzA5MDBmMGY0MDU2ZGY4MjYw
NjJhZDI2YjkxOTJmZGZiODAxZTM3YTZhODQ3ZjQwZjEyMjBhYjllNzYyMDM3ODVj
AoIBADM3OWMxZDJmNzFjMmJhNzhmYjllZTZjNTQxZjUyOWI2YmQ2YTRhOWJmOTYz
ZDk0MTM4M2ZhY2Y2OWUwNjZiZTU1ZGY1NzEyMDE1Mzc0ZjE0NGIxMWVmY2ZlMzA0
OWU4NTAxMmFkZTlhMmFmN2QxNTAwYTc4ODg1Yzg4YTcxOTY5ZTFlY2E1YzU4ZjMz
OGIzM2RlOTEyZGJkOTVjNDRjYjA4ZjYxMWFlODM2MGE4Mjg1ODYxNTA5ZDYwMGZj
MWI2ZjM0Y2M4YTE2MGIxY2FjMzZmOTc4ZDI0ZmU1ZjBlNGVkM2NhYWU3OTYxMDMz
NDFjMjMyY2JmNDQxZDI1YzFiZTk=
-----END PUBLIC KEY-----
Card used: 6011361000006668
Prefix used: 00000000

Generated pan using the generated public key for encryption:
MDF6ssGpYN3tbzLy5cMkNM/nefm4YggJTb0+xTVjDXbrSUzJOslb7tJBszxAU+RQhatSwmPHd9kEIl9aQogJSEt8+QfKsh0ivPVnvAU45zSw5S6XVQw+lxnI7DGFdBygh6XFhr7Yzq+00Fwv2bByWBKGH+mSMzuAAhx9bD73b3bymnpXLoG2Jzv90BAdtPSYChENVTfqh1RGyGVnquGP8lsKME9eQsY83r/siHkH76PGXHdV8Axvbp8pD4Hytxm2BwDBsXvGaeDBg7O8vvJOoRMDviHg9e8wfftgWQZcse1BlF8D3vMHu7BwQ2gQsLLKgCmihHYl5Dh13mv0lFUZ2A==

Error returned:
Array
(
    [message] => 500 Internal Server Error
    [error] => Array
        (
            [type] => api_error
            [code] => processing_error
            [message] => An error occurred while processing the token request.
        )

)
Request Body:
{
"card": {
"encrypted_pan": "MDF6ssGpYN3tbzLy5cMkNM/nefm4YggJTb0+xTVjDXbrSUzJOslb7tJBszxAU+RQhatSwmPHd9kEIl9aQogJSEt8+QfKsh0ivPVnvAU45zSw5S6XVQw+lxnI7DGFdBygh6XFhr7Yzq+00Fwv2bByWBKGH+mSMzuAAhx9bD73b3bymnpXLoG2Jzv90BAdtPSYChENVTfqh1RGyGVnquGP8lsKME9eQsY83r/siHkH76PGXHdV8Axvbp8pD4Hytxm2BwDBsXvGaeDBg7O8vvJOoRMDviHg9e8wfftgWQZcse1BlF8D3vMHu7BwQ2gQsLLKgCmihHYl5Dh13mv0lFUZ2A==",
"first6": "601136",
"last4": "6668",
"exp_month": "09",
"exp_year": "23",
"cvv": "123",
"brand": "Discover"
}
}

POST => https://token-sandbox.dev.clover.com/v1/tokens
API TokenDeveloper Pay API
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

zelnaga avatar image
zelnaga answered exelanzgirija commented

Most likely the modulus is this:

bf2611400ddc4b8c15f7293ee9b17303b28b0e613e0ff48e3fe4396cd38f1bd9d40e400f6a5441cf5d8a039483c53c36bced4121e4855109584e9cc40dbfd791f34d9b97ba9ea7f5d8ce247473f51761175b8580ae295465e800570fd8ec5bc0900f0f4056df826062ad26b9192fdfb801e37a6a847f40f1220ab9e76203785c379c1d2f71c2ba78fb9ee6c541f529b6bd6a4a9bf963d941383facf69e066be55df5712015374f144b11efcfe3049e85012ade9a2af7d1500a78885c88a71969e1eca5c58f338b33de912dbd95c44cb08f611ae8360a8285861509d600fc1b6f34cc8a160b1cac36f978d24fe5f0e4ed3caae796103341c232cbf441d25c1be9

...and the exponent is this:

65535

65537 is a really common RSA exponent. 65535 isn't so common so who knows. I suppose it could be 415029 as well (eg. pack('H*', '065535')).

The modulus I posted is 512-bytes vs 256-bytes but it's only 512-bytes in base-16 - in base-256 it's 256-bytes, which is consistent with what you're saying.

So if you use the 512-byte modulus as the string you'd do this:

new BigInteger('...', 16);

...and for the exponent, possibly, this:

new BigInteger('65535);
7 comments
10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

martin7 avatar image martin7 commented ·

I tried and didn't work.

The exponent being 415029 (btw how did you get 415029 from 065535?) seems correct because the same exponent is returned from the pay/key endpoint, but the modulus returned from the endpoint is different "2413028797502124404270222380568....". Maybe needs some decoding since its length doesn't seem right and is 617 characters .

0 Likes 0 ·
martin7 avatar image martin7 commented ·

So dechex(415029) returns 65535 and also I tried converting the modulus returned from the pay/key endpoint to hex and got the same value as the 512 one above. So I am getting these correct and still getting the same error.

0 Likes 0 ·
martin7 avatar image martin7 commented ·

Finally got it.

I used the native PHP function for encryption

openssl_public_encrypt( '00000000' . $card_number, $encrypted, "$rsa", OPENSSL_PKCS1_OAEP_PADDING );

Bellow full code:

$pa_key = bin2hex( base64_decode( TA_PUBLIC_KEY_DEV ) );

$modulus  = substr( $pa_key, 0, 512 );
$exponent = substr( $pa_key, -5 );

$rsa_public_key = PublicKeyLoader::load(
   array(
     'e' => new BigInteger( $exponent, 16 ),
     'n' => new BigInteger( $modulus, 16 ),
   )
);

openssl_public_encrypt( '00000000' . $card_number, $encrypted, "$rsa", OPENSSL_PKCS1_OAEP_PADDING );

$pan = base64_encode( $encrypted );
0 Likes 0 ·
David Marginian avatar image David Marginian ♦♦ martin7 commented ·
Thank you for posting your solution, I'm glad you got it working and I am sure this will be helpful to others in the future.
1 Like 1 ·
exelanzgirija avatar image exelanzgirija David Marginian ♦♦ commented ·

Hi David, I am able to generate the pan. But while using this pan to create card token i am getting below error. Could you please help me?


{

"message": "400 Bad Request",

"error": {

"type": "invalid_request_error",

"code": "invalid_request",

"message": "Please provide either raw pan or encrypted pan."

}

}


************************** API Request **************************
Here is my API request:

<?php

require_once('vendor/autoload.php');


$client = new \GuzzleHttp\Client();


$response = $client->request('POST', 'https://token-sandbox.dev.clover.com/v1/tokens', [

'body' => '{"card":{"brand":"VISA","encrypted_pan":"WxT5sFWwHdhZZt+wijzt1OhO7ekiwO6XUa6fkk+EuxSiC9J4JLPwzyEulS4ltp6V7uW9+khSskCMBmXXB7hoGxEnQcfPjn6u5906yJe8bCzjjVKmH0z9pYwQcvDbZX2KoOEtcAQRDPO5uQMyj1Og4IJeHTRNmiTq3HHr4NqIORf8T/fl9RK0z+TI6aIk1c4qKaJj8lZMuE3GSLkJ4PBr7ORofZPmpvhFMA37g9s7mBAK4ES5r/+mDAZP5RoaSE/ISuUsrP25kmElHvdjfnemro9Ga/EFj/14KSjsAXKR++yNMrJ4Ai1e9Wl0OsaV78rF9lj+Us8BDzf2W5RvUE0a1g==","number":"4242424242424242","exp_month":"07","exp_year":"2025","cvv":"123","last4":"4242","first6":"424242"}}',

'headers' => [

'accept' => 'application/json',

'apikey' => '6fbcb53820d1de2ee765d1fdc9d0e044',

'content-type' => 'application/json',

],

]);


echo $response->getBody();

0 Likes 0 ·
exelanzgirija avatar image exelanzgirija martin7 commented ·

Hi Martin, I am able to generate the pan. But while using this pan to create card token i am getting below error:


{

"message": "400 Bad Request",

"error": {

"type": "invalid_request_error",

"code": "invalid_request",

"message": "Please provide either raw pan or encrypted pan."

}

}


************************** API Request **************************
Here is my API request:

<?php

require_once('vendor/autoload.php');


$client = new \GuzzleHttp\Client();


$response = $client->request('POST', 'https://token-sandbox.dev.clover.com/v1/tokens', [

'body' => '{"card":{"brand":"VISA","encrypted_pan":"WxT5sFWwHdhZZt+wijzt1OhO7ekiwO6XUa6fkk+EuxSiC9J4JLPwzyEulS4ltp6V7uW9+khSskCMBmXXB7hoGxEnQcfPjn6u5906yJe8bCzjjVKmH0z9pYwQcvDbZX2KoOEtcAQRDPO5uQMyj1Og4IJeHTRNmiTq3HHr4NqIORf8T/fl9RK0z+TI6aIk1c4qKaJj8lZMuE3GSLkJ4PBr7ORofZPmpvhFMA37g9s7mBAK4ES5r/+mDAZP5RoaSE/ISuUsrP25kmElHvdjfnemro9Ga/EFj/14KSjsAXKR++yNMrJ4Ai1e9Wl0OsaV78rF9lj+Us8BDzf2W5RvUE0a1g==","number":"4242424242424242","exp_month":"07","exp_year":"2025","cvv":"123","last4":"4242","first6":"424242"}}',

'headers' => [

'accept' => 'application/json',

'apikey' => '6fbcb53820d1de2ee765d1fdc9d0e044',

'content-type' => 'application/json',

],

]);


echo $response->getBody();

0 Likes 0 ·
exelanzgirija avatar image exelanzgirija exelanzgirija commented ·

Here is my PHP code to generate PAN:



<?php

include 'vendor/autoload.php';

use phpseclib3\Crypt\PublicKeyLoader;

use phpseclib3\Crypt\RSA;

use phpseclib3\Math\BigInteger;

use phpseclib3\Net\SSH2;


//card number

$card_number = "4242424242424242";


//Get Public Encryption Key from CDN ...

$PUBLIC_ENCRYPTION_KEYS_URL = "https://checkout.clover.com/assets/keys.json";

$TA_PUBLIC_KEYS_JSON_ARRAY = file_get_contents($PUBLIC_ENCRYPTION_KEYS_URL);

$TA_PUBLIC_KEYS = json_decode($TA_PUBLIC_KEYS_JSON_ARRAY,true);

//Parse the Base64 public key string (returned by the CDN). Obtain the modulus and exponent.

$pa_key = bin2hex( base64_decode( $TA_PUBLIC_KEYS['TA_PUBLIC_KEY_DEV'] ) );

$modulus = substr( $pa_key, 0, 512 );

$exponent = substr( $pa_key, -5 );


//Generate an RSA public key using the modulus and exponent values.

$rsa_public_key = PublicKeyLoader::load(

array(

'e' => new BigInteger( $exponent, 16 ),

'n' => new BigInteger( $modulus, 16 ),

)

);

//print_r($rsa_public_key); exit;

//Prepend the prefix value to the card number.

//Using the public key, encrypt the combined prefix and card number.

openssl_public_encrypt( '00000000'.$card_number, $encrypted, $rsa_public_key, OPENSSL_PKCS1_OAEP_PADDING );

$pan = base64_encode( $encrypted );

print_r($pan);

?>

Could you please help me?

0 Likes 0 ·
blasthaus avatar image
blasthaus answered

sorry I just say you have $encrypted returned by openssl_public_encrypt. Were you able to get it to work? I'm also looking for a snipped to use for this.

10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

finacussolutions avatar image
finacussolutions answered

To encrypt a card and get a token in PHP, you can use a payment gateway's API like Stripe or Braintree. These services provide secure methods to tokenize card details without storing sensitive data on your server. You will typically send the card information to the gateway via a secure connection, which returns a token. This token can then be used for future transactions without exposing the card details directly.


10 |2000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Welcome to the
Clover Developer Community