GnuPG Encryption with PHP

I found PHP’s documentation on the GnuPG functions to be pretty sparse, so thought I would share some specific steps that I went though in order to get everything working.

Prerequisites

First off, you have to install the GnuPG PHP libraries through pecl. It requires the GnuPG Made Easy (gpgme) packages to get working. The following shell commands will install the OS packages, install the GnuPG PHP libraries, then enable the PHP extension and restart Apache:

# apt-get install gnupg gpgme gpgme-devel

# pecl install gnupg

# echo extension=gnupg.so > /etc/php.d/gnupg.ini

# apachectl restart

Creating GnuPG Keys

Next, you need to create a set of keys to encrypt and decrypt your data. You’ll need to put the keys somewhere where the webserver can read and write to a directory. I’ll use /var/www/.gnupg since that is the default home directory for many Apache installations. After running the gpg command, answer the questions as prompted. User input is red in the output shown below.

# mkdir -p /var/www/.gnupg

# gpg --homedir /var/www/.gnupg --gen-keygpg
WARNING: unsafe permissions on homedir `/tmp/keys'

gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
gpg: keyring `/tmp/keys/secring.gpg' created
gpg: keyring `/tmp/keys/pubring.gpg' created
Please select what kind of key you want:
   (1) DSA and Elgamal (default)
   (2) DSA (sign only)
   (5) RSA (sign only)
Your selection? 1
DSA keypair will have 1024 bits.
ELG-E keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 2048
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 10y
Key expires at Fri Feb 23 16:35:14 2018 PST
Is this correct? (y/N) y
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
Real name: Some User
Email address: some@user.com
Comment: This is a key for Some User
You selected this USER-ID:
    "Some User (This is a key for Some User) <some@user.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key. Enter your passphrase here
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /tmp/keys/trustdb.gpg: trustdb created
gpg: key 21CCC3D6 marked as ultimately trusted
public and secret key created and signed.
.... a bunch of random characters here....
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2018-02-24
pub   1024D/21CCC3D6 2008-02-27 [expires: 2018-02-24]
      Key fingerprint = FA45 1EE9 8772 70EF 1CFA  99CE 048A 6139 21CC C3D6
uid                  Some User (This is a key for Some User) <some@user.com>
sub   2048g/A83E754B 2008-02-27 [expires: 2018-02-24]
#chown -R apache:apache /var/www/.gnupg

Make note of the key fingerprint in the 4th from the bottom line. You’ll need this in your PHP code when referencing the key. Also, make sure that you write down your pass phrase somewhere. Your encrypted data will be useless if you don’t have the pass phrase.

Your Application

Now you can write your PHP code that will do the encryption. Here is a sample that encrypts, then decrypts something:

<?php
$CONFIG['gnupg_home'] = '/var/www/.gnupg';
$CONFIG['gnupg_fingerprint'] = 'FA451EE9877270EF1CFA99CE048A613921CCC3D6';

$data = 'this is some confidential information';

$gpg = new gnupg();
putenv("GNUPGHOME={$CONFIG['gnupg_home']}");
$gpg->seterrormode(GNUPG_ERROR_SILENT);
$gpg->addencryptkey($CONFIG['gnupg_fingerprint']);
$encrypted =  $this->gpg->encrypt($data);
echo "Encrypted text: \n<pre>$encrypted</pre>\n";

// Now you can store $encrypted somewhere.. perhaps in a MySQL text or blob field.

// Then use something like this to decrypt the data.
$passphrase = 'Your_secret_passphrase';
$gpg->adddecryptkey($CONFIG['gnugp_fingerprint'], $passphrase);
$decrypted = $gpg->decrypt($encrypted);

echo "Decrypted text: $decrypted";
?>

It would be best to store $passphrase somewhere completely separate from your application configuration. Perhaps an admin user would be required to enter the passphrase when looking up this information. That way your passphrase is not stored in your config file or anywhere that an attacker could potentially gain access to it.

Troubleshooting

Make sure that the web server can write to the GnuPG Home directory. This obviously is not ideal, but seems to be required in the testing that I have done. I’ve been able to set ‘secring.gpg’ to be owned by root, but that does little good since the directory it is in has to be writable.

You can raise the error mode to GNUPG_ERROR_WARNING to generate PHP warnings on GnuPG errors. That might help to track down where errors are occurring

Receive $50 to $210 in free Money

I don’t generally post about non-technical things on this blog, but these deals are too good to pass up. I just received my payments for $85 and am expecting $100 more by doing nothing more than signing up for a couple of web sites. There is no obligation required, you just need to sign up at these sites and provide them with some personal information and a bank account so that you can withdraw your money. Both sites are reputable companies.

LendingClub.com

The first free money comes from LendingClub.com. LendingClub is a peer to peer lending site when people can make to or receive loans from other people. The site gives you $25 just for signing up when you follow this link. Their referral program means that I earn $25 for referring you, and you if you are married, you can refer your spouse so you each earn $25. You can then withdraw your $50 and your spouse’s $25 with nothing more required. It takes about 4 business days between signing up and confirming your bank account, and then 4 more days to get the cash in your bank account.

If you have some extra money in your bank account and feel up for it, then each of those $25 bonuses double to $50 when your first bank activity is transferring $1000 into your LendingClub account. You would receive an additional $25 for signing up for you and your spouse, and the referral fee doubles to $50 when the referred person transfers in the $1000. Your $1000 can be immediately withdrawn without loaning it on the site. It ties up your $1000 for about 4 days when the money is transferred into the account, and 4 more days when you withdraw it back into your bank account.

So if you are married and take advantage of the $1000 bonus, you and your spouse can earn $150 from LendingClub for free. Instead of withdrawing it, you might actually try lending it to people on the site. If you invest your free $150 in some investments, you would receive around $4.80 per month for the next 36 months totaling $172.80.

RevolutionMoneyExchange.com

RevolutionMoneyExchange is a new site that seems to be competing directly with PayPal. The benefit over PayPal is that any transfers between members are completely free (Paypal charges 3% + $0.30). Seems like it is worth trying out and I know that PayPal needs some competition. They also give you $25 just for signing up and $10 for referring someone. So you could earn $60 free by signing up and referring your spouse.

The fee is only available when somebody refers you, so just let me know your email address in the box below, and I’ll send you an invitation.

Quick WordPress Upgrade

WordPress has had a few security vulnerabilities recently, and I thought it best to upgrade all of my blogs. Although it is not at easy as dummy-proof as I would like, upgrading WordPress is pretty simple. The key to doing it safely is in not modifying any of the core wordpress files. As long as you don’t customize those at all, upgrading is a piece of cake. I just had to do it on three blogs that, and it took about a minute a piece. From a shell, just run these commands

cd <your_wordpress_directory>
mkdir /tmp/wpbackup
cp -R * /tmp/wpbackup
wget -O /tmp/wordpress.tar.gz http://wordpress.org/latest.tar.gz
tar -xvzf /tmp/wordpress.tar.gz  -C /tmp
unalias cp
cp -R /tmp/wordpress/* .
alias cp='cp -i'

Then hit your admin page in a web browser. If the database layout changed at all, you should be prompted to update your database with a single click. Once everything is done, look at the bottom of your admin page to make sure it is the current version.

PHP Functions to Convert APR to APY and APY to APR

Thanks to The Finance Buff for these formulas.

Here are some simple PHP functions to convert and APY to an APR and back

function apy_to_apr($rate, $periods = 365)
{
    return  ( $periods *  pow( (1+$rate), (1/$periods) )) - $periods;
}

function apr_to_apy($rate, $periods = 365)
{
    return ( pow( (1+ ($rate/$periods)), $periods) - 1);
}

When Random Isn’t Very Random

I have a PHP function that I wrote a long time ago to generate a random string of characters:

function randomString($size = 25)
{
    $charset = 'abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
    $string = '';
    for ($i=0; $i < $size; $i++) {
        $string .= $charset[(mt_rand(0,(strlen($charset) -1)))];
    }
    return $string;
}

I have been using this to generate a random ID, which is then inserted into a database column as a unique key. Theoretically, it has
6225 possible unique values (or 6.45 * 1044) if case sensitive or 3625 (2.36 * 1035) when used in a case-insensitive application. That is a lot of possible combinations, and I figured it would be rare that I’d get two that ever were the same.

I was wrong.

I must be running into some kind of issue where the pseudo-randomness is not as random as I thought. I’m inserting somewhere in the neighborhood of 25k rows a day into this table for the past couple weeks and have had over 20 errors generated where the database complained that the unique key already existed. I investigated a couple and found that it was, indeed correct. My database is not case sensitive and would have complained if the two had the same characters even if the cases weren’t the same. So I was pretty surprised when I looked at the errors and found that in each case the 25 character strings were exactly the same, even all of the letters in it were the same case.

So I’ve had to revert to another method that I like a little better anyway.

Google Spam Filtering Sounds Great but I Can’t Sign Up

Google announced yesterday new services and pricing based on their Postini message filtering service.  The service sounds great, and I’ve been looking at moving away from my current mail filtering service for a couple months now.  Pricing starts out at only $3.00 per user per year.  I did a little checking around, and verified that I can add domain aliases and user aliases and that it looks like they can be tied to a single $3.00 account.

That is exactly like what I need.  I have a bunch of domains, and use several email addresses at each one that all forward to a single Inbox.  For $3.00 a year, it sounds like a great savings over my alternate plan which was creating my own MailScanner box.  Plus with Google, I won’t have to worry about redundancy, or keeping my own filtering up to date.

Perfect, so I went to sign up.  I put in my domain name, agreed to the TOS, then put in my credit card information and hit submit:

Google Won’t let me sign up for Posting

Oops, looks like something went wrong there. That’s not the best way to instill confidence into your new customers.

Amazon ECS 3.0 is shutting down

Amazon’s long lived ECS 3.0 will be shutting down soon. This was an early version of their API that allowed 3rd party applications to access Amazon’s vast database of books and products. It was very widely used, and it will be interesting to see what kind of impact it has when they turn it off for good on March 31st. I’m sure there are plenty of small sites that will break in some way when they turn it off.

From what I’ve seen, they have been pretty good at notifying customers who are still using it. I’ve gotten several emails about it in the past couple weeks. Despite all of their efforts, though, I’m sure that there has got to be all kinds of small sites that were written at one point and haven’t been touched since.

The easiest way to tell if you have a site that uses it, is to grep through all of your code for ‘AssociateTag’ or ‘SubscriptionId’. Those are the authentication parameters used by the 3.0 version that it shutting down. The newer version of ECS uses a ‘AWSAccessKeyId’ parameter instead.

If you have a PHP or Perl-based website that needs to be upgraded to the new version, you can hire me to fix it.

GoDaddy’s DNS Doesn’t Update SOA Serial

I recently moved one of my blog’s to it’s own IP address, but strangely Google’s feed readers are still picking up that site on the original IP Address. It has been several days now, and Google is still requesting the site at the old IP Address. I did some digging and found that even though I changed the IP Address, the SOA Serial didn’t get incremented. As a result, Google’s DNS servers are using cached records and not requesting new ones because the serial hasn’t changed.

This seems like a pretty serious problem for GoDaddy. I double checked everything again tonight by creating some new records. The new records resolve to the IP’s that I specified, but the serial remains unchanged.

I tried various things that definitely should have caused the serial number to be incremented:
– Adding a new A record,
– Modifying an A record
– Deleting an A record

None of which updated the serial as it should have.

Finally, I noticed on the main page for my domain (the one that lists the name servers, registrant info, etc) that next to Name Servers: it said Last Update: 11/23/2007, which coincided with the date of the serial. I was finally able to update the Serial by acting like I was changing my name servers, then just submitting the page without making any changes.

It seems this is a fundamentally broken DNS system though. Frankly, I’m pretty surprised to have something like that from GoDaddy, where I’m sure you do DNS for hundreds of thousands of domains. While troubleshooting, I emailed GoDaddy’s support a couple times and were less than helpful. Their basic response was:

We are unable to update the SOA serial on demand. This information is updated periodically, and is the way our systems currently process. We apologize for any inconvenience this may cause.

Converting mbox’s to maildir format

There is a handy utility for converting mbox style mailboxes into maildir format at http://batleth.sapienti-sat.org/projects/mb2md/

To convert all of the mailboxes on your server:

Edit /etc/sudoers and comment out the env_keep section. These variables make it so that the sudo command keeps some environment variables and tries to put things in the wrong directory.

Download mb2db, unzip it, and copy the binary to /bin (where all users can access it)

# wget http://batleth.sapienti-sat.org/projects/mb2md/mb2md-3.20.pl.gz
# gunzip mb2db-3.20.pl.gz
# cp mb2db-3.20.pl.gz / bin

Then run this command to convert all of the mailboxes into maildir format.

cd /var/spool/mail

for username in `ls`; do echo $username; sudo -u $username /bin/mb2md -m -d Maildir; done

That will create a directory called Maildir in each user’s home directory. Then just configure your MTA to deliver mail there, and your IMAP server to pick it up there
In postfix, add this to /etc/postfix/main.cf

home_mailbox = mail/

And in Dovecot, change this in /etc/dovecot.conf

mail_location=maildir:~/mail/

Now you can edit /etc/sudoers and uncomment the env_keep section.