2个不稳定版本
0.2.0 | 2021年6月15日 |
---|---|
0.1.0 | 2021年5月23日 |
#1784 in 密码学
59KB
639 行
Slow Lock
Slow是一个薄薄的便利层,它使得使用工作量证明函数推导密钥变得容易。
它不实现任何新的加密原语,而是提供一些机制来
- 帮助调整工作量证明函数,使其花费合理的时间
- 检测先前配置的工作量证明函数是否不再足够
目前工作量证明函数的唯一实现是基于Argon2。
基本流程
password
通过工作量证明函数传递以创建cipher_key
。cipher_key
和nonce
用于解密/加密内容。
为了有用,工作量证明函数必须花费合理的时间/努力才能完成。这个crate提供了一个[Argon2WorkFunctionCalibrator],可以用来创建具有可变目标持续时间的函数。
机器性能逐年提高,因此我们需要偶尔重新校准工作量函数,以确保它继续提供足够的挑战。这个crate不会自动更改工作量函数。相反,它测量其持续时间,并提供一种机制来在工作量证明函数完成得太快时通知调用代码。这些由[WorkPolicy]控制。目前有两种方式,工作策略可以通知用户工作量证明函数完成得太快
- 如果启用了
log_warning
策略,将消息写入日志 - 如果启用了
return_error
策略,则返回错误
默认情况下,同时启用了log_warning
和return_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
。
因此,它不仅是一个工作量证明函数,还在执行任何必要的密钥扩展。将这两个职责分开似乎更清晰。当然,解耦会引入其自身的复杂性。鉴于当前的工作量证明函数是专门为考虑密钥扩展而设计的,因此避免了这种复杂性。
如果真正实现分离有价值(欢迎反馈),则另一种方法可以是
- 将
password
的哈希传递给工作量证明函数 - 将工作量证明输出与原始
password
结合以生成cipher_key
。
这将防止工作量证明函数泄露密码或密钥。为此,我需要某种安全执行步骤2的方法。我假设对于256位加密,使用SHA-256将密码和工作量证明结合是可接受的。我目前看不到一种优雅的方法来实现不同密钥长度的组合。审查https://www.iana.org/assignments/aead-parameters/aead-parameters.xhtml表明,128位和256位是迄今为止最常见的密钥长度。
秘密卫生
该包处理两个秘密,原始的password
和cipher_key
。如果其中任何一个泄露,则加密的安全性就会受到损害。以下是我知道它们可能泄露的方式
- 记录(容易避免)。
- 内存被释放而未被清除。
- 内存被写入持久存储。
该包仅将password
作为引用&[u8]
处理。如前所述,password
被传递到工作量证明函数。由于当前的工作量证明函数是为密码散列开发的,我假设它已经采取了合理的预防措施,但无法保证这一点。
cipher_key
是在此包内部生成和消耗的。为了给它一些保护,工作量证明函数将结果包装在SecretVec中返回,以确保在内存释放之前清除cipher_key
。
这两个都不解决第三个问题,即秘密被写入持久存储。
固定的Argon2 "秘密"
Argon2支持使用秘密。这可以用作'胡椒'。与盐不同,胡椒不会与哈希一起存储,可以在多个密码之间重复使用。通常它很长,可以存储在某个受信任的硬件存储中。这个秘密意味着即使攻击者拥有散列密码和盐,仍然缺少一个组件。
在这个库中,Argon2不是用于验证密码,而是作为工作量证明函数。因此,尽管盐被存储,但散列密码永远不会暴露(它直接用作cipher_key
)。这意味着攻击者即使获得了散列密码和盐,也仍然缺少一个组件。
当前版本:0.2.0
这是一个爱好项目;我没有足够的带宽来正确维护它。您可以使用并分叉,但我不建议将此crate用于任何严重的工作。
许可证:MIT/Apache-2.0
依赖关系
~2MB
~40K SLoC