Coder Social home page Coder Social logo

lockbox's Introduction

build status

Lockbox

Lockbox is a simple, tiered system for working with cryptographic keys and encrypted data. It provides a set of easy-to-use interfaces that encourage a "secure by default" design.

There are three primary concepts to Lockbox: Keys, secrets and vaults. Keys are used for encryption or decryption of data. Secrets are data values that can be read or written using multiple keys. A vault is one way to store a collection of secrets, that also comes with some built-in key management tools.

Interesting fact: The classes here were originally designed to support responsible handling of authentication tokens for server applications, to ensure that the various passwords and API keys for production servers weren't just dumped in plain text into the file system. This is particularly important for cloud servers, where access to the disk images is entirely out of the so-called "site owner's" control.

Quick-Start

Manual Install

Grab all the files from the src/ directory here and stick them somewhere. Arrange to autoload them. If you instead want to require() them, this is the block you'll need:

require_once "CryptoCore.php";
require_once "CryptoCoreLoader.php";
require_once "CryptoCoreFailed.php";
require_once "CryptoCoreBuiltin.php";
require_once "CryptoCoreOpenssl.php";
require_once "Crypto.php";
require_once "CryptoKey.php";
require_once "Secret.php";
require_once "Vault.php";

Composer Install

See https://packagist.org/packages/starekrow/lockbox. Greetz to @KJLJon.

Using Lockbox

Suck the classes you'll be using into your namespace (or don't, but you'll be typing "starekrow" a lot).

use starekrow\Lockbox\CryptoKey;
use starekrow\Lockbox\Secret;
use starekrow\Lockbox\Vault;

To encrypt some data:

// CryptoKey defaults to AES-128-CBC encryption with a random key
$key = new CryptoKey();
$message = "You can't see me.";
$ciphertext = $key->Lock( $message );

file_put_contents( "key.txt", $key->Export() );
file_put_contents( "cipher.txt", $ciphertext );

To decrypt some encrypted data:

$key = CryptoKey::Import( file_get_contents( "key.txt" ) );
$ciphertext = file_get_contents( "cipher.txt" );
$message = $key->Unlock( $ciphertext );
echo $message; 			// "You can't see me."

To use a specified key and a different cipher:

$key = new CryptoKey( "ILikeCheese", null, "AES-256-ECB" );
$no_see_um = $key->Lock( "This text is safe." );
$see_um = $key->Unlock( $no_see_um );

Note that if your key is not the expected length for the given cipher, PHP's openssl extension will apply some default padding or cropping to your key data. For interoperability with non-PHP crypto systems, be sure to specify the key at the proper length for your chosen cipher.

To encrypt some data (even structured data) so that it can be decrypted with more than one key:

$s = new Secret( [ "my stuff" => "Sooper seekrit" ] );
$k = new CryptoKey( "correcthorsebatterystaple" );
$k2 = new CryptoKey( "ILikeCheese" );
$s->AddLockbox( $k );
$s->AddLockbox( $k2 );
file_put_contents( "secret.txt", $s->Export() );
file_put_contents( "key.txt", $k->Export() );
file_put_contents( "key2.txt", $k2->Export() );

To get that data back:

$s = Secret::Import( file_get_contents( "secret.txt" ) );
$k = CryptoKey::Import( file_get_contents( "key.txt" ) );
$s->Unlock( $k );
$val = $s->Read();
echo $val["my stuff"]; 				// "Sooper seekrit"

Interestingly, secret.txt contains something like:

{
    "locks": {
	"17e9c178-7a99-47ac-a422-5ec9a9e0a6e8": "2W2ElRE4S7xu93xxcvIF7dubb+46YhgZKDS3Lnztc7YDL+Had4nNIRqZ03jzW8w1IaZtMAudFTQFLejVYMwDeHnpHotBR5UBo0TZq4jgW2hetGbahLOpni3hhwbU9at8By34Dj53UfK84pXyOe2RH90+b/vL9OLAD51hupsbI2TlKPjCsys8V3EhaIz0a57yCKhAyMarZkyklRKvFYvbKw==",
	"0188b485-0937-4695-a0d6-5f968b286fc9": "Ugq4MuwOfvyKlREhVJDFLuRR8U7O6y0e3KYD2Gllk4QC0EaC2MJDtJ9yCkePF49zsukgmjSpHvhAjg1ZN3yWEOR8DE3kDY8rai9RC1LRRC0iK2nTg7DqCsvUV57nY1mG5MVpW8LXAirjRtCasj2yJu1D1JY0U06hXpSDoVzaLSFqPoRoSAI231SwISgnqhLCUEt7L7LGwIt3voMehH6wxg=="
    },
    "data": "+2uEgQ52VGOVvGu41umPhjurmqhoXHMqhbzoFeQuWs63rFQNVW9HK3dlEddEyZfoe+lXT2M5MElUfdXF1vWZ8mLiorVkN8N+Waz6YeyZ3CePpYPNsZT9yMCWAQNwnTjU"
}

