add slow hashes and password derivation
This commit is contained in:
parent
10af290e2f
commit
8660457f95
38
Cargo.lock
generated
38
Cargo.lock
generated
@ -175,6 +175,17 @@ version = "1.0.70"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arraydeque"
|
name = "arraydeque"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@ -595,6 +606,12 @@ version = "0.21.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.57.0"
|
version = "0.57.0"
|
||||||
@ -661,6 +678,15 @@ dependencies = [
|
|||||||
"wyz",
|
"wyz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest 0.10.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake3"
|
name = "blake3"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@ -3809,6 +3835,17 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
@ -6030,6 +6067,7 @@ dependencies = [
|
|||||||
name = "veilid-core"
|
name = "veilid-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
"async-io",
|
"async-io",
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
@ -10,9 +10,9 @@ license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)"
|
|||||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "enable-crypto-vld0" ]
|
default = ["enable-crypto-vld0"]
|
||||||
crypto-test = [ "enable-crypto-vld0", "enable-crypto-none" ]
|
crypto-test = ["enable-crypto-vld0", "enable-crypto-none"]
|
||||||
crypto-test-none = [ "enable-crypto-none" ]
|
crypto-test-none = ["enable-crypto-none"]
|
||||||
enable-crypto-vld0 = []
|
enable-crypto-vld0 = []
|
||||||
enable-crypto-none = []
|
enable-crypto-none = []
|
||||||
rt-async-std = ["async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std"]
|
rt-async-std = ["async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std"]
|
||||||
@ -70,6 +70,7 @@ rkyv = { version = "^0", default_features = false, features = ["std", "alloc", "
|
|||||||
data-encoding = { version = "^2" }
|
data-encoding = { version = "^2" }
|
||||||
weak-table = "0.3.2"
|
weak-table = "0.3.2"
|
||||||
range-set-blaze = { git = "https://github.com/crioux/range-set-blaze.git" } # "0.1.4" xxx replace with git repo
|
range-set-blaze = { git = "https://github.com/crioux/range-set-blaze.git" } # "0.1.4" xxx replace with git repo
|
||||||
|
argon2 = "0.5.0"
|
||||||
|
|
||||||
# Dependencies for native builds only
|
# Dependencies for native builds only
|
||||||
# Linux, Windows, Mac, iOS, Android
|
# Linux, Windows, Mac, iOS, Android
|
||||||
|
@ -13,6 +13,19 @@ pub trait CryptoSystem {
|
|||||||
) -> Result<SharedSecret, VeilidAPIError>;
|
) -> Result<SharedSecret, VeilidAPIError>;
|
||||||
|
|
||||||
// Generation
|
// Generation
|
||||||
|
fn random_bytes(&self, len: u32) -> Vec<u8>;
|
||||||
|
fn default_salt_length(&self) -> u32;
|
||||||
|
fn hash_password(&self, password: &[u8], salt: &[u8]) -> Result<String, VeilidAPIError>;
|
||||||
|
fn verify_password(
|
||||||
|
&self,
|
||||||
|
password: &[u8],
|
||||||
|
password_hash: String,
|
||||||
|
) -> Result<bool, VeilidAPIError>;
|
||||||
|
fn derive_shared_secret(
|
||||||
|
&self,
|
||||||
|
password: &[u8],
|
||||||
|
salt: &[u8],
|
||||||
|
) -> Result<SharedSecret, VeilidAPIError>;
|
||||||
fn random_nonce(&self) -> Nonce;
|
fn random_nonce(&self) -> Nonce;
|
||||||
fn random_shared_secret(&self) -> SharedSecret;
|
fn random_shared_secret(&self) -> SharedSecret;
|
||||||
fn compute_dh(
|
fn compute_dh(
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use argon2::password_hash::Salt;
|
||||||
|
use data_encoding::BASE64URL_NOPAD;
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
|
||||||
const AEAD_OVERHEAD: usize = PUBLIC_KEY_LENGTH;
|
const AEAD_OVERHEAD: usize = PUBLIC_KEY_LENGTH;
|
||||||
pub const CRYPTO_KIND_NONE: CryptoKind = FourCC([b'N', b'O', b'N', b'E']);
|
pub const CRYPTO_KIND_NONE: CryptoKind = FourCC([b'N', b'O', b'N', b'E']);
|
||||||
|
|
||||||
@ -80,6 +81,52 @@ impl CryptoSystem for CryptoSystemNONE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generation
|
// Generation
|
||||||
|
fn random_bytes(&self, len: u32) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::<u8>::with_capacity(len as usize);
|
||||||
|
bytes.resize(len as usize, 0u8);
|
||||||
|
random_bytes(bytes.as_mut()).unwrap();
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
fn default_salt_length(&self) -> u32 {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
fn hash_password(&self, password: &[u8], salt: &[u8]) -> Result<String, VeilidAPIError> {
|
||||||
|
if salt.len() < Salt::MIN_LENGTH || salt.len() > Salt::MAX_LENGTH {
|
||||||
|
apibail_generic!("invalid salt length");
|
||||||
|
}
|
||||||
|
Ok(format!(
|
||||||
|
"{}:{}",
|
||||||
|
BASE64URL_NOPAD.encode(salt),
|
||||||
|
BASE64URL_NOPAD.encode(password)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn verify_password(
|
||||||
|
&self,
|
||||||
|
password: &[u8],
|
||||||
|
password_hash: String,
|
||||||
|
) -> Result<bool, VeilidAPIError> {
|
||||||
|
let Some((salt, _)) = password_hash.split_once(":") else {
|
||||||
|
apibail_generic!("invalid format");
|
||||||
|
};
|
||||||
|
let Ok(salt) = BASE64URL_NOPAD.decode(salt.as_bytes()) else {
|
||||||
|
apibail_generic!("invalid salt");
|
||||||
|
};
|
||||||
|
return Ok(self.hash_password(password, &salt)? == password_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_shared_secret(
|
||||||
|
&self,
|
||||||
|
password: &[u8],
|
||||||
|
salt: &[u8],
|
||||||
|
) -> Result<SharedSecret, VeilidAPIError> {
|
||||||
|
if salt.len() < Salt::MIN_LENGTH || salt.len() > Salt::MAX_LENGTH {
|
||||||
|
apibail_generic!("invalid salt length");
|
||||||
|
}
|
||||||
|
Ok(SharedSecret::new(
|
||||||
|
*blake3::hash(self.hash_password(password, salt)?.as_bytes()).as_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn random_nonce(&self) -> Nonce {
|
fn random_nonce(&self) -> Nonce {
|
||||||
let mut nonce = [0u8; NONCE_LENGTH];
|
let mut nonce = [0u8; NONCE_LENGTH];
|
||||||
random_bytes(&mut nonce).unwrap();
|
random_bytes(&mut nonce).unwrap();
|
||||||
|
@ -162,6 +162,66 @@ pub async fn test_dh(vcrypto: CryptoSystemVersion) {
|
|||||||
trace!("cached_dh: {:?}", r5);
|
trace!("cached_dh: {:?}", r5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn test_generation(vcrypto: CryptoSystemVersion) {
|
||||||
|
let b1 = vcrypto.random_bytes(32);
|
||||||
|
let b2 = vcrypto.random_bytes(32);
|
||||||
|
assert_ne!(b1, b2);
|
||||||
|
assert_eq!(b1.len(), 32);
|
||||||
|
assert_eq!(b2.len(), 32);
|
||||||
|
let b3 = vcrypto.random_bytes(0);
|
||||||
|
let b4 = vcrypto.random_bytes(0);
|
||||||
|
assert_eq!(b3, b4);
|
||||||
|
assert_eq!(b3.len(), 0);
|
||||||
|
|
||||||
|
assert_ne!(vcrypto.default_salt_length(), 0);
|
||||||
|
|
||||||
|
let pstr1 = vcrypto.hash_password(b"abc123", b"qwerasdf").unwrap();
|
||||||
|
let pstr2 = vcrypto.hash_password(b"abc123", b"qwerasdf").unwrap();
|
||||||
|
assert_eq!(pstr1, pstr2);
|
||||||
|
let pstr3 = vcrypto.hash_password(b"abc123", b"qwerasdg").unwrap();
|
||||||
|
assert_ne!(pstr1, pstr3);
|
||||||
|
let pstr4 = vcrypto.hash_password(b"abc124", b"qwerasdf").unwrap();
|
||||||
|
assert_ne!(pstr1, pstr4);
|
||||||
|
let pstr5 = vcrypto.hash_password(b"abc124", b"qwerasdg").unwrap();
|
||||||
|
assert_ne!(pstr3, pstr5);
|
||||||
|
|
||||||
|
vcrypto
|
||||||
|
.hash_password(b"abc123", b"qwe")
|
||||||
|
.expect_err("should reject short salt");
|
||||||
|
vcrypto
|
||||||
|
.hash_password(
|
||||||
|
b"abc123",
|
||||||
|
b"qwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerz",
|
||||||
|
)
|
||||||
|
.expect_err("should reject long salt");
|
||||||
|
|
||||||
|
assert!(vcrypto.verify_password(b"abc123", pstr1.clone()).unwrap());
|
||||||
|
assert!(vcrypto.verify_password(b"abc123", pstr2.clone()).unwrap());
|
||||||
|
assert!(vcrypto.verify_password(b"abc123", pstr3.clone()).unwrap());
|
||||||
|
assert!(!vcrypto.verify_password(b"abc123", pstr4.clone()).unwrap());
|
||||||
|
assert!(!vcrypto.verify_password(b"abc123", pstr5.clone()).unwrap());
|
||||||
|
|
||||||
|
let ss1 = vcrypto.derive_shared_secret(b"abc123", b"qwerasdf");
|
||||||
|
let ss2 = vcrypto.derive_shared_secret(b"abc123", b"qwerasdf");
|
||||||
|
assert_eq!(ss1, ss2);
|
||||||
|
let ss3 = vcrypto.derive_shared_secret(b"abc123", b"qwerasdg");
|
||||||
|
assert_ne!(ss1, ss3);
|
||||||
|
let ss4 = vcrypto.derive_shared_secret(b"abc124", b"qwerasdf");
|
||||||
|
assert_ne!(ss1, ss4);
|
||||||
|
let ss5 = vcrypto.derive_shared_secret(b"abc124", b"qwerasdg");
|
||||||
|
assert_ne!(ss3, ss5);
|
||||||
|
|
||||||
|
vcrypto
|
||||||
|
.derive_shared_secret(b"abc123", b"qwe")
|
||||||
|
.expect_err("should reject short salt");
|
||||||
|
vcrypto
|
||||||
|
.derive_shared_secret(
|
||||||
|
b"abc123",
|
||||||
|
b"qwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerqwerz",
|
||||||
|
)
|
||||||
|
.expect_err("should reject long salt");
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn test_all() {
|
pub async fn test_all() {
|
||||||
let api = crypto_tests_startup().await;
|
let api = crypto_tests_startup().await;
|
||||||
let crypto = api.crypto().unwrap();
|
let crypto = api.crypto().unwrap();
|
||||||
@ -171,7 +231,8 @@ pub async fn test_all() {
|
|||||||
let vcrypto = crypto.get(v).unwrap();
|
let vcrypto = crypto.get(v).unwrap();
|
||||||
test_aead(vcrypto.clone()).await;
|
test_aead(vcrypto.clone()).await;
|
||||||
test_no_auth(vcrypto.clone()).await;
|
test_no_auth(vcrypto.clone()).await;
|
||||||
test_dh(vcrypto).await;
|
test_dh(vcrypto.clone()).await;
|
||||||
|
test_generation(vcrypto).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
crypto_tests_shutdown(api.clone()).await;
|
crypto_tests_shutdown(api.clone()).await;
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, Salt, SaltString},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
use chacha20::cipher::{KeyIvInit, StreamCipher};
|
use chacha20::cipher::{KeyIvInit, StreamCipher};
|
||||||
use chacha20::XChaCha20;
|
use chacha20::XChaCha20;
|
||||||
use chacha20poly1305 as ch;
|
use chacha20poly1305 as ch;
|
||||||
@ -71,6 +75,63 @@ impl CryptoSystem for CryptoSystemVLD0 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generation
|
// Generation
|
||||||
|
fn random_bytes(&self, len: u32) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::<u8>::with_capacity(len as usize);
|
||||||
|
bytes.resize(len as usize, 0u8);
|
||||||
|
random_bytes(bytes.as_mut()).unwrap();
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
fn default_salt_length(&self) -> u32 {
|
||||||
|
16
|
||||||
|
}
|
||||||
|
fn hash_password(&self, password: &[u8], salt: &[u8]) -> Result<String, VeilidAPIError> {
|
||||||
|
if salt.len() < Salt::MIN_LENGTH || salt.len() > Salt::MAX_LENGTH {
|
||||||
|
apibail_generic!("invalid salt length");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password to PHC string ($argon2id$v=19$...)
|
||||||
|
let salt = SaltString::encode_b64(salt).map_err(VeilidAPIError::generic)?;
|
||||||
|
|
||||||
|
// Argon2 with default params (Argon2id v19)
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
let password_hash = argon2
|
||||||
|
.hash_password(password, &salt)
|
||||||
|
.map_err(VeilidAPIError::generic)?
|
||||||
|
.to_string();
|
||||||
|
Ok(password_hash)
|
||||||
|
}
|
||||||
|
fn verify_password(
|
||||||
|
&self,
|
||||||
|
password: &[u8],
|
||||||
|
password_hash: String,
|
||||||
|
) -> Result<bool, VeilidAPIError> {
|
||||||
|
let parsed_hash = PasswordHash::new(&password_hash).map_err(VeilidAPIError::generic)?;
|
||||||
|
// Argon2 with default params (Argon2id v19)
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
Ok(argon2.verify_password(password, &parsed_hash).is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_shared_secret(
|
||||||
|
&self,
|
||||||
|
password: &[u8],
|
||||||
|
salt: &[u8],
|
||||||
|
) -> Result<SharedSecret, VeilidAPIError> {
|
||||||
|
if salt.len() < Salt::MIN_LENGTH || salt.len() > Salt::MAX_LENGTH {
|
||||||
|
apibail_generic!("invalid salt length");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Argon2 with default params (Argon2id v19)
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
let mut output_key_material = [0u8; SHARED_SECRET_LENGTH];
|
||||||
|
argon2
|
||||||
|
.hash_password_into(password, salt, &mut output_key_material)
|
||||||
|
.map_err(VeilidAPIError::generic)?;
|
||||||
|
Ok(SharedSecret::new(output_key_material))
|
||||||
|
}
|
||||||
|
|
||||||
fn random_nonce(&self) -> Nonce {
|
fn random_nonce(&self) -> Nonce {
|
||||||
let mut nonce = [0u8; NONCE_LENGTH];
|
let mut nonce = [0u8; NONCE_LENGTH];
|
||||||
random_bytes(&mut nonce).unwrap();
|
random_bytes(&mut nonce).unwrap();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user