starekrow/lockbox

Lockbox是一个用于处理加密密钥和加密数据的简单分层系统。

dev-master 2017-12-15 16:29 UTC

This package is not auto-updated.

Last update: 2024-09-20 08:00:23 UTC


README

build status

Lockbox

Lockbox是一个用于处理加密密钥和加密数据的简单分层系统。它提供了一组易于使用的接口,鼓励“默认安全”的设计。

Lockbox有三个主要概念:密钥秘密保险库。密钥用于数据的加密或解密。秘密是可以使用多个密钥读取或写入的数据值。保险库是存储秘密集合的一种方式,同时也附带了一些内置的密钥管理工具。

有趣的事实:这里的类最初是为了支持服务器应用程序的认证令牌的负责任处理而设计的,以确保生产服务器的各种密码和API密钥不会直接以纯文本形式存储在文件系统中。这对于云服务器尤为重要,因为在云服务器中,对磁盘镜像的访问完全超出了所谓的“站点所有者”的控制。

快速入门

手动安装

从这里的src/目录中获取所有文件,并将它们放在某个地方。安排自动加载它们。如果您想使用require(),则需要此代码块

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安装

请参阅https://packagist.org.cn/packages/starekrow/lockbox。向@KJLJon致敬。

使用Lockbox

将您将使用的类引入您的命名空间(或者不引入,但您将经常输入“starekrow”)。

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

要加密一些数据

// 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 );

要解密一些加密数据

$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."

要使用指定的密钥和不同的加密算法

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

请注意,如果您的密钥不是给定加密算法所需的长度的预期长度,PHP的openssl扩展将对您的密钥数据应用一些默认的填充或裁剪。为了与非PHP加密系统兼容,请确保指定密钥的长度与您选择的加密算法相匹配。

要加密一些数据(即使是结构化数据),以便可以使用多个密钥解密

$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() );

要获取该数据

$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"

有趣的是,secret.txt包含如下内容

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

key.txt包含

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

创建一个保险库并将值放入其中

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

打开现有的保险库并从中读取值

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

它的工作原理

界面中的每一层都添加了功能

  • CryptoKey处理基本的加密和解密,并打包密钥和密文以进行输出、验证和解密。
  • Secret是带有lockbox式密钥管理的受管理加密数据值。
  • 一个Vault是基于文件的秘密存储,具有主密钥和一些额外的密钥管理工具。
  • Crypto是一个低级接口,提供原始数据的哈希和加密。

CryptoKey

所有的加密和解密都是通过CryptoKey实例完成的。这些实例将所需的部分捆绑在一起 - 加密类型和密钥数据,以及一个唯一的密钥标识符,以帮助进行高级管理 - 以成功解密之前加密的消息。它们还生成自身和加密消息的整洁、ASCII安全的字符串表示形式。

将密钥视为一个完整的加密包:您使用密钥锁定明文(生成密文)和解除密文(生成明文)。

可以使用(可选的)密码短语、(可选的)ID 字符串和(可选的)加密方式来构建一个 CryptoKey。未指定的任何参数都将使用合理的默认值填充:一个 256 位的随机密钥、一个 128 位(实际上为 123 位)的随机 ID,格式化为 GUID,以及 AES-128-CBC 加密方式。

Lock() 操作的输出包括使用的 IV、密文的自适应消息认证码(HMAC)以及密文本身。这些数据将被连接并 base-64 编码。结果可以很容易地通过 Unlock() 进行验证和解密。

密钥本身、确切使用的加密方式和密钥标识符可以通过 Export() 转换为简单的可打印字符串表示,并通过 Import() 读取。

保密

Secret 类提供了更丰富的接口来处理需要保护的值。每个保密信息由三部分组成:

  • 值本身是一个二进制字符串。从其他数据类型到其他数据类型的序列化是自动的。
  • 使用一个随机的 256 位 内部密钥 来加密值。
  • 附加一个或多个 保险箱