And key.txt contains:

k0|17e9c178-7a99-47ac-a422-5ec9a9e0a6e8|QUVTLTEyOC1DQkM=|Y29ycmVjdGhvcnNlYmF0dGVyeXN0YXBsZQ==

Create a vault and put a value into it:

$v = new Vault( "./secrets" );
$v->CreateVault( "CorrectHorseBatteryStaple" );
$v->Put( "test1", "This is a test." );
$v->Close();

Open an existing vault and read a value from it:

$v = new Vault( "./secrets" );
$v->Open( "CorrectHorseBatteryStaple" );
$got = $v->Get( "test1" );
echo $got;						// prints "This is a test."

How it Works

Each tier of the interface adds capabilities:

  • CryptoKey handles basic encryption and decryption, and packages both the keys and the ciphertext for output, verification and decryption.
  • Secret is a managed, encrypted data value with lockbox-style key handling.
  • A Vault is file-based storage for secrets, with a master key and some additional key management tools.
  • Crypto is a low-level interface that provides hashing and encryption of raw data.

CryptoKey

All of the actual encryption and decryption is done with CryptoKey instances. These bundle together the pieces - cipher type and key data, along with a unique key identifier to help with higher-level management - needed to successfully decrypt a previously encrypted message. They also produce representations of themselves and of the encrypted messages in tidy, ASCII-safe strings.

Think of a key as a complete encryption package: You use the key to lock plaintext (producing ciphertext) and to unlock ciphertext (producing plaintext).

A CryptoKey can be built with an (optional) passphrase, an (optional) id string and an (optional) cipher specifier. Any parameter that isn't specified is filled with a reasonable default: A 256-bit random key, a 128-bit (well, 123-bit) random ID formatted as a GUID, and an AES-128-CBC cipher.

The output of a Lock() operation includes the IV used and an HMAC of the ciphertext, as well as the ciphertext itself. Those are then concatenated and base-64 encoded. The result can be easily verified and decrypted with Unlock().

The keys themselves, along with the exact cipher used and the key identifier, can be converted to a simple printable string representation with Export() and read back in with Import().

Secret

The Secret class provides a more extensive interface to handling a value that needs to be protected. Each secret consists of three parts:

  • The value itself is a binary string. Serialization from and to other data types is automatic.
  • A random 256-bit internal key is used to encrypt the value.
  • One or more lockboxes is attached.

The secret is created with any value, and can be locked (or unlocked) with one or more different keys, using a virtual lockbox model. In this model, the secret's value is encrypted with the internal key. However, the internal key is never saved directly, but is itself encrypted by the various lockbox keys you supply. This arrangement has the following interesting properties:

  • The value can be decrypted with multiple, independent passphrases.
  • The value can be updated by anyone with a valid lockbox key. Other key-holders will then see the updated value.
  • A lockbox can be removed without decrypting the value.
  • A lockbox can only be added by a valid key-holder.
  • A lockbox key cannot be used to learn anything about the other lockbox keys (except their public ID, which is in the clear anyway).

Like keys, secrets are rendered in an ASCII-safe printable package (in this case, a JSON wrapper around some base-64 strings) with Export() and can be reconstituted with Import(). AddLockbox() takes a key you supply and encrypts the internal key with it, adding the resulting virtual lockbox to the secret. Additional management can be done with RemoveLockbox(), HasLockbox() and ListLockboxes().

Unlock() takes a key matching any lockbox and decrypts the internal key from the lockbox. Lock() just discards any saved copies of the internal key and decrypted value.

If the secret has been unlocked, you can use Read() to get the value out of it, and Update() to change it.

Vault

A Vault uses the properties of the Secret class to provide an encrypted key-value store on disk that is as robust to interruption as the underlying file system.

You supply the name of a directory for the vault, and the secrets are stored in individual files in that directory. A random master key is generated and used to encrypt each one. That master key is itself encrypted with a passphrase, an arrangement that allows the passphrase to be changed without changing the master key, and allows the master key itself to be rotated in stages, no matter the size of the vault.

