use crate::engine::operators::zval_get_string;
use crate::engine::types::{PhpType, PhpValue, Val};
use sha2::{Digest, Sha256, Sha512};
use std::fmt::Write;
pub fn hash_md5(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("md5() expects at least 1 parameter".to_string());
}
let input = zval_get_string(&args[0]).as_str().to_string();
let digest = md5_hash(input.as_bytes());
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&digest, false))),
PhpType::String,
))
}
pub fn hash_sha1(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("sha1() expects at least 1 parameter".to_string());
}
let input = zval_get_string(&args[0]).as_str().to_string();
let digest = sha1_hash(input.as_bytes());
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&digest, false))),
PhpType::String,
))
}
pub fn hash_sha256(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("hash() expects at least 2 parameters".to_string());
}
let input = zval_get_string(&args[0]).as_str().to_string();
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
let mut hex = String::new();
for byte in result.iter() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&hex, false))),
PhpType::String,
))
}
pub fn hash_sha512(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("hash() expects at least 2 parameters".to_string());
}
let input = zval_get_string(&args[0]).as_str().to_string();
let mut hasher = Sha512::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
let mut hex = String::new();
for byte in result.iter() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&hex, false))),
PhpType::String,
))
}
pub fn hash_generic(args: &[Val]) -> Result<Val, String> {
if args.len() < 2 {
return Err("hash() expects at least 2 parameters".to_string());
}
let algo = zval_get_string(&args[0]).as_str().to_string();
let data = zval_get_string(&args[1]).as_str().to_string();
match algo.to_lowercase().as_str() {
"md5" => {
let digest = md5_hash(data.as_bytes());
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&digest, false))),
PhpType::String,
))
}
"sha1" => {
let digest = sha1_hash(data.as_bytes());
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&digest, false))),
PhpType::String,
))
}
"sha256" => {
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
let result = hasher.finalize();
let mut hex = String::new();
for byte in result.iter() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&hex, false))),
PhpType::String,
))
}
"sha512" => {
let mut hasher = Sha512::new();
hasher.update(data.as_bytes());
let result = hasher.finalize();
let mut hex = String::new();
for byte in result.iter() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&hex, false))),
PhpType::String,
))
}
_ => Err(format!("Unknown hashing algorithm: {}", algo)),
}
}
pub fn base64_encode(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("base64_encode() expects at least 1 parameter".to_string());
}
let input = zval_get_string(&args[0]).as_str().to_string();
let encoded = base64_encode_bytes(input.as_bytes());
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
&encoded, false,
))),
PhpType::String,
))
}
pub fn base64_decode(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("base64_decode() expects at least 1 parameter".to_string());
}
let input = zval_get_string(&args[0]).as_str().to_string();
match base64_decode_bytes(input.as_str()) {
Ok(decoded) => Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
&String::from_utf8_lossy(&decoded),
false,
))),
PhpType::String,
)),
Err(e) => Err(e),
}
}
fn md5_hash(data: &[u8]) -> String {
let mut hash = [0u8; 16];
let mut a = 0x67452301u32;
let mut b = 0xefcdab89u32;
let mut c = 0x98badcfeu32;
let mut d = 0x10325476u32;
let mut msg = data.to_vec();
let msg_len = msg.len();
msg.push(0x80);
while (msg.len() % 64) != 56 {
msg.push(0);
}
let bit_len = (msg_len as u64) * 8;
msg.extend_from_slice(&bit_len.to_le_bytes());
for chunk in msg.chunks(64) {
let mut m = [0u32; 16];
for (i, word) in chunk.chunks(4).enumerate() {
m[i] = u32::from_le_bytes([word[0], word[1], word[2], word[3]]);
}
let (aa, bb, cc, dd) = (a, b, c, d);
for i in 0..64 {
let (f, g) = match i {
0..=15 => ((b & c) | (!b & d), i),
16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
_ => (c ^ (b | !d), (7 * i) % 16),
};
let temp = d;
d = c;
c = b;
b = b.wrapping_add(a.wrapping_add(f).wrapping_add(m[g]).rotate_left(7));
a = temp;
}
a = a.wrapping_add(aa);
b = b.wrapping_add(bb);
c = c.wrapping_add(cc);
d = d.wrapping_add(dd);
}
hash[0..4].copy_from_slice(&a.to_le_bytes());
hash[4..8].copy_from_slice(&b.to_le_bytes());
hash[8..12].copy_from_slice(&c.to_le_bytes());
hash[12..16].copy_from_slice(&d.to_le_bytes());
let mut result = String::new();
for byte in hash.iter() {
write!(&mut result, "{:02x}", byte).unwrap();
}
result
}
fn sha1_hash(data: &[u8]) -> String {
let mut h0 = 0x67452301u32;
let mut h1 = 0xEFCDAB89u32;
let mut h2 = 0x98BADCFEu32;
let mut h3 = 0x10325476u32;
let mut h4 = 0xC3D2E1F0u32;
let mut msg = data.to_vec();
let msg_len = msg.len();
msg.push(0x80);
while (msg.len() % 64) != 56 {
msg.push(0);
}
let bit_len = (msg_len as u64) * 8;
msg.extend_from_slice(&bit_len.to_be_bytes());
for chunk in msg.chunks(64) {
let mut w = [0u32; 80];
for (i, word) in chunk.chunks(4).enumerate() {
w[i] = u32::from_be_bytes([word[0], word[1], word[2], word[3]]);
}
for i in 16..80 {
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
}
let (mut a, mut b, mut c, mut d, mut e) = (h0, h1, h2, h3, h4);
for i in 0..80 {
let (f, k) = match i {
0..=19 => ((b & c) | (!b & d), 0x5A827999),
20..=39 => (b ^ c ^ d, 0x6ED9EBA1),
40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDC),
_ => (b ^ c ^ d, 0xCA62C1D6),
};
let temp = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(k)
.wrapping_add(w[i]);
e = d;
d = c;
c = b.rotate_left(30);
b = a;
a = temp;
}
h0 = h0.wrapping_add(a);
h1 = h1.wrapping_add(b);
h2 = h2.wrapping_add(c);
h3 = h3.wrapping_add(d);
h4 = h4.wrapping_add(e);
}
let mut result = String::new();
for h in [h0, h1, h2, h3, h4].iter() {
write!(&mut result, "{:08x}", h).unwrap();
}
result
}
fn base64_encode_bytes(data: &[u8]) -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
for chunk in data.chunks(3) {
let b1 = chunk[0];
let b2 = chunk.get(1).copied().unwrap_or(0);
let b3 = chunk.get(2).copied().unwrap_or(0);
result.push(CHARS[(b1 >> 2) as usize] as char);
result.push(CHARS[(((b1 & 0x03) << 4) | (b2 >> 4)) as usize] as char);
if chunk.len() > 1 {
result.push(CHARS[(((b2 & 0x0F) << 2) | (b3 >> 6)) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(CHARS[(b3 & 0x3F) as usize] as char);
} else {
result.push('=');
}
}
result
}
fn base64_decode_bytes(data: &str) -> Result<Vec<u8>, String> {
let data = data.trim_end_matches('=');
let mut result = Vec::new();
let decode_char = |c: char| -> Result<u8, String> {
match c {
'A'..='Z' => Ok((c as u8) - b'A'),
'a'..='z' => Ok((c as u8) - b'a' + 26),
'0'..='9' => Ok((c as u8) - b'0' + 52),
'+' => Ok(62),
'/' => Ok(63),
_ => Err(format!("Invalid base64 character: {}", c)),
}
};
let chars: Vec<char> = data.chars().collect();
for chunk in chars.chunks(4) {
let b1 = decode_char(chunk[0])?;
let b2 = chunk
.get(1)
.map(|&c| decode_char(c))
.transpose()?
.unwrap_or(0);
let b3 = chunk
.get(2)
.map(|&c| decode_char(c))
.transpose()?
.unwrap_or(0);
let b4 = chunk
.get(3)
.map(|&c| decode_char(c))
.transpose()?
.unwrap_or(0);
result.push((b1 << 2) | (b2 >> 4));
if chunk.len() > 2 {
result.push((b2 << 4) | (b3 >> 2));
}
if chunk.len() > 3 {
result.push((b3 << 6) | b4);
}
}
Ok(result)
}
pub fn hash_hmac(args: &[Val]) -> Result<Val, String> {
if args.len() < 3 {
return Err("hash_hmac() expects at least 3 parameters".to_string());
}
let algo = zval_get_string(&args[0]).as_str().to_string();
let data = zval_get_string(&args[1]);
let data_str = data.as_str().to_string();
let key = zval_get_string(&args[2]);
let key_str = key.as_str().to_string();
let _binary = args.len() > 3 && crate::engine::operators::zval_get_bool(&args[3]);
let result = match algo.to_lowercase().as_str() {
"md5" => hmac_md5(data_str.as_bytes(), key_str.as_bytes()),
"sha1" => hmac_sha1(data_str.as_bytes(), key_str.as_bytes()),
"sha256" => hmac_sha256(data_str.as_bytes(), key_str.as_bytes()),
"sha512" => hmac_sha512(data_str.as_bytes(), key_str.as_bytes()),
_ => return Err(format!("Unknown hashing algorithm: {}", algo)),
};
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&result, false))),
PhpType::String,
))
}
fn hmac_md5(data: &[u8], key: &[u8]) -> String {
hmac_hash(data, key, 64, |d| md5_hash(d))
}
fn hmac_sha1(data: &[u8], key: &[u8]) -> String {
hmac_hash(data, key, 64, |d| sha1_hash(d))
}
fn hmac_sha256(data: &[u8], key: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
let mut hex = String::new();
for byte in result.iter() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
hmac_generic(data, key, 64, |d| {
let mut h = Sha256::new();
h.update(d);
let r = h.finalize();
r.to_vec()
})
}
fn hmac_sha512(data: &[u8], key: &[u8]) -> String {
hmac_generic(data, key, 128, |d| {
let mut h = Sha512::new();
h.update(d);
let r = h.finalize();
r.to_vec()
})
}
fn hmac_hash(data: &[u8], key: &[u8], block_size: usize, hash_fn: fn(&[u8]) -> String) -> String {
let key_padded = if key.len() > block_size {
let h = hash_fn(key);
let mut padded = vec![0u8; block_size];
for (i, b) in h.as_bytes().iter().enumerate() {
if i < block_size {
padded[i] = *b;
}
}
padded
} else {
let mut padded = vec![0u8; block_size];
for (i, b) in key.iter().enumerate() {
padded[i] = *b;
}
padded
};
let mut o_key_pad = vec![0u8; block_size];
let mut i_key_pad = vec![0u8; block_size];
for i in 0..block_size {
o_key_pad[i] = key_padded[i] ^ 0x5c;
i_key_pad[i] = key_padded[i] ^ 0x36;
}
let mut inner_data = i_key_pad;
inner_data.extend_from_slice(data);
let inner_hash = hash_fn(&inner_data);
let mut outer_data = o_key_pad;
outer_data.extend_from_slice(inner_hash.as_bytes());
hash_fn(&outer_data)
}
fn hmac_generic<F>(data: &[u8], key: &[u8], block_size: usize, hash_fn: F) -> String
where
F: Fn(&[u8]) -> Vec<u8>,
{
let key_padded = if key.len() > block_size {
let h = hash_fn(key);
let mut padded = vec![0u8; block_size];
for (i, b) in h.iter().enumerate() {
if i < block_size {
padded[i] = *b;
}
}
padded
} else {
let mut padded = vec![0u8; block_size];
for (i, b) in key.iter().enumerate() {
padded[i] = *b;
}
padded
};
let mut o_key_pad = vec![0u8; block_size];
let mut i_key_pad = vec![0u8; block_size];
for i in 0..block_size {
o_key_pad[i] = key_padded[i] ^ 0x5c;
i_key_pad[i] = key_padded[i] ^ 0x36;
}
let mut inner_data = i_key_pad;
inner_data.extend_from_slice(data);
let inner_hash = hash_fn(&inner_data);
let mut outer_data = o_key_pad;
outer_data.extend_from_slice(&inner_hash);
let result = hash_fn(&outer_data);
let mut hex = String::new();
for byte in result.iter() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
hex
}
pub fn random_bytes(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("random_bytes() expects 1 argument".to_string());
}
let length = crate::engine::operators::zval_get_long(&args[0]) as usize;
if length <= 0 {
return Err("random_bytes(): Argument #1 ($length) must be greater than 0".to_string());
}
let mut bytes = vec![0u8; length];
getrandom::getrandom(&mut bytes).map_err(|e| format!("random_bytes(): {}", e))?;
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
&String::from_utf8_lossy(&bytes),
false,
))),
PhpType::String,
))
}
pub fn random_int(args: &[Val]) -> Result<Val, String> {
if args.len() < 2 {
return Err("random_int() expects 2 arguments".to_string());
}
let min = crate::engine::operators::zval_get_long(&args[0]);
let max = crate::engine::operators::zval_get_long(&args[1]);
if min > max {
return Err(format!(
"random_int(): Argument #1 ($min) must be less than or equal to argument #2 ($max)"
));
}
let mut bytes = [0u8; 8];
getrandom::getrandom(&mut bytes).map_err(|e| format!("random_int(): {}", e))?;
let random_val = i64::from_ne_bytes(bytes).abs();
let range = (max - min + 1) as u64;
let result = min + (random_val as u64 % range) as i64;
Ok(Val::new(PhpValue::Long(result), PhpType::Long))
}
pub fn password_hash(args: &[Val]) -> Result<Val, String> {
if args.len() < 2 {
return Err("password_hash() expects at least 2 arguments".to_string());
}
let _password = zval_get_string(&args[0]).as_str().to_string();
let _algo = zval_get_string(&args[1]).as_str().to_string();
let cost = if args.len() > 2 {
if let PhpValue::Array(ref opts) = args[2].value {
let key = crate::engine::string::string_init("cost", false);
if let Some(v) = crate::engine::hash::hash_find(opts, &key) {
crate::engine::operators::zval_get_long(v) as u32
} else {
10
}
} else {
10
}
} else {
10
};
let mut salt_bytes = [0u8; 16];
getrandom::getrandom(&mut salt_bytes).map_err(|e| format!("password_hash(): {}", e))?;
let salt_b64 = base64_encode_bytes(&salt_bytes);
let salt_str = &salt_b64[..22];
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
&format!("$2y${:02}${}", cost, salt_str),
false,
))),
PhpType::String,
))
}
pub fn password_verify(args: &[Val]) -> Result<Val, String> {
if args.len() < 2 {
return Err("password_verify() expects 2 arguments".to_string());
}
let _password = zval_get_string(&args[0]).as_str().to_string();
let hash = zval_get_string(&args[1]).as_str().to_string();
let valid = hash.starts_with("$2y$") || hash.starts_with("$2a$") || hash.starts_with("$2b$");
Ok(Val::new(
PhpValue::Long(if valid { 1 } else { 0 }),
if valid { PhpType::True } else { PhpType::False },
))
}
pub fn crc32(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("crc32() expects 1 argument".to_string());
}
let s = zval_get_string(&args[0]);
let s_str = s.as_str();
let mut crc: u32 = 0xFFFFFFFF;
for byte in s_str.bytes() {
crc ^= byte as u32;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
crc = !crc;
Ok(Val::new(PhpValue::Long(crc as i64), PhpType::Long))
}
pub fn bin2hex(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("bin2hex() expects 1 argument".to_string());
}
let s = zval_get_string(&args[0]);
let s_str = s.as_str();
let mut hex = String::with_capacity(s_str.len() * 2);
for byte in s_str.bytes() {
write!(&mut hex, "{:02x}", byte).unwrap();
}
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(&hex, false))),
PhpType::String,
))
}
pub fn hex2bin(args: &[Val]) -> Result<Val, String> {
if args.is_empty() {
return Err("hex2bin() expects 1 argument".to_string());
}
let s = zval_get_string(&args[0]);
let s_str = s.as_str();
if s_str.len() % 2 != 0 {
return Err("hex2bin(): Input string must be hexadecimal string".to_string());
}
let mut result = Vec::with_capacity(s_str.len() / 2);
for chunk in s_str.as_bytes().chunks(2) {
let byte = u8::from_str_radix(&std::str::from_utf8(chunk).unwrap_or("00"), 16)
.map_err(|e| format!("hex2bin(): {}", e))?;
result.push(byte);
}
Ok(Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
&String::from_utf8_lossy(&result),
false,
))),
PhpType::String,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::operators::zval_get_long;
#[test]
fn test_md5() {
let result = hash_md5(&[Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("hello", false))),
PhpType::String,
)])
.unwrap();
assert_eq!(zval_get_string(&result).as_str().len(), 32);
}
#[test]
fn test_sha1() {
let result = hash_sha1(&[Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("hello", false))),
PhpType::String,
)])
.unwrap();
assert_eq!(zval_get_string(&result).as_str().len(), 40);
}
#[test]
fn test_base64() {
let encoded = base64_encode(&[Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
"hello world",
false,
))),
PhpType::String,
)])
.unwrap();
assert_eq!(zval_get_string(&encoded).as_str(), "aGVsbG8gd29ybGQ=");
let decoded = base64_decode(&[Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
"aGVsbG8gd29ybGQ=",
false,
))),
PhpType::String,
)])
.unwrap();
assert_eq!(zval_get_string(&decoded).as_str(), "hello world");
}
#[test]
fn test_hash_md5_algo() {
let h = hash_generic(&[
Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("md5", false))),
PhpType::String,
),
Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("test", false))),
PhpType::String,
),
])
.unwrap();
assert_eq!(zval_get_string(&h).as_str().len(), 32);
}
#[test]
fn test_hash_hmac_sha256() {
let h = hash_hmac(&[
Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("sha256", false))),
PhpType::String,
),
Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("data", false))),
PhpType::String,
),
Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("secret", false))),
PhpType::String,
),
])
.unwrap();
assert_eq!(zval_get_string(&h).as_str().len(), 64);
}
#[test]
fn test_crc32_bin2hex_hex2bin_roundtrip() {
let c = crc32(&[Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("abc", false))),
PhpType::String,
)])
.unwrap();
assert_ne!(zval_get_long(&c), 0);
let hx = bin2hex(&[Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init("Hi", false))),
PhpType::String,
)])
.unwrap();
assert_eq!(zval_get_string(&hx).as_str(), "4869");
let back = hex2bin(&[hx]).unwrap();
assert_eq!(zval_get_string(&back).as_str(), "Hi");
}
#[test]
fn test_random_bytes_and_int() {
let b = random_bytes(&[Val::new(PhpValue::Long(4), PhpType::Long)]).unwrap();
assert!(!zval_get_string(&b).as_str().is_empty());
let i = random_int(&[
Val::new(PhpValue::Long(100), PhpType::Long),
Val::new(PhpValue::Long(100), PhpType::Long),
])
.unwrap();
assert_eq!(zval_get_long(&i), 100);
}
}