Skip to content

PGP Encryption with Ruby

Reid Morrison edited this page Feb 6, 2017 · 1 revision

PGP Overview

Private key

The key that is used to encrypt data. Must be properly secured to prevent the key from being used by unauthorized parties.

Public key

Key that is shared with others and is derived from the private key.

Passphrase

When a key is generated, it is recommended to use a passphrase to secure access to the private key. Without a passphrase anyone with access to the PGP keystore can encrypt and decrypt files with that key.

Signing files

When a file is encrypted for a specific recipient it should be signed with the senders credentials so that the recipient is certain the file came from the expected sender.

Signing encrypted files is optional, as well as verifying the signature of the sent file. It is however recommended to both sign and verify any signatures to prevent the "man in the middle" attack.

Key exposure

If the Private key is ever exposed:

  • A new Private key must be generated.
  • The old private and public keys should be discarded.
  • Any parties that have been sent the previous public key should be instructed to remove it immediately from their keystore, since that public key can no longer be trusted.
  • Send out the new public key to relevant parties.

Trust Level

GPG supports several trust levels, but only appears to allow the use of keys at the ultimate trust level. Keys at other levels, although present in the keystore, are not used for encryption, decryption, signing, or signature verification purposes.

After importing either a public or private key, the key must then be explicitly trusted as ultimate before it can be used.

Compression

:none

  • No compression, encrypt only

:zip

  • Default

:zlib

  • :zlib is better than zip

:bzip2

  • :bzip2 is smallest, but uses a lot of memory and is much slower.
Note

PGP standard only supports :zip. OpenGPG supports the others.

Key Types

PGP supports several encryption algorithms referred to as key types in PGP:

  • RSA Encryption with a key length of 2048 or 4096.

Although the key is encryption is RSA, AES is used to encrypt the contents of the file.

Ruby

The iostreams gem supports streaming data to/from various targets, one of which is PGP using Gnu Privacy Guard (GPG). GPG is called via the command line so that data can be streamed to avoid encrypting or decrypting the entire file in memory.

Installation

Mac OSX via homebrew

brew install gpg2

Redhat Linux

rpm install gpg2

Generating new keys

Senders private and public key

IOStreams::Pgp.generate_key(
  name:       'Sender',
  email:      'sender@example.org',
  passphrase: 'sender_passphrase'
)

Receivers private and public key

IOStreams::Pgp.generate_key(
  name:       'Receiver',
  email:      'receiver@example.org',
  passphrase: 'receiver_passphrase'
)

Example 1:

Sender

Create encrypted file that only receiver@example.org can decrypt, and sign it with the senders public key: sender@example.org.

data = %w(this is some data that should be encrypted using pgp)
IOStreams::Pgp::Writer.open(
  'secure.pgp',
  recipient:         'receiver@example.org',
  signer:            'sender@example.org',
  signer_passphrase: 'sender_passphrase'
) do |output|
  data.each { |word| output.puts(word) }
end
Receiver

Decrypt the file for receiver@example.org using the private key, and verify the signature using the senders public key.

IOStreams::Pgp::Reader.open('secure.pgp', passphrase: 'receiver_passphrase') do |stream|
  while !stream.eof?
    puts stream.read(10)
  end
end

Example 2:

Using a default user and passphrase to sign the output file:

IOStreams::Pgp::Writer.default_signer            = 'sender@example.org'
IOStreams::Pgp::Writer.default_signer_passphrase = 'sender_passphrase'

Default passphrase for decrypting file

IOStreams::Pgp::Reader.default_passphrase        = 'receiver_passphrase'
Sender

Create encrypted file that only receiver@example.org can decrypt, and sign it with the senders public key: sender@example.org.

data = %w(this is some data that should be encrypted using pgp)
IOStreams.writer('secure.gpg', pgp: {recipient: 'receiver@example.org'}) do |output|
  data.each { |word| output.puts(word) }
end
Receiver

Decrypt the file for receiver@example.org using the private key, and verify the signature using the senders public key.

IOStreams.reader('secure.gpg') do |stream|
  while data = stream.read(10)
    ap data
  end
end

Secure Passphrase

To generate a secure passphrase:

SecureRandom.urlsafe_base64(128)

Limitations

  • Designed for processing larger files since a process is spawned for each file processed.
  • For small in memory files or individual emails, use the 'opengpgme' library.

Compression Performance

Running tests on an Early 2015 Macbook Pro Dual Core with Ruby v2.3.1

Input file: test.log 3.6GB

  • :none: size: 3.6GB write: 52s read: 45s
  • :zip: size: 411MB write: 75s read: 31s
  • :zlib: size: 241MB write: 66s read: 23s ( 756KB Memory )
  • :bzip2: size: 129MB write: 430s read: 130s ( 5MB Memory )

Other Features

  • Generate keys
  • Export keys
  • Import keys
  • Set trust level
  • Delete Keys
  • Check for keys
  • Key fingerprint

Rocket Job

Output PGP encrypted files.

class Cron::ExportJob < RocketJob::Job
  include RocketJob::Plugins::Batch
  
  before_complete :download_file

  def download_file
    download(
      output_file_name,
      streams: [
        pgp: {
          recipient:   'receiver@example.org',
          compression: :zlib
        }
      ]
    )
  end
  
  def perform(record)
    # Implement
  end
end