When using a vault, all the key and secret management is handled for you. Use CreateVault() to set up a new vault (this will fail if a vault already exists in the chosen directory) or Open() to open an existing one. VaultExists() and DestroyVault() round out the vault management suite. Close() forgets all keys and cached secrets without affecting the vault on disk.

Manage values within the vault with Put(), Get(), Has(), Remove() and PerSecret().

You can change the passphrase used to encrypt the master key with ChangePassphrase(). This is a comparatively fast and safe operation, affecting only one file. Rotate the master key itself with RotateMasterKey().

Crypto

Crypto is a low-level encryption interface. It is designed as a straightforward compatibility layer, to hide some of the differences between the various available cryptography extensions (and PHP versions) from CryptoKey.

You should only use Crypto directly if you have a pretty good idea of what you're doing.

Exposure Risks

This being PHP, there's no way (barring extensions) to actually scrub the contents of RAM. It might in fact not even be useful to do so anyway, due to other information leaks (swap files) allowed by many OS configurations. In-process damage (e.g. Heartbleed) might also defeat such measures. The following steps are taken to try to minimize the exposure risk:

  • Secrets can be loaded without supplying a key at all. This leaves just the encrypted text and the encrypted lockboxes in RAM. So you can prospectively load secrets and only decrypt them if needed. This also allows removing a lockbox from a secret without ever supplying the key.

  • Unlocking a secret does not automatically decrypt the value. The internal key is, however, stored - in the clear - in RAM after unlocking. This provides for the case where you want to add a lockbox to a secret but do not want to needlessly expose the unencrypted value in RAM.

  • Vaults only load secret values from disk as they are requested.

  • The passphrase used to decrypt a Vault master key is not retained by the Vault instance. Though, again, the master key itself is stored in the clear.

  • Both Secret and Vault provide a Close() method that immediately detaches stored keys and plaintext values. CryptoKey provides a Shred() method that detaches the key data and id.

lockbox's People

Contributors

alexmanno avatar kjljon avatar starekrow avatar ukd1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lockbox's Issues

separate key for hmac and encryption

I am not a security expert, but I typically read that the HMAC and Encryption keys should be different.

A unique key can be derived from a hkdf function. If your targeting >= php 7 then you can use hash_hkdf()

What versions of PHP are you looking to support?

php version bump

I see you bumped the version to php 7 because of phpunit. I quick tested and it looks like I can get phpunit 4.x running with 2 minor changes to the tests (which supports php 5.5).

Although, I still like the idea of supporting 7+ (or 5.6+) because of PHP security patching support, but I also think the version shouldn't be bumped just because of the testing framework.

PHP support: http://php.net/supported-versions.php

see KJLJon@d8be026 for 5.5+ fix.

@starekrow what are your thoughts? I can send a PR for the commit listed above if you still want to support php 5.5+

Secure distribution of secrets (KeyDrop)

Getting distribution right is apparently hard. I've started a branch for a new module called KeyDrop that will handle that. It will supply all of the guts for a client/server model for secret distribution, with an offline master keyring.

  • You designate a server to store the master keys and vaults and write a trivial wrapper to get HTTP queries into and out of the KeyDrop class for it. This is the KeyDrop server.
  • The KeyDrop server has all the master keys and all the vaults for all the clients, however the master keys are encrypted, and the KeyDrop server is never given the information needed to decrypt them. Compromising the KeyDrop server gains an attacker nothing at all.
  • Each KeyDrop client is assigned an ID and a client key. The client key is stored locally on the client, and its only purpose is to decrypt the master key. The master key is never stored on the client.
  • When the client is running, if it doesn't have a copy of the master key in RAM, it asks the KeyDrop server for the master key. The KeyDrop server returns an encrypted master key, which the client must decode. For efficiency, the master key should then be tucked away somewhere (NOT on the filesystem).
  • The client can also ask the KeyDrop server for a list of updates to that client's vault.
  • A mechanism is provided to push updates to client vaults out to the KeyDrop server. You should provide your own mechanism to force your client to request an update.
  • Another mechanism is provided to organize all the client, vault and shared keys in an encrypted master keyring. This obviously should be kept offline.

I think this actually solves the entire question of how to securely configure a server; each KeyDrop client only needs a couple of items - the client ID and client key - to automatically and securely pull, store and update when needed all the other secrets assigned to that client.

Fatal error: Uncaught Exception: Unknown algorithm

Hi

I'm trying to use lockbox but it fails on my computer (localhost, Windows 10).
I'm using the code below (a copy/paste from your readme.md) and I receive a fatal error when calling the Lock() method.

I'm under PHP 7.2.0 (it's working fine under PHP 5.6.25 or 7.0.10). I've just install PHP 7.2.1, same result : NOK.