使用任何值创建保密信息,并可以使用一个或多个不同的密钥对其进行锁定(或解锁),使用虚拟保险箱模型。在此模型中,保密信息的值使用内部密钥进行加密。然而,内部密钥本身从未直接保存,而是由您提供的各种保险箱密钥进行加密。这种安排具有以下有趣的特性:

  • 可以使用多个独立的密码短语解密值。
  • 任何人都可以使用有效的保险箱密钥更新值。其他密钥持有者将看到更新的值。
  • 可以不解密值就移除保险箱。
  • 只有有效的密钥持有者才能添加保险箱。
  • 保险箱密钥不能用来了解其他保险箱密钥的任何信息(除了它们的公开 ID,因为它们本身就在明文里)。

像密钥一样,保密信息通过 Export() 以 ASCII 安全的可打印包装(在这种情况下,是围绕一些 base-64 字符串的 JSON 包装)呈现,并通过 Import() 重新构成。使用 AddLockbox() 可以获取您提供的密钥,并使用它加密内部密钥,将生成的虚拟保险箱添加到保密信息中。可以使用 RemoveLockbox()HasLockbox()ListLockboxes() 进行额外的管理。

Unlock() 接受与任何保险箱匹配的密钥,并从保险箱中解密内部密钥。而 Lock() 则丢弃任何保存的内部密钥和已解密值的副本。

如果保密信息已被解锁,可以使用 Read() 获取其值,并使用 Update() 更改它。

保险库

Vault 使用 Secret 类的特性,在磁盘上提供一个加密的键值存储,其抗中断性不亚于底层文件系统。

您提供用于保险库的目录名称,保密信息存储在该目录中的单独文件中。生成一个随机的 主密钥 并用于加密每个文件。该主密钥本身通过密码进行加密,这种安排允许在不更改主密钥的情况下更改密码,并允许在保险库大小不受影响的情况下逐步旋转主密钥。

使用保险库时,所有密钥和保密信息管理都由您处理。使用 CreateVault() 设置新保险库(如果选择目录中已存在保险库,则操作将失败)或 Open() 打开现有保险库。《VaultExists() 和 DestroyVault() 完成了保险库管理套件。使用 Close() 忘记所有密钥和缓存的保密信息,而不影响磁盘上的保险库。

使用 Put()Get()Has()Remove()PerSecret() 管理保险库内的值。

可以使用 ChangePassphrase() 更改用于加密主密钥的密码。这是一个相对快速且安全的操作,仅影响一个文件。使用 RotateMasterKey() 旋转主密钥本身。

加密

加密库是一个低级别的加密接口。它被设计为一个简单的兼容层,用于隐藏不同可用的加密扩展(以及PHP版本)与CryptoKey之间的差异。

只有在你非常清楚自己在做什么的情况下,才应该直接使用加密库

暴露风险

由于PHP的特性,没有方法(除了扩展)可以真正清除RAM中的内容。实际上,由于许多操作系统配置允许其他信息泄露(交换文件),这样做可能实际上并不有用。进程内的损坏(例如Heartbleed)也可能破坏这些措施。以下步骤被采取以尽量减少暴露风险:

  • 可以在不提供密钥的情况下加载秘密。这样,RAM中就只剩下加密文本和加密锁箱。因此,你可以预先加载秘密,仅在需要时解密它们。这也允许在不提供密钥的情况下从秘密中移除锁箱。

  • 解密秘密不会自动解密值。然而,内部密钥在解锁后确实以明文形式存储在RAM中。这为了一种情况,即你想要向秘密添加锁箱,但不想在RAM中无谓地暴露未加密的值。

  • 保险库仅在需要时从磁盘加载秘密值。

  • 用于解密保险库主密钥的密码不会被保险库实例保留。尽管如此,主密钥本身确实以明文形式存储。

  • SecretVault都提供了一个Close()方法,该方法立即断开存储的密钥和明文值。CryptoKey提供了一个Shred()方法,用于断开密钥数据和id。