Add Crypt functions

This commit is contained in:
Darley Barreto
2024-03-01 00:23:42 +00:00
committed by Jeremy Soller
parent a7b2294b62
commit 55dc2019f2
30 changed files with 1180 additions and 11 deletions
Generated
+236 -5
View File
@@ -8,12 +8,54 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bcrypt-pbkdf"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2"
dependencies = [
"blowfish",
"pbkdf2",
"sha2",
]
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "blowfish"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cbitset"
version = "0.2.0"
@@ -32,6 +74,31 @@ dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crt0"
version = "0.1.0"
@@ -44,10 +111,41 @@ version = "0.1.0"
name = "crtn"
version = "0.1.0"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "dlmalloc"
version = "0.2.4"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "goblin"
version = "0.7.1"
@@ -59,6 +157,24 @@ dependencies = [
"scroll",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "ld_so"
version = "0.1.0"
@@ -75,6 +191,16 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.7.1"
@@ -99,6 +225,28 @@ dependencies = [
"autocfg",
]
[[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]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
"sha2",
]
[[package]]
name = "plain"
version = "0.2.3"
@@ -140,8 +288,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"rand_chacha",
"rand_core",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -151,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.5.1",
]
[[package]]
@@ -160,13 +309,19 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
@@ -176,17 +331,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a404fd88e0f817fc1c3351b9ba2207ffa65038cdde464405308a5f5d254835fe"
dependencies = [
"libc",
"rand_core",
"rand_core 0.5.1",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
@@ -216,14 +380,18 @@ dependencies = [
name = "relibc"
version = "0.2.5"
dependencies = [
"base64ct",
"bcrypt-pbkdf",
"bitflags",
"cbitset",
"cc",
"dlmalloc",
"goblin",
"libc",
"md-5",
"memchr",
"memoffset",
"pbkdf2",
"plain",
"posix-regex",
"rand",
@@ -233,9 +401,21 @@ dependencies = [
"redox-path",
"redox_syscall",
"sc",
"scrypt",
"sha-crypt",
"sha2",
"unicode-width",
]
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "sc"
version = "0.2.7"
@@ -262,6 +442,45 @@ dependencies = [
"syn",
]
[[package]]
name = "scrypt"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
"password-hash",
"pbkdf2",
"salsa20",
"sha2",
]
[[package]]
name = "sha-crypt"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88e79009728d8311d42d754f2f319a975f9e38f156fd5e422d2451486c78b286"
dependencies = [
"base64ct",
"sha2",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.48"
@@ -273,6 +492,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
@@ -285,6 +510,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "winapi"
version = "0.3.9"
+8 -1
View File
@@ -28,7 +28,7 @@ memoffset = "0.9"
posix-regex = { path = "posix-regex", features = ["no_std"] }
# TODO: For some reason, rand_jitter hasn't been updated to use the latest rand_core
rand = { version = "0.7", default-features = false }
rand = { version = "0.7", default-features = false, features = ["small_rng"] }
rand_xorshift = "0.2"
rand_jitter = "0.3"
@@ -36,6 +36,13 @@ memchr = { version = "2.2.0", default-features = false }
plain = "0.2"
unicode-width = "0.1"
__libc_only_for_layout_checks = { package = "libc", version = "0.2.149", optional = true }
md5-crypto = { package = "md-5", version = "0.10.6", default-features = false}
sha-crypt = { version = "0.5", default-features = false }
base64ct = { version = "1.6", default-features = false, features = ["alloc"] }
bcrypt-pbkdf = { version = "0.10", default-features = false, features = ["alloc"] }
scrypt = { version = "0.11", default-features = false, features = ["simple"]}
pbkdf2 = { version = "0.12", features = ["sha2"]}
sha2 = { version = "0.10", default-features = false }
[dependencies.goblin]
version = "0.7"
+90
View File
@@ -0,0 +1,90 @@
use crate::platform::types::{c_uchar, c_uint};
use alloc::{string::String, vec::Vec};
use base64ct::{Base64Bcrypt, Encoding};
use bcrypt_pbkdf::bcrypt_pbkdf;
use core::str;
const MIN_COST: u32 = 4;
const MAX_COST: u32 = 31;
const BHASH_WORDS: usize = 8;
const BHASH_OUTPUT_SIZE: usize = BHASH_WORDS * 4;
/// Inspired by https://github.com/Keats/rust-bcrypt/blob/87fc59e917bcb6cf3f3752fc7f2b4c659d415597/src/lib.rs#L135
fn split_with_prefix(hash: &str) -> Option<(&str, &str, c_uint)> {
let valid_prefixes = ["2y", "2b", "2a", "2x"];
// Should be [prefix, cost, hash]
let raw_parts: Vec<_> = hash.split('$').skip(1).collect();
if raw_parts.len() != 3 {
return None;
}
let prefix = raw_parts[0];
let setting = raw_parts[2];
if !valid_prefixes.contains(&prefix) {
return None;
}
raw_parts[1]
.parse::<c_uint>()
.ok()
.map(|cost| (prefix, setting, cost))
}
/// Performs Blowfish key derivation on a given password with a specific setting.
///
/// # Parameters
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
/// * `setting`: The settings for the Blowfish key derivation. It must be a string slice (`&str`)
/// and should follow the format `$<prefix>$<cost>$<setting>`, where `<prefix>` is a string that
/// indicates the type of the hash (e.g., "$2a$"), `<cost>` is a decimal number representing
/// the cost factor for the Blowfish operation, and `<setting>` is a base64-encoded string
/// representing the salt to be used for the Blowfish function.
///
/// # Returns
/// * `Option<String>`: Returns `Some(String)` if the Blowfish operation was successful, where the
/// returned string is the result of the Blowfish operation formatted according to the Modular
/// Crypt Format (MCF). If the Blowfish operation failed, it returns `None`.
///
/// # Errors
/// * If the cost factor is outside the range `[MIN_COST, MAX_COST]`.
///
/// # Example
/// ```
/// let password = "correctbatteryhorsestapler";
/// let setting = "$2y$12$L6Bc/AlTQHyd9liGgGEZyO";
/// let result = crypt_blowfish(password, setting);
/// assert!(result.is_some());
///```
///
/// # Note
/// The `crypt_blowfish` function uses the Blowfish block cipher for hashing.
/// The output of the Blowfish operation is base64-encoded using the BCrypt variant of base64.
pub fn crypt_blowfish(passw: &str, setting: &str) -> Option<String> {
if let Some((prefix, setting, cost)) = split_with_prefix(setting) {
if !(MIN_COST..=MAX_COST).contains(&cost) {
return None;
}
// Passwords need to be null terminated
let mut vec = Vec::with_capacity(passw.len() + 1);
vec.extend_from_slice(passw.as_bytes());
vec.push(0);
// We only consider the first 72 chars; truncate if necessary.
let passw_t = if vec.len() > 72 { &vec[..72] } else { &vec };
let passw: &[c_uchar] = &passw_t;
let setting = setting.as_bytes();
let mut output = vec![0; BHASH_OUTPUT_SIZE + 1];
bcrypt_pbkdf(passw, setting, cost, &mut output).ok()?;
Some(format!(
"${}${}${}",
prefix,
cost,
Base64Bcrypt::encode_string(&output),
))
} else {
None
}
}
+9
View File
@@ -0,0 +1,9 @@
include_guard = "_RELIBC_CRYPT_H"
language = "C"
style = "Type"
no_includes = true
cpp_compat = true
[enum]
prefix_with_name = true
+146
View File
@@ -0,0 +1,146 @@
use crate::platform::types::c_uchar;
use alloc::string::String;
use base64ct::{Base64ShaCrypt, Encoding};
use core::str;
use md5_crypto::{Digest, Md5};
// Block size for MD5
const BLOCK_SIZE: usize = 16;
// PWD part length of the password string
const PW_SIZE_MD5: usize = 22;
// Maximum length of a setting
const SALT_MAX: usize = 8;
// Inverse encoding map for MD5.
const MAP_MD5: [c_uchar; BLOCK_SIZE] = [12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11];
const KEY_MAX: usize = 30000;
fn encode_md5(source: &[c_uchar]) -> Option<[c_uchar; PW_SIZE_MD5]> {
let mut transposed = [0; BLOCK_SIZE];
for (i, &ti) in MAP_MD5.iter().enumerate() {
transposed[i] = source[ti as usize];
}
let mut buf = [0; PW_SIZE_MD5];
Base64ShaCrypt::encode(&transposed, &mut buf).ok()?;
Some(buf)
}
/// Function taken from PR: https://github.com/RustCrypto/password-hashes/pull/351
/// This won't be needed once the PR is merged
fn inner_md5(passw: &str, setting: &str) -> Option<String> {
let mut digest_b = Md5::default();
digest_b.update(passw);
digest_b.update(setting);
digest_b.update(passw);
let hash_b = digest_b.finalize();
let mut digest_a = Md5::default();
digest_a.update(passw);
digest_a.update("$1$");
digest_a.update(setting);
let mut pw_len = passw.len();
let rounds = pw_len / BLOCK_SIZE;
for _ in 0..rounds {
digest_a.update(hash_b);
}
// leftover passw
digest_a.update(&hash_b[..(pw_len - rounds * BLOCK_SIZE)]);
while pw_len > 0 {
match pw_len & 1 {
0 => digest_a.update(&passw[..1]),
1 => digest_a.update([0u8]),
_ => unreachable!(),
}
pw_len >>= 1;
}
let mut hash_a = digest_a.finalize();
// Repeatedly run the collected hash value through MD5 to burn
// CPU cycles
for i in 0..1000_usize {
// new hasher
let mut hasher = Md5::default();
// Add key or last result
if (i & 1) != 0 {
hasher.update(passw);
} else {
hasher.update(hash_a);
}
// Add setting for numbers not divisible by 3
if i % 3 != 0 {
hasher.update(setting);
}
// Add key for numbers not divisible by 7
if i % 7 != 0 {
hasher.update(passw);
}
// Add key or last result
if (i & 1) != 0 {
hasher.update(hash_a);
} else {
hasher.update(passw);
}
// digest_c.clone_from_slice(&hasher.finalize());
hash_a = hasher.finalize();
}
encode_md5(hash_a.as_slice())
.map(|encstr| format!("$1${}${}", setting, str::from_utf8(&encstr).unwrap()))
}
/// Performs MD5 hashing on a given password with a specific setting.
///
/// # Parameters
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
/// * `setting`: The settings for the MD5 hashing. It must be a string slice (`&str`)
/// and should start with "$1$". The rest of the string should represent the salt
/// to be used for the MD5 hashing.
///
/// # Returns
/// * `Option<String>`: Returns `Some(String)` if the MD5 operation was successful, where the
/// returned string is the result of the MD5 operation formatted according to the Modular
/// Crypt Format (MCF). If the MD5 operation failed, it returns `None`.
///
/// # Errors
/// * If the `passw` length exceeds `KEY_MAX`.
/// * If the `setting` does not start with "$1$".
///
/// # Example
/// ```
/// let password = "my_password";
/// let setting = "$1$saltstring";
/// let result = crypt_md5(password, setting);
/// assert!(result.is_some());
/// ```
///
/// # Note
/// The `crypt_md5` function uses the MD5 hashing algorithm for hashing.
/// The output of the MD5 operation is base64-encoded using the BCrypt variant of base64.
pub fn crypt_md5(passw: &str, setting: &str) -> Option<String> {
/* reject large keys */
if passw.len() > KEY_MAX {
return None;
}
if &setting[0..3] != "$1$" {
return None;
}
let cursor = 3;
let slen = cursor
+ setting[cursor..cursor + SALT_MAX]
.chars()
.take_while(|c| *c != '$')
.count();
let setting = &setting[cursor..slen];
inner_md5(passw, setting)
}
+101
View File
@@ -0,0 +1,101 @@
use ::scrypt::password_hash::{Salt, SaltString};
use alloc::{
ffi::CString,
string::{String, ToString},
};
use core::ptr;
use rand::{rngs::SmallRng, RngCore, SeedableRng};
use crate::{
c_str::CStr,
header::{errno::EINVAL, stdlib::rand},
platform::{self, types::*},
};
mod blowfish;
mod md5;
mod pbkdf2;
mod scrypt;
mod sha;
use self::{
blowfish::crypt_blowfish,
md5::crypt_md5,
pbkdf2::crypt_pbkdf2,
scrypt::crypt_scrypt,
sha::{crypt_sha, ShaType::*},
};
#[repr(C)]
pub struct crypt_data {
initialized: c_int,
buff: [c_char; 256],
}
impl crypt_data {
pub fn new() -> Self {
crypt_data {
initialized: 1,
buff: [0; 256],
}
}
}
fn gen_salt() -> Option<String> {
let mut rng = SmallRng::seed_from_u64(unsafe { rand() as u64 });
let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
rng.fill_bytes(&mut bytes);
Some(SaltString::encode_b64(&bytes).ok()?.as_str().to_string())
}
#[no_mangle]
pub unsafe extern "C" fn crypt_r(
key: *const c_char,
setting: *const c_char,
data: *mut crypt_data,
) -> *mut c_char {
if (*data).initialized == 0 {
*data = crypt_data::new();
}
let key = CStr::from_ptr(key).to_str().expect("key must be utf-8");
let setting = CStr::from_ptr(setting)
.to_str()
.expect("setting must be utf-8");
let setting_bytes = setting.as_bytes();
let encoded = if setting_bytes[0] == b'$' && setting_bytes[1] != 0 && setting_bytes[2] != 0 {
if setting_bytes[1] == b'1' && setting_bytes[2] == b'$' {
crypt_md5(key, setting)
} else if setting_bytes[1] == b'2' && setting_bytes[3] == b'$' {
crypt_blowfish(key, setting)
} else if setting_bytes[1] == b'5' && setting_bytes[2] == b'$' {
crypt_sha(key, setting, Sha256)
} else if setting_bytes[1] == b'6' && setting_bytes[2] == b'$' {
crypt_sha(key, setting, Sha512)
} else if setting_bytes[1] == b'7' && setting_bytes[2] == b'$' {
crypt_scrypt(key, setting)
} else if setting_bytes[1] == b'8' && setting_bytes[2] == b'$' {
crypt_pbkdf2(key, setting)
} else {
platform::errno = EINVAL;
return ptr::null_mut();
}
} else {
None
};
if let Some(inner) = encoded {
let len = inner.len();
if let Ok(ret) = CString::new(inner) {
let ret_ptr = ret.into_raw();
let dst = (*data).buff.as_mut_ptr();
ptr::copy_nonoverlapping(ret_ptr, dst.cast(), len);
ret_ptr.cast()
} else {
ptr::null_mut()
}
} else {
ptr::null_mut()
}
}
+64
View File
@@ -0,0 +1,64 @@
use super::gen_salt;
use alloc::string::{String, ToString};
use base64ct::{Base64Bcrypt, Encoding};
use core::{str, u32};
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
/// Performs PBKDF2 key derivation on a given password with a specific setting.
///
/// # Parameters
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
/// * `setting`: The settings for the PBKDF2 key derivation. It must be a string slice (`&str`)
/// and should follow the format `$<iter>$<salt>`. The `<iter>` part should be a hexadecimal
/// number representing the iteration count for the PBKDF2 function. The `<salt>` part should
/// be a base64-encoded string representing the salt to be used for the PBKDF2 function.
///
/// # Returns
/// * `Option<String>`: Returns `Some(String)` if the PBKDF2 operation was successful, where the
/// returned string is the result of the PBKDF2 operation formatted according to the Modular
/// Crypt Format (MCF). If the PBKDF2 operation failed, it returns `None`.
///
/// # Errors
/// * If the `setting` does not contain a '$' character.
/// * If the `setting` contains another '$' character after the first one.
/// * If the `<salt>` part of the `setting` is empty.
/// * If the `<iter>` part of the `setting` cannot be converted into a `u32` integer.
///
/// # Example
/// ```
/// let password = "my_password";
/// let setting = "$8$3e8$salt";
/// let result = crypt_pbkdf2(password, setting);
/// assert!(result.is_some());
/// ```
///
/// # Note
/// The `crypt_pbkdf2` function uses the SHA256 hashing algorithm for the PBKDF2 operation.
/// The output of the PBKDF2 operation is base64-encoded using the BCrypt variant of base64.
pub fn crypt_pbkdf2(passw: &str, setting: &str) -> Option<String> {
if let Some((iter_str, salt)) = &setting[3..].split_once('$') {
if salt.contains('$') {
return None;
}
let actual_salt = if salt.len() > 0 {
salt.to_string()
} else {
gen_salt()?
};
let iter = u32::from_str_radix(iter_str, 16).ok()?;
let mut buffer = [0u8; 32];
pbkdf2_hmac::<Sha256>(passw.as_bytes(), actual_salt.as_bytes(), iter, &mut buffer);
Some(format!(
"$8${}${}${}",
iter_str,
salt,
Base64Bcrypt::encode_string(&buffer)
))
} else {
None
}
}
+115
View File
@@ -0,0 +1,115 @@
use super::gen_salt;
use crate::platform::types::*;
use alloc::string::{String, ToString};
use base64ct::{Base64Bcrypt, Encoding};
use core::{str, u32};
use scrypt::{scrypt, Params};
/// Map for encoding and decoding
#[inline(always)]
fn to_digit(c: char, radix: u32) -> Option<u32> {
match c {
'.' => Some(0),
'/' => Some(1),
_ => c.to_digit(radix).map(|d| d + 2),
}
}
/// Decodes a 5 character lengt str value to c_uint
///
/// # Arguments
///
/// * `value` - A string slice that represents a u32 value in base64
///
/// # Returns
///
/// * `Option<c_uint>` - Returns the decoded c_uint value if successful, otherwise None
fn dencode_uint(value: &str) -> Option<c_uint> {
if value.len() != 5 {
return None;
}
let result = value
.chars()
.enumerate()
.try_fold(0 as c_uint, |acc, (i, c)| {
acc.checked_add((to_digit(c, 30)? as c_uint) << (i * 6))
});
Some(result?)
}
/// Reads settings for password encryption
///
/// # Arguments
///
/// * `setting` - A string slice that represents the settings
///
/// # Returns
///
/// * `Option<(c_uchar, c_uint, c_uint, String)>` - Returns a tuple containing the settings if successful, otherwise None
fn read_setting(setting: &str) -> Option<(c_uchar, c_uint, c_uint, String)> {
let nlog2 = to_digit(setting.chars().next()?, 30)? as c_uchar;
let r = dencode_uint(&setting[1..6])?;
let p = dencode_uint(&setting[6..11])?;
let salt = &setting[11..];
let actual_salt = if salt.len() > 0 {
salt.to_string()
} else {
gen_salt()?
};
Some((nlog2, r, p, actual_salt))
}
/// Performs Scrypt key derivation on a given password with a specific setting.
///
/// # Parameters
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
/// * `setting`: The settings for the Scrypt key derivation. It must be a string slice (`&str`)
/// and should follow the format `$<Nlog2>$<r>$<p>$<salt>`. The `<Nlog2>` part should be a decimal
/// number representing the logarithm base 2 of the CPU/memory cost factor N for Scrypt. The `<r>`
/// part should be a decimal number representing the block size r. The `<p>` part should be a decimal
/// number representing the parallelization factor p. The `<salt>` part should be a base64-encoded
/// string representing the salt to be used for the Scrypt function.
///
/// # Returns
/// * `Option<String>`: Returns `Some(String)` if the Scrypt operation was successful, where the
/// returned string is the result of the Scrypt operation formatted according to the Modular
/// Crypt Format (MCF). If the Scrypt operation failed, it returns `None`.
///
/// # Errors
/// * If the `setting` length is less than 14 characters.
/// * If the `scrypt` function fails to perform the Scrypt operation.
///
/// # Example
/// ```
/// let password = "my_password";
/// let setting = "$7$C6..../....SodiumChloride";
/// let result = crypt_scrypt(password, setting);
/// assert!(result.is_some());
/// ```
///
/// # Note
/// The `crypt_scrypt` function uses the Scrypt key derivation function for hashing.
/// The output of the Scrypt operation is base64-encoded using the BCrypt variant of base64.
pub fn crypt_scrypt(passw: &str, setting: &str) -> Option<String> {
if setting.len() < 14 {
return None;
}
let (nlog2, r, p, salt) = read_setting(&setting[3..])?;
let params = Params::new(nlog2, r, p, 32).ok()?;
let mut output = [0u8; 32];
scrypt(passw.as_bytes(), salt.as_bytes(), &params, &mut output).ok()?;
Some(format!(
"$7${}${}${}",
&setting[3..14],
salt,
Base64Bcrypt::encode_string(&output)
))
}
+141
View File
@@ -0,0 +1,141 @@
use alloc::string::{String, ToString};
use sha_crypt::{
sha256_crypt_b64, sha512_crypt_b64, Sha256Params, Sha512Params, ROUNDS_DEFAULT, ROUNDS_MAX,
ROUNDS_MIN,
};
use crate::platform::types::*;
// key limit is not part of the original design, added for DoS protection.
// rounds limit has been lowered (versus the reference/spec), also for DoS
// protection. runtime is O(klen^2 + klen*rounds)
const KEY_MAX: usize = 256;
const SALT_MAX: usize = 16;
const RSTRING: &str = "rounds=";
pub enum ShaType {
Sha256,
Sha512,
}
/// Performs SHA hashing on a given password with a specific setting.
///
/// # Parameters
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
/// * `setting`: The settings for the SHA hashing. It must be a string slice (`&str`)
/// and should start with "$5$" for SHA256 or "$6$" for SHA512. The rest of the string should represent the salt
/// to be used for the SHA hashing.
/// * `cipher`: The type of SHA algorithm to use. It should be either `ShaType::Sha256` or `ShaType::Sha512`.
///
/// # Returns
/// * `Option<String>`: Returns `Some(String)` if the SHA operation was successful, where the
/// returned string is the result of the SHA operation formatted according to the Modular
/// Crypt Format (MCF). If the SHA operation failed, it returns `None`.
///
/// # Errors
/// * If the `passw` length exceeds `KEY_MAX`.
/// * If the `setting` does not start with "$5$" or "$6$".
/// * If the `setting` does not contain a '$' character.
/// * If the `setting` contains another '$' character after the first one.
/// * If the `setting` contains invalid characters.
/// * If the `setting` contains an invalid number of rounds.
/// * If the `sha256_crypt_b64` or `sha512_crypt_b64` function fails to hash the password.
///
/// # Example
/// ```
/// let password = "my_password";
/// let setting = "$5$rounds=1400$anotherlongsaltstringg";
/// let result = crypt_sha(password, setting, ShaType::Sha256);
/// assert!(result.is_some());
/// ```
///
/// # Note
/// The `crypt_sha` function uses the SHA256 or SHA512 hashing algorithm for hashing.
/// The output of the SHA operation is base64-encoded using the BCrypt variant of base64.
pub fn crypt_sha(passw: &str, setting: &str, cipher: ShaType) -> Option<String> {
let mut cursor = 3;
let rounds;
/* reject large keys */
if passw.len() > KEY_MAX {
return None;
}
// SHA256
// setting: $5$rounds=n$setting$ (rounds=n$ and closing $ are optional)
// SHA512
// setting: $6$rounds=n$setting$ (rounds=n$ and closing $ are optional)
let param = match cipher {
ShaType::Sha256 => "$5$",
ShaType::Sha512 => "$6$",
};
if &setting[0..3] != param {
return None;
}
let has_round;
// 7 is len("rounds=")
if &setting[cursor..cursor + 7] == RSTRING {
cursor += 7;
has_round = true;
if let Some(c_end) = setting[cursor..].chars().position(|r| r == '$') {
if let Ok(u) = setting[cursor..cursor + c_end].parse::<c_ulong>() {
cursor += c_end + 1;
rounds = u.min(ROUNDS_MAX as c_ulong).max(ROUNDS_MIN as c_ulong);
} else {
return None;
}
} else {
return None;
}
} else {
has_round = false;
rounds = ROUNDS_DEFAULT as c_ulong;
}
let mut slen = cursor;
for i in 0..SALT_MAX.min(setting.len() - cursor) {
let idx = cursor + i;
if &setting[idx..idx + 1] == "$" {
break;
}
// reject characters that interfere with /etc/shadow parsing
if &setting[idx..idx + 1] == "\n" || &setting[idx..idx + 1] == ":" {
return None;
}
slen += 1;
}
let setting = &setting[cursor..slen];
if let Ok(enc) = match cipher {
ShaType::Sha256 => {
let params = Sha256Params::new(rounds as usize)
.unwrap_or(Sha256Params::new(ROUNDS_DEFAULT).unwrap());
sha256_crypt_b64(passw.as_bytes(), setting.as_bytes(), &params)
}
ShaType::Sha512 => {
let params = Sha512Params::new(rounds as usize)
.unwrap_or(Sha512Params::new(ROUNDS_DEFAULT).unwrap());
sha512_crypt_b64(passw.as_bytes(), setting.as_bytes(), &params)
}
} {
let (r_slice, rn_slice) = if has_round {
(RSTRING, rounds.to_string() + "$")
} else {
("", String::new())
};
Some(format!(
"{}{}{}{}${}",
param, r_slice, rn_slice, setting, enc
))
} else {
None
}
}
+1
View File
@@ -4,6 +4,7 @@ pub mod arpa_inet;
pub mod assert;
pub mod bits_pthread;
pub mod bits_sched;
pub mod crypt;
pub mod ctype;
pub mod dirent;
#[path = "dl-tls/mod.rs"]
+9 -5
View File
@@ -10,8 +10,11 @@ use core::{
use crate::{
c_str::CStr,
header::{
errno, fcntl, limits, stdlib::getenv, sys_ioctl, sys_resource, sys_time, sys_utsname,
termios, time::timespec,
crypt::{crypt_data, crypt_r},
errno, fcntl, limits,
stdlib::getenv,
sys_ioctl, sys_resource, sys_time, sys_utsname, termios,
time::timespec,
},
platform::{self, types::*, Pal, Sys},
};
@@ -123,9 +126,10 @@ pub extern "C" fn confstr(name: c_int, buf: *mut c_char, len: size_t) -> size_t
unimplemented!();
}
// #[no_mangle]
pub extern "C" fn crypt(key: *const c_char, salt: *const c_char) -> *mut c_char {
unimplemented!();
#[no_mangle]
pub unsafe extern "C" fn crypt(key: *const c_char, salt: *const c_char) -> *mut c_char {
let mut data = crypt_data::new();
crypt_r(key, salt, &mut data as *mut _)
}
#[no_mangle]
+6
View File
@@ -6,6 +6,12 @@ EXPECT_NAMES=\
assert \
constructor \
ctype \
crypt/blowfish \
crypt/md5 \
crypt/pbkdf2 \
crypt/scrypt \
crypt/sha256 \
crypt/sha512 \
destructor \
dirent/scandir \
errno \
+26
View File
@@ -0,0 +1,26 @@
#include <assert.h>
#include <crypt.h>
#include <string.h>
#include <unistd.h>
int main()
{
char *expected_output = "$2y$12$CJr4uRfziaGp4CWIBk0fB.I2tCOHYe3pomaWbC53/92p";
char *result = crypt("correctbatteryhorsestapler", "$2y$12$L6Bc/AlTQHyd9liGgGEZyO");
assert(strcmp(result, expected_output) == 0);
// Invalid salt for Blowfish
result = crypt("correctbatteryhorsestapler", "$2t$12$L6Bc/AlTQHyd9liGgGEZyO");
assert(result == NULL);
expected_output = "$2a$4$IAwt7hxuME3DekssMMTWU.xnJub2Xn45xK/oP.wWt3UC";
result = crypt("password", "$2a$04$UuTkLRZZ6QofpDOlMz32Mu");
assert(strcmp(result, expected_output) == 0);
// Invalid salt for Blowfish
result = "$2b$10$testtesttesttes";
result = crypt("correctbatteryhorsestapler", "$2y$12$L6Bc/AlTQHyd9liGgGEZyO");
assert(result != NULL);
return 0;
}
+38
View File
@@ -0,0 +1,38 @@
/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <crypt.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main () {
const char salt[] = "$1$saltstring";
char *cp;
int result = 0;
cp = crypt ("Hello world!", salt);
/* MD5 is disabled in FIPS mode. */
if (cp) {
result |= strcmp ("$1$saltstri$YMyguxXMBpd2TEZ.vS/3q1", cp);
if (!result)
printf("Success!\n");
}
return result;
}
+25
View File
@@ -0,0 +1,25 @@
#include <assert.h>
#include <crypt.h>
#include <string.h>
#include <unistd.h>
int main()
{
char *expected_output = "$8$3e8$salt$ZyTZgT5Pyp4CKdom6q6zHg0QrrAQO4ptWYCpWz4gu16";
char *result = crypt("pleaseletmein", "$8$3e8$salt");
assert(strcmp(result, expected_output) == 0);
// No salt
result = crypt("pleaseletmein", "$8$3e8$");
assert(result != NULL);
// Invalid encoded number for rounds
result = crypt("pleaseletmein", "$8$$salt");
assert(result == NULL);
// Invalid encoded number for rounds
result = crypt("pleaseletmein", "$8$.$salt");
assert(result == NULL);
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include <assert.h>
#include <crypt.h>
#include <string.h>
#include <unistd.h>
int main()
{
char *expected_output = "$7$C6..../....$SodiumChloride$aAM7wxp7ayfEF.ZLedy2490m85oOR228oZTB7jPbmdG";
char *result = crypt("pleaseletmein", "$7$C6..../....SodiumChloride");
assert(strcmp(result, expected_output) == 0);
// No salt
result = crypt("pleaseletmein", "$7$C6..../....");
assert(result != NULL);
// Invalid encoded number for r
result = crypt("pleaseletmein", "$7$C6.../....SodiumChloride");
assert(result == NULL);
// Invalid encoded number for p
result = crypt("pleaseletmein", "$7$C6..../...SodiumChloride");
assert(result == NULL);
return 0;
}
+67
View File
@@ -0,0 +1,67 @@
/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <crypt.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static const struct
{
const char *salt;
const char *input;
const char *expected;
} tests[] =
{
{ "$5$saltstring", "Hello world!",
"$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" },
{ "$5$rounds=10000$saltstringsaltstring", "Hello world!",
"$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2."
"opqey6IcA" },
{ "$5$rounds=5000$toolongsaltstring", "This is just a test",
"$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8"
"mGRcvxa5" },
{ "$5$rounds=1400$anotherlongsaltstring",
"a very much longer text to encrypt. This one even stretches over more"
"than one line.",
"$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12"
"oP84Bnq1" },
{ "$5$rounds=77777$short",
"we have a short salt string but not a short password",
"$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" },
{ "$5$rounds=123456$asaltof16chars..", "a short string",
"$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/"
"cZKmF/wJvD" },
{ "$5$rounds=10$roundstoolow", "the minimum number is still observed",
"$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97"
"2bIC" },
};
const int ntests = sizeof(tests) / sizeof(tests[0]);
int main (void) {
int result = 0;
for (int i = 0; i < ntests; ++i){
char *cp = crypt (tests[i].input, tests[i].salt);
if (strcmp (cp, tests[i].expected) != 0) {
printf ("test %d: expected \"%s\", got \"%s\"\n", i, tests[i].expected, cp);
result = 1;
}
}
return result;
}
+72
View File
@@ -0,0 +1,72 @@
/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <crypt.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static const struct
{
const char *salt;
const char *input;
const char *expected;
} tests[] =
{
{ "$6$saltstring", "Hello world!",
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
"esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" },
{ "$6$rounds=10000$saltstringsaltstring", "Hello world!",
"$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb"
"HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." },
{ "$6$rounds=5000$toolongsaltstring", "This is just a test",
"$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ"
"zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" },
{ "$6$rounds=1400$anotherlongsaltstring",
"a very much longer text to encrypt. This one even stretches over more"
"than one line.",
"$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP"
"vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" },
{ "$6$rounds=77777$short",
"we have a short salt string but not a short password",
"$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g"
"ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" },
{ "$6$rounds=123456$asaltof16chars..", "a short string",
"$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
"elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" },
{ "$6$rounds=10$roundstoolow", "the minimum number is still observed",
"$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x"
"hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." },
};
const int ntests = sizeof(tests) / sizeof(tests[0]);
int main(void) {
int result = 0;
int i;
for (i = 0; i < ntests; ++i) {
char * cp = crypt(tests[i].input, tests[i].salt);
if (strcmp(cp, tests[i].expected) != 0) {
printf("test %d: expected \"%s\", got \"%s\"\n", i, tests[i].expected, cp);
result = 1;
}
}
return result;
}
@@ -0,0 +1 @@
Success!