This page has been designed specifically for the printed screen. It may look different than the page you were viewing on the web.
Please recycle it when you're done reading.

The URI for this page is { http://blog.scrobbld.com }

Protecting your payments with EWP Posted on February 15th

This probably should have come first in the series of PayPal technologies, as it concerns protecting the way that you (well, your website) sends information to PayPal.

Encrypted website Payments (EWP) is a useful tool to stop your customers looking for more of a deal than they are entitled to. First, we will consider the button code below, which creates a PayPal buy now button.

1
2
3
4
5
6
7
8
<form name="_xclick" action="https://www.paypal.com/uk/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="business" value="">
<input type="hidden" name="currency_code" value="GBP">
<input type="hidden" name="item_name" value="Teddy Bear">
<input type="hidden" name="amount" value="12.99">
<input type="image" src="http://www.paypal.com/en_GB/i/btn/x-click-but01.gif" border="0" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
</form>

These are all hidden fields, that cannot be seen directly by your customer when they are looking at your website. They will only see the input type=”image” … field which links to an image on the PayPal server. But what if your customer is aware of the intricacies of the Buy Now button? If they use Firefox, and have installed Firebug, it is more than easy to edit the DOM of the page, and post that off to PayPal instead. So instead of having the amount at 12.99, I might set it to:

<input type="hidden" name="amount" value="0.99">

Sure, this isn’t really a problem if you’re selling tangible goods, as you would probably notice that they were trying to buy it for the cost of a machine made coffee. But what if you’re automatically delivering ebooks? The customer will have got a copy of your digital file for a fraction of the price. Obviously, this is not something you want to happen (unless you’re feeling extremely generous).

So in fact you can use the class below for EWP, which will encrypt your website payments (a PHP4 file courtesy of Mr. Ivor Durham).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
class PayPalEWP {
  var $certificate;	// Certificate resource
  var $certificateFile;	// Path to the certificate file
 
  var $privateKey;	// Private key resource (matching certificate)
  var $privateKeyFile;	// Path to the private key file
  var $paypalCertificate;	// PayPal public certificate resource
  var $paypalCertificateFile;	// Path to PayPal public certificate file
  var $certificateID; // ID assigned by PayPal to the $certificate.
  var $tempFileDirectory;
 
  function PayPal() {
  }
 
  /*
    setCertificate: set the client certificate and private key pair.
 
    $certificateFilename - The path to the client certificate
 
    $keyFilename - The path to the private key corresponding to the certificate
 
    Returns: TRUE iff the private key matches the certificate.
   */
 
  function setCertificate($certificateFilename, $privateKeyFilename) {
    $result = FALSE;
 
    if (is_readable($certificateFilename) && is_readable($privateKeyFilename)) {
      $certificate =
	openssl_x509_read(file_get_contents($certificateFilename));
 
      $privateKey =
	openssl_get_privatekey(file_get_contents($privateKeyFilename));
 
      if (($certificate !== FALSE) &&
	  ($privateKey !== FALSE) &&
	  openssl_x509_check_private_key($certificate, $privateKey)) {
	$this->certificate = $certificate;
	$this->certificateFile = $certificateFilename;
 
	$this->privateKey = $privateKey;
	$this->privateKeyFile = $privateKeyFilename;
 
	$result = TRUE;
      }
    }
 
    return $result;
  }
 
  /*
    setPayPalCertificate: Sets the PayPal certificate
 
    $fileName - The path to the PayPal certificate.
 
    Returns: TRUE iff the certificate is read successfully, FALSE otherwise.
   */
 
  function setPayPalCertificate($fileName) {
    if (is_readable($fileName)) {
      $certificate = openssl_x509_read(file_get_contents($fileName));
 
      if ($certificate !== FALSE) {
	$this->paypalCertificate = $certificate;
	$this->paypalCertificateFile = $fileName;
 
	return TRUE;
      }
    }
 
    return FALSE;
  }
 
  /*
    setCertificateID: Sets the ID assigned by PayPal to the client certificate
 
    $id - The certificate ID assigned when the certificate was uploaded to PayPal
   */
 
  function setCertificateID($id) {
    $this->certificateID = $id;
  }
 
  /*
    setTempFileDirectory: Sets the directory into which temporary files are written.
 
    $directory - Directory in which to write temporary files.
 
    Returns: TRUE iff directory is usable.
   */
 
  function setTempFileDirectory($directory) {
    if (is_dir($directory) && is_writable($directory)) {
      $this->tempFileDirectory = $directory;
      return TRUE;
    } else {
      return FALSE;
    }
  }
 
  /*
    encryptButton: Using the previously set certificates and tempFileDirectory
    encrypt the button information.
 
    $parameters - Array with parameter names as keys.
 
    Returns: The encrypted string for the _s_xclick button form field.
   */
 
  function encryptButton($parameters) {
    // Check encryption data is available.
 
    if (($this->certificateID == '') ||
	!IsSet($this->certificate) ||
	!IsSet($this->paypalCertificate)) {
      return FALSE;
    }
 
    $clearText = '';
    $encryptedText = '';
 
    // Compose clear text data.
 
    $clearText = 'cert_id=' . $this->certificateID;
 
    foreach (array_keys($parameters) as $key) {
      $clearText .= "\n{$key}={$parameters[$key]}";
    }
 
    $clearFile = tempnam($this->tempFileDirectory, 'clear_');
    $signedFile = preg_replace('/clear/', 'signed', $clearFile);
    $encryptedFile = preg_replace('/clear/', 'encrypted', $clearFile);
 
    $out = fopen($clearFile, 'wb');
    fwrite($out, $clearText);
    fclose($out);
 
    if (!openssl_pkcs7_sign($clearFile, $signedFile, $this->certificate, $this->privateKey, array(), PKCS7_BINARY)) {
      return FALSE;
    }
 
    $signedData = explode("\n\n", file_get_contents($signedFile));
 
    $out = fopen($signedFile, 'wb');
    fwrite($out, base64_decode($signedData[1]));
    fclose($out);
 
    if (!openssl_pkcs7_encrypt($signedFile, $encryptedFile, $this->paypalCertificate, array(), PKCS7_BINARY)) {
      return FALSE;
    }
 
    $encryptedData = explode("\n\n", file_get_contents($encryptedFile));
 
    $encryptedText = $encryptedData[1];
 
    @unlink($clearFile);
    @unlink($signedFile);
    @unlink($encryptedFile);
 
    return $encryptedText;
  }
}

I had a few problems initially with PayPal not accepting the encrypted string, so you should also add a function similar to this one:

function getEncryptedString($params) {
	return "-----BEGIN PKCS7-----" . str_replace("\n", "", $this->encryptButton($params)) . "-----END PKCS7-----";
}

Consequently, the usage would be something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  include "PayPalEWP.php";
  $paypal = &new PayPalEWP();
  //make sure the dir is writable, maybe create a /certs dir instead.
  $paypal->setTempFileDirectory('/tmp');
  $paypal->setCertificate('my-prvkey.pem', 'my-pubcert.pem');
  $paypal->setCertificateID('WhateverYourCertificateIDis');
  $paypal->setPayPalCertificate('paypal_cert_pem.txt');
 
  $parameters = array("cmd" => "_xclick",
		      "business" => "sales@mycompany.com",
		      "item_name" => "Cat Litter #40",
		      "amount" => "12.95",
		      "no_shipping" => "1",
		      "return" => "http://mycompany.com/paypal_ok.php",
		      "cancel_return" => "http://mycompany.com/paypal_cancel.php",
		      "no_note" => "1",
		      "currency_code" => "USD",
		      "bn" => "PP-BuyNowBF"
  );

With the HTML looking something like this (for the US sandbox).

1
2
3
4
5
  <form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post">
  <input type="hidden" name="cmd" value="_s-xclick">
  <input type="image" src="https://www.sandbox.paypal.com/en_US/i/btn/x-click-but23.gif" border="0" name="submit" alt="Make payments with PayPal - it\'s fast, free and secure!">
  <input type="hidden" name="encrypted" value="<?php echo $paypal->getEncryptedString($parameters); ?>">
  </form>

Which, when the content is posted, will send an encrypted string to PayPal, which cannot be changed (otherwise PayPal will register an error and the transaction cannot be completed). About now though, you might be wondering how to actually get all those pem, cert and ids from. So I’ll demonstrate how.

Generating Keys, Certificates, IDs

Referring to the document at PayPal, you have to first generate a Private Key, a Public Certificate, upload your Public Certificate to PayPal, who will then give you a Certificate ID. This may look a little complicated at first, and can be a bit confusing, but following the steps one by one will make it a very simple procedure.

If you are using Windows (at least the computer you want to generate the private key) you can use the Windows version of OpenSSL for 32bit machines, which works fine on XP (32bit). If you are using Linux you will probably have OpenSSL already installed on your machine, though the following instructions will be for Windows users.

Once you have installed OpenSSL, goto ‘Start > Run > type cmd’. If you installed in the default directory, it will be under C:\OpenSSL\. To generate the private key type in the following at the command prompt:

cd C:\OpenSSL
openssl genrsa -out my-prvkey.pem 1024

Which will create the file my-prvkey.pem in the C:\OpenSSL directory. Subsequently, to generate your public certificate, type in:

openssl req -new -key my-prvkey.pem -x509 -days 365 -out my‑pubcert.pem

Which will generate the file my-pubcert.pem in C:\OpenSSL, please provide factual information to the OpenSSL program when prompted. Now you can upload this file to PayPal, making sure you are logged in, goto https://www.paypal.com/uk/cgi-bin/webscr?cmd=_profile-website-cert.

Scroll to the bottom of the page and select “Add”, then when prompted, provide the path to my-pubcert.pem. This will upload your public certificate to the server. You can now download the PayPal certificate, as well as the certificate ID (which is next to the list of files you have uploaded). Finally, upload these three files to your server, and set the Certificate ID to the one provided by PayPal:

  $paypal->setTempFileDirectory('/tmp');
  $paypal->setCertificate('my-prvkey.pem', 'my-pubcert.pem');
  $paypal->setCertificateID('WhateverYourCertificateIDis');
  $paypal->setPayPalCertificate('paypal_cert_pem.txt');

And that’s almost everything you have to do. One more thing is to set PayPal to only accept payments which use encryption, which is obviously worth the effort if you only sell on one site (if you sell using eBay or other sites that don’t encrypt then don’t make this alteration).

Anyway, happy selling!

Trackback URL

Some Responses to “Protecting your payments with EWP” :

  1. Saw this on PDNCommunity the other day, but their site seems to have disappeared. So glad you have this up here. I got it working with just a little bit of work!

    Commented Charlie on March 5th, 2010.
  2. Hi, you code is very neat concise. I like it.
    However, it still can’t work for me and receiving message 4003.
    After google it, one said that related to \r\n issue.
    Is your server using Linux OS?
    Mine is Microsoft IIS, I tried with play with \r\n and fopen(xxx,’wt’) but can’t solve the problem also.
    Any suggestion?
    Thanks!

    Commented Keith on July 25th, 2010.
  3. Hi, when i veiw the source, it only comes out with:. It dosent echo the encrpytion..

    Commented Lee on August 6th, 2010.
  4. —–BEGIN PKCS7———-END PKCS7—–. It removed the html from my last comment, this is all it comes out with in the veiw source

    Commented Lee on August 6th, 2010.
  5. Thanks a lot for this tutorial, I have not put it to work right now, but I know this is gonna be very useful!!

    Commented Carl L. on September 24th, 2010.
  6. I suspect you haven’t got the correct path for your certificates – as the function returns false if things aren’t set properly.

    Commented admin on October 10th, 2010.
  7. Encrypting Transactions with Paypal…

    Initial setup of PayPal’s “Donate Now” button was fairly simple and it worked! As I began thinking ahead about adding the “Event Registration” plugin, however, I was reviewing my PayPal profile and added the setting to onl…

    Commented My Ways on October 25th, 2010.
  8. I had some trouble with this script, kept getting error msg on paypal live site “email address not found in blob”.

    I ended up changing the following lines of code and everything worked as expected:

    line 126 to: $clearText = ‘cert_id=’ . $this->_pp_certificate_id . chr(10);
    line 129 to: $clearText .= $key . ‘=’ . $hash[$key] . chr(10);

    HTH

    Commented Scott on March 14th, 2011.
  9. Hi, thanks very much for your addition Scott; everytime I implement this script it changes between PayPal being fine with things, to it not accepting the encrypted string. It usually is something to do with line breaks, but seems to depend on which way the wind is blowing..!

    Commented admin on April 12th, 2011.
  10. IMPORTANT:
    $paypal->setCertificate(‘my-prvkey.pem’, ‘my-pubcert.pem’);
    should be reversed:
    $paypal->setCertificate(‘my-pubcert.pem’, ‘my-prvkey.pem’);
    the first parameter is the certificate and the second is the private key otherwise you get:
    Warning: openssl_x509_read() [function.openssl-x509-read]: supplied parameter cannot be coerced into an X509 certificate!

    Commented Justin on November 12th, 2012.
  11. Great post! Just to let others know I kept getting ‘The email address for the business is not present in the encrypted blob. Please contact your merchant.’. My development machine is on windows and the problem was down to the temporary file, it needs changing to following as only 3 characters are supported on windows.
    $clearFile = tempnam($this->tempFileDirectory, ‘cle’);
    $signedFile = preg_replace(‘/cle/’, ‘signed’, $clearFile);
    $encryptedFile = preg_replace(‘/cle/’, ‘encrypted’, $clearFile);

    BTW the line breaks were OK for me but I did have the swap the certificate and key as in the above post.

    Commented Steve on April 26th, 2013.
Leave your own comments about this post: