#proof #argon2 #aead #work

slowlock

SlowLock是用于处理带有工作量证明函数的密钥密码的便捷包装器

2个不稳定版本

0.2.0 2021年6月15日
0.1.0 2021年5月23日

#1784 in 密码学

MIT/Apache

59KB
639

Rust Docs

Slow Lock

Slow是一个薄薄的便利层,它使得使用工作量证明函数推导密钥变得容易。

它不实现任何新的加密原语,而是提供一些机制来

  1. 帮助调整工作量证明函数,使其花费合理的时间
  2. 检测先前配置的工作量证明函数是否不再足够

目前工作量证明函数的唯一实现是基于Argon2。

基本流程

  1. password通过工作量证明函数传递以创建cipher_key
  2. cipher_keynonce用于解密/加密内容。

为了有用,工作量证明函数必须花费合理的时间/努力才能完成。这个crate提供了一个[Argon2WorkFunctionCalibrator],可以用来创建具有可变目标持续时间的函数。

机器性能逐年提高,因此我们需要偶尔重新校准工作量函数,以确保它继续提供足够的挑战。这个crate不会自动更改工作量函数。相反,它测量其持续时间,并提供一种机制来在工作量证明函数完成得太快时通知调用代码。这些由[WorkPolicy]控制。目前有两种方式,工作策略可以通知用户工作量证明函数完成得太快

  1. 如果启用了log_warning策略,将消息写入日志
  2. 如果启用了return_error策略,则返回错误

默认情况下,同时启用了log_warningreturn_error。确切的触发级别可以根据目标持续时间的百分比进行调整。

此外,如果以调试模式校准工作量证明函数,将生成警告,因为参数将过于弱。

基本用法

use slowlock::{WorkPolicyBuilder, Argon2WorkFunctionCalibrator, NewSlowAead};
use std::time::Duration;
use hex_literal::hex;
use aes_gcm::Aes256Gcm;
use aead::Aead;
use aead::generic_array::GenericArray;

let target_duration = Duration::from_millis(2500);

// Create a policy with a target duration of 2.5s that will:
//  * log a warning if the proof of work function takes less than 75% of the target duration
//  * return an error if the proof of work function takes less than 30% of target duration.
let policy = WorkPolicyBuilder::default()
                .build(target_duration);

// Try to create a lock that uses approximately 5% of total memory and takes
// about 2.5s to process
let work_fn = Argon2WorkFunctionCalibrator::default()
          .calibrate(target_duration)?;

// User supplied secret. This could be a password or a key recovered
// from an earlier stage in the decryption/encryption process.
let password = "super secret password".as_bytes();

// Salt should be generated using a cryptographically secure pseudo-random number generator
// It needs to be stored alongside the encrypted values
let salt = hex!("8a248444f2fc50308a856b35de67b312a4c4be1d180f49e101bf6330af5d47");

// Attempt to create the cipher algorithm using the work function and checking the
// policy.
// This process should take about 2.5 seconds.
let algo: Aes256Gcm = work_fn.slow_new(&password, &salt, &policy)?;


// Nonce _must_ be unique each time data is encrypted with the same cipher_key.
// If the salt if changed the cipher_key is implicitly changed so the nonce
// could be reset.
let nonce = hex!("1d180f49e101bf6330af5d47");

let plain_text = "secret content to protect".as_bytes();

let cipher_text = algo.encrypt(
     GenericArray::from_slice(&nonce),
     plain_text
)?;

let recovered_plain_text = algo.decrypt(
    GenericArray::from_slice(&nonce),
    cipher_text.as_slice()
)?;

assert_eq!(plain_text, recovered_plain_text);

直接创建密钥

调用者不必立即将导出的密钥转换为密文,而是可以直接获取密钥。如果调用者想要缓存密钥以供以后重用(以避免需要反复导出它),这可能很有用。显然,调用者必须注意不要泄露导出的密钥。

