I've spent this afternoon fighting with Paypal's developer sandbox to make encrypted web payments work. This is the system of whereby the details of an order are transferred as a form field encrypted with public key cryptography, but I wouldn't expect anyone to know that because Paypal has a wide glossary of internal terminology that is almost impenetrable to the novice.
I have never done a particularly large amount with Paypal. It's come up occasionally but I've always done the hastiest job possible, perhaps pasting some code from PaypalTech.com or something. This time I've been working in Python with Django so I've had to develop everything from scratch with the M2Crypto OpenSSL wrapper. Paypal's developer documentation lacks sufficient detail for a clean-room implementation (thought there are numerous examples to be found, none exactly corresponded to M2Crypto/Python). Furthermore, it does not give useful error messages, which can make it extremely troublesome to integrate with.
This is how I made EWP work.
- Paypal expects data to arrive as a set of key-value pairs, which you should already have/know about. The documentation for these is extensive.
- Make the payment system work unencrypted, using input fields with the corresponding names and values to the key-value pairs. This ensures that the information PayPal needs to receive is correct before you start faffing with encrypting that data. If you do not have a PayPal account, you can sign up for a "sandbox" account, then register as many scratch accounts as you like. Note that PayPal does not email you the confirmation emails for sandbox account; they appear in the sandbox interface under the "Emails" tab.
- Generate an SSL keypair in PEM format with
openssl genrsa. If you view this file, it is titled as "PRIVATE KEY", but it does contain both private and public keys. The exact commandline arguments for this command are documented in the PayPal Website Payments Standard Integration Guide.
- Generate a self-signed SSL certificate from your keypair with
openssl req. An SSL certificate contains your public key, some details about the owner, and one or more cryptographic signatures. Again, this is well documented.
- Exchange certficates with PayPal by logging in and visiting "Encrypted Web Payments" under "Profile". You save PayPal's certificate to a file, and upload your own. Paypal assigns a certificate ID to your certificate, which you must now add to your key-value pairs under the key
cert_id. It displays this certificate ID in the table and will also email you a copy. Recall however, that PayPal's sandbox development server does not actually email you; "emails" are stored and are available on the web interface under the "Emails" tab.
- Generate the plaintext for the signing by encoding the key-value pairs as
key=value, separated only by linefeed (\n, ASCII 0x0a) characters. CR-LF does not work.
- Sign the plaintext using S/MIME. This requires both your private key (for the cryptography) and your certificate (to identify whose signature it is). Use these options:
- Use binary input mode, which prevents OpenSSL munging its input.
- Encode the data in opaque form. This implies that the text to be signed is encoded along with the signature, as opposed to detached, which doesn't encode the plaintext.
- Output the resulting PKCS7 structure in DER (not PEM, nor S/MIME) format. This is a binary format.
- Encrypt the resulting DER using PayPal's certificate (ie. public key):
- Again, use binary input mode
- Use the 3-key triple-DES, CBC mode block cipher. OpenSSL calls this des-ede3-cbc or just des3.
- Output the resulting PKCS7 structure in PEM format, which is a base64-encoded format.
- Insert the PEM blob into a form field named
encrypted. There must also be a hidden value form field, named
cmd, with value