Can you give advices please ? Any installation problem on my computer ?

Thanks.

define ('DS', DIRECTORY_SEPARATOR);

use starekrow\Lockbox\CryptoKey;
use starekrow\Lockbox\Secret;
use starekrow\Lockbox\Vault;

$lib = 'some_dir';

// Include Lockbox
require_once $lib."CryptoCore.php";
require_once $lib."CryptoCoreLoader.php";
require_once $lib."CryptoCoreFailed.php";
require_once $lib."CryptoCoreBuiltin.php";
require_once $lib."CryptoCoreOpenssl.php";
require_once $lib."Crypto.php";
require_once $lib."CryptoKey.php";
require_once $lib."Secret.php";
require_once $lib."Vault.php";

// CryptoKey defaults to AES-128-CBC encryption with a random key
$key = new CryptoKey();
$message = "You can't see me.";
echo $key->Lock( $message ).'<hr/>';

$key = new CryptoKey( "ILikeCheese", null, "AES-256-ECB" );
$no_see_um = $key->Lock( "This text is safe." );
echo $no_see_um.'<hr/>';
$see_um = $key->Unlock( $no_see_um );
echo $see_um.'<hr/>';

Full error message :

Fatal error: Uncaught Exception: Unknown algorithm in 
libs\lockbox\CryptoCoreBuiltin.php:86 Stack trace: 
#0 libs\lockbox\Crypto.php(84): starekrow\Lockbox\CryptoCoreBuiltin->ivlen('AES-128-CBC') 
#1 libs\lockbox\CryptoKey.php(129): starekrow\Lockbox\Crypto::ivlen('AES-128-CBC') 
#2 test.php(28): starekrow\Lockbox\CryptoKey->lock('You can't see m...') 
#3 {main} thrown in libs\lockbox\CryptoCoreBuiltin.php on line 86

abstract filesystem calls

I think the file system calls in Vault should be abstracted into an interface so the vault could be stored in other locations and someone could leverage packages like flysystem.

Of course default it with the file system for ease of use.

namespace starekrow\lockbox;

interface FileSystemInterface {
    /**
     * gets the content of a file
     * @param string $file
     * @return string
     */
    public function get($file);

    /**
     * puts content into a file
     * @param string $file
     * @param string $content
     * @return bool if it was successful
     */
    public function put($file, $content);

    /**
     * checks if the file exists
     * @param string $file
     * @return bool if it exists
     */
    public function has($file);
}
namespace starekrow\lockbox;

class LocalFileSystem implements FileSystemInterface {
    /**
     * @inheritdoc
     */
    public function get($file)
    {
        return @file_get_contents($file);
    }

    /**
     * @inheritdoc
     */
    public function put($file, $content)
    {
        return @file_put_contents($file, $content);
    }

    /**
     * @inheritdoc
     */
    public function has($file)
    {
        return file_exists($file);
    }
}

Add test coverage for all crypto drivers

The CryptoKey tests offer an amount of indirect coverage, but there should be some explicit exercise of Failed, Builtin and Openssl (and Sodium when it arrives)

libsodium support

It would be nice to have optional support for libsodium as an alternative to the openssl extension, since libsodium is moving into core.

This could be hacked in place, or done by isolating the crypto use in CryptoKey. Either way might complicate cipher selection (needs research).

tag a version

I think a version should be tagged so it will be easier to install via composer. I recommend following symver

I am open to either one of these versions (depending on what your thoughts of the stability of the package is):

  • 0.1.0 - if you think there still might be major changed / API changes
  • 1.0.0 - if you are are pretty set on the packages public API.

polyfill for random_bytes

This is a potential solution to support random bytes. (it is also released under MIT license, and can be required with composer)

see https://github.com/paragonie/random_compat

In regards to

public function random( $count )
{
if (function_exists( "random_bytes" )) {
return random_bytes( $count );
}
// TODO: windows: COM stuff, linux: /dev/urandom
if (function_exists( "openssl_random_pseudo_bytes" )) {
return openssl_random_pseudo_bytes( $count );
}
throw new Exception( "No good source of randomness found" );
}

Raw encryption

For improved interoperability, CryptoKey should have some facility for returning and accepting raw binary ciphertext and IVs.

For example, update the signature of Lock() to accept a second argument $raw. If true, return an array of [ "iv" => "...", "data" => "..." ]. Likewise, Unlock() could accept such an array.

make hmac algo configurable

I think the HMAC hashing algorithm should be configurable and included in the export function.
For example, if I wanted to use sha512 instead of sha256

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.