use slowlock::{WorkPolicyBuilder, Argon2WorkFunctionCalibrator};
use std::time::Duration;
use hex_literal::hex;;

let target_duration = Duration::from_millis(2500);

// Create a policy with a target duration of 2.5s that will:
//  * log a warning if the proof of work function takes less than 75% of the target duration
//  * return an error if the proof of work function takes less than 30% of target duration.
let policy = WorkPolicyBuilder::default()
                .build(target_duration);

// Try to create a lock that uses approximately 5% of total memory and takes
// about 2.5s to process
let work_fn = Argon2WorkFunctionCalibrator::default()
          .calibrate(target_duration)?;

// User supplied secret. This could be a password or a key derived
// from an earlier stage in the decryption/encryption process.
let password = "super secret password".as_bytes();

// Salt should be generated using a cryptographically secure pseudo-random number generator
// It needs to be stored alongside the encrypted values
let salt = hex!("8a248444f2fc50308a856b35de67b312a4c4be1d180f49e101bf6330af5d47");

// Attempt to derive the key.
// This process should take about 2.5 seconds.
let key = policy.make_cipher_key(32, &password, &salt, &work_fn)?;

// Can then use key to initialize cipher as many times as needed.

设计选择(供审查)

这是我在这个库中做出的设计选择的日志。我不是安全专家,这些可能是错误的 - 因此我在这里指出,以便用户预先警告。任何审查都受到欢迎。

可信的工作函数

当前,密码被传递到工作量证明函数,而工作量证明函数的输出被用作cipher_key

因此,它不仅是一个工作量证明函数,还在执行任何必要的密钥扩展。将这两个职责分开似乎更清晰。当然,解耦会引入其自身的复杂性。鉴于当前的工作量证明函数是专门为考虑密钥扩展而设计的,因此避免了这种复杂性。

如果真正实现分离有价值(欢迎反馈),则另一种方法可以是

  1. password的哈希传递给工作量证明函数
  2. 将工作量证明输出与原始password结合以生成cipher_key

这将防止工作量证明函数泄露密码或密钥。为此,我需要某种安全执行步骤2的方法。我假设对于256位加密,使用SHA-256将密码和工作量证明结合是可接受的。我目前看不到一种优雅的方法来实现不同密钥长度的组合。审查https://www.iana.org/assignments/aead-parameters/aead-parameters.xhtml表明,128位和256位是迄今为止最常见的密钥长度。

秘密卫生

该包处理两个秘密,原始的passwordcipher_key。如果其中任何一个泄露,则加密的安全性就会受到损害。以下是我知道它们可能泄露的方式

  1. 记录(容易避免)。
  2. 内存被释放而未被清除。
  3. 内存被写入持久存储。

该包仅将password作为引用&[u8]处理。如前所述,password被传递到工作量证明函数。由于当前的工作量证明函数是为密码散列开发的,我假设它已经采取了合理的预防措施,但无法保证这一点。

cipher_key是在此包内部生成和消耗的。为了给它一些保护,工作量证明函数将结果包装在SecretVec中返回,以确保在内存释放之前清除cipher_key

这两个都不解决第三个问题,即秘密被写入持久存储。

固定的Argon2 "秘密"

Argon2支持使用秘密。这可以用作'胡椒'。与盐不同,胡椒不会与哈希一起存储,可以在多个密码之间重复使用。通常它很长,可以存储在某个受信任的硬件存储中。这个秘密意味着即使攻击者拥有散列密码和盐,仍然缺少一个组件。

在这个库中,Argon2不是用于验证密码,而是作为工作量证明函数。因此,尽管盐被存储,但散列密码永远不会暴露(它直接用作cipher_key)。这意味着攻击者即使获得了散列密码和盐,也仍然缺少一个组件。

当前版本:0.2.0

这是一个爱好项目;我没有足够的带宽来正确维护它。您可以使用并分叉,但我不建议将此crate用于任何严重的工作。

许可证:MIT/Apache-2.0

依赖关系

~2MB
~40K SLoC