use md5::{Digest, Md5};
use sha2::{Sha256, Sha384, Sha512};
const PADDING: &[u8; 32] = b"\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\
\x64\x00\x4E\x56\xFF\xFA\x01\x08\
\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\
\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
pub fn compute_encryption_key(
password: &[u8],
owner_key: &[u8],
permissions: i32,
file_id: &[u8],
revision: u32,
key_length: usize,
encrypt_metadata: bool,
) -> Vec<u8> {
if revision >= 5 {
return generate_random_encryption_key(key_length);
}
let mut hasher = Md5::new();
let mut padded_password = [0u8; 32];
let pass_len = password.len().min(32);
padded_password[..pass_len].copy_from_slice(&password[..pass_len]);
if pass_len < 32 {
padded_password[pass_len..].copy_from_slice(&PADDING[..(32 - pass_len)]);
}
hasher.update(padded_password);
hasher.update(owner_key);
hasher.update(permissions.to_le_bytes());
hasher.update(file_id);
if revision >= 4 && !encrypt_metadata {
hasher.update([0xFF, 0xFF, 0xFF, 0xFF]);
}
let mut hash = hasher.finalize().to_vec();
if revision >= 3 {
for _ in 0..50 {
let mut hasher = Md5::new();
hasher.update(&hash[..key_length.min(16)]);
hash = hasher.finalize().to_vec();
}
}
hash[..key_length.min(16)].to_vec()
}
fn generate_random_encryption_key(key_length: usize) -> Vec<u8> {
use sha2::{Digest, Sha256};
let mut key = Vec::with_capacity(key_length);
while key.len() < key_length {
let uuid1 = uuid::Uuid::new_v4();
let uuid2 = uuid::Uuid::new_v4();
let mut hasher = Sha256::new();
hasher.update(uuid1.as_bytes());
hasher.update(uuid2.as_bytes());
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
hasher.update(now.as_nanos().to_le_bytes());
let hash = hasher.finalize();
let remaining = key_length - key.len();
key.extend_from_slice(&hash[..remaining.min(32)]);
}
key
}
#[allow(dead_code)]
pub fn pad_password(password: &[u8]) -> Vec<u8> {
let mut padded = Vec::with_capacity(32);
let pass_len = password.len().min(32);
padded.extend_from_slice(&password[..pass_len]);
if pass_len < 32 {
padded.extend_from_slice(&PADDING[..(32 - pass_len)]);
}
padded
}
pub fn authenticate_user_password(
password: &[u8],
user_key: &[u8],
owner_key: &[u8],
permissions: i32,
file_id: &[u8],
revision: u32,
key_length: usize,
encrypt_metadata: bool,
user_encryption: Option<&[u8]>,
) -> Option<Vec<u8>> {
if revision >= 5 {
return authenticate_user_password_r5_r6(password, user_key, revision, user_encryption);
}
let key = compute_encryption_key(
password,
owner_key,
permissions,
file_id,
revision,
key_length,
encrypt_metadata,
);
let expected_user_key = if revision >= 3 {
compute_user_key_r3(&key, file_id)
} else {
compute_user_key_r2(&key)
};
if user_key.len() < 16 || expected_user_key.len() < 16 {
return None;
}
let matches = constant_time_compare(&user_key[..16], &expected_user_key[..16]);
if matches {
Some(key)
} else {
None
}
}
fn authenticate_user_password_r5_r6(
password: &[u8],
user_key: &[u8],
revision: u32,
user_encryption: Option<&[u8]>,
) -> Option<Vec<u8>> {
if user_key.len() < 48 {
return None;
}
let password = saslprep_password(password);
let password = truncate_password_utf8(&password);
let validation_salt = &user_key[32..40];
let key_salt = &user_key[40..48];
let hash = if revision >= 6 {
algorithm_2b(&password, validation_salt, &[])
} else {
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(validation_salt);
hasher.finalize().to_vec()
};
if !constant_time_compare(&hash[..32], &user_key[..32]) {
return None;
}
if revision >= 6 {
let ue = user_encryption?;
if ue.len() < 32 {
return None;
}
let intermediate_key = algorithm_2b(&password, key_salt, &[]);
let iv = [0u8; 16];
super::aes::aes256_decrypt_no_padding(&intermediate_key[..32], &iv, &ue[..32]).ok()
} else {
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(key_salt);
Some(hasher.finalize().to_vec())
}
}
fn saslprep_password(password: &[u8]) -> Vec<u8> {
let Ok(password_str) = std::str::from_utf8(password) else {
return password.to_vec();
};
match stringprep::saslprep(password_str) {
Ok(normalized) => normalized.as_bytes().to_vec(),
Err(_) => password.to_vec(),
}
}
fn algorithm_2b(password: &[u8], salt: &[u8], user_key: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(salt);
hasher.update(user_key);
let mut k = hasher.finalize().to_vec();
let mut round: usize = 0;
loop {
let k1_unit_len = password.len() + k.len() + user_key.len();
let mut k1 = Vec::with_capacity(k1_unit_len * 64);
for _ in 0..64 {
k1.extend_from_slice(password);
k1.extend_from_slice(&k);
k1.extend_from_slice(user_key);
}
let remainder = k1.len() % 16;
if remainder != 0 {
k1.extend(std::iter::repeat_n(0u8, 16 - remainder));
}
let aes_key = &k[..16];
let aes_iv = &k[16..32];
let e = match super::aes::aes128_encrypt_no_padding(aes_key, aes_iv, &k1) {
Ok(encrypted) => encrypted,
Err(_) => return k, };
let sum: u32 = e.iter().take(16).map(|&b| b as u32).sum();
let remainder = sum % 3;
k = match remainder {
0 => {
let mut h = Sha256::new();
h.update(&e);
h.finalize().to_vec()
},
1 => {
let mut h = Sha384::new();
h.update(&e);
h.finalize().to_vec()
},
_ => {
let mut h = Sha512::new();
h.update(&e);
h.finalize().to_vec()
},
};
round += 1;
let last_byte = *e.last().unwrap_or(&0) as usize;
if round >= 64 && last_byte <= round.saturating_sub(32) {
break;
}
}
k.truncate(32);
k
}
fn compute_user_key_r2(key: &[u8]) -> Vec<u8> {
super::rc4::rc4_crypt(key, PADDING)
}
fn compute_user_key_r3(key: &[u8], file_id: &[u8]) -> Vec<u8> {
let mut hasher = Md5::new();
hasher.update(PADDING);
hasher.update(file_id);
let mut hash = hasher.finalize().to_vec();
for i in 0..20 {
let mut modified_key = key.to_vec();
for byte in &mut modified_key {
*byte ^= i as u8;
}
hash = super::rc4::rc4_crypt(&modified_key, &hash);
}
hash.extend_from_slice(&[0u8; 16]);
hash
}
pub fn compute_owner_password_hash(
owner_password: &[u8],
user_password: &[u8],
revision: u32,
key_length: usize,
) -> Vec<u8> {
if revision >= 5 {
return compute_owner_hash_r5(owner_password, user_password);
}
let password = if owner_password.is_empty() {
user_password
} else {
owner_password
};
let padded_password = pad_password(password);
let mut hasher = Md5::new();
hasher.update(&padded_password);
let mut hash = hasher.finalize().to_vec();
if revision >= 3 {
for _ in 0..50 {
let mut hasher = Md5::new();
hasher.update(&hash[..key_length.min(16)]);
hash = hasher.finalize().to_vec();
}
}
let rc4_key_len = key_length.min(16);
let rc4_key = &hash[..rc4_key_len];
let padded_user = pad_password(user_password);
let mut result = super::rc4::rc4_crypt(rc4_key, &padded_user);
if revision >= 3 {
for i in 1..=19 {
let mut modified_key = rc4_key.to_vec();
for byte in &mut modified_key {
*byte ^= i as u8;
}
result = super::rc4::rc4_crypt(&modified_key, &result);
}
}
result
}
fn compute_owner_hash_r5(owner_password: &[u8], _user_password: &[u8]) -> Vec<u8> {
let validation_salt = generate_random_bytes(8);
let key_salt = generate_random_bytes(8);
let password = truncate_password_utf8(owner_password);
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(&validation_salt);
let hash = hasher.finalize();
let mut result = hash.to_vec(); result.extend_from_slice(&validation_salt); result.extend_from_slice(&key_salt);
result
}
pub fn compute_user_password_hash(encryption_key: &[u8], file_id: &[u8], revision: u32) -> Vec<u8> {
if revision >= 5 {
compute_user_hash_r5(encryption_key)
} else if revision >= 3 {
compute_user_key_r3(encryption_key, file_id)
} else {
compute_user_key_r2(encryption_key)
}
}
fn compute_user_hash_r5(user_password: &[u8]) -> Vec<u8> {
let validation_salt = generate_random_bytes(8);
let key_salt = generate_random_bytes(8);
let password = truncate_password_utf8(user_password);
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(&validation_salt);
let hash = hasher.finalize();
let mut result = hash.to_vec(); result.extend_from_slice(&validation_salt); result.extend_from_slice(&key_salt);
result
}
fn generate_random_bytes(len: usize) -> Vec<u8> {
use md5::{Digest, Md5};
let mut result = Vec::with_capacity(len);
while result.len() < len {
let uuid = uuid::Uuid::new_v4();
let mut hasher = Md5::new();
hasher.update(uuid.as_bytes());
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
hasher.update(now.as_nanos().to_le_bytes());
let hash = hasher.finalize();
let remaining = len - result.len();
result.extend_from_slice(&hash[..remaining.min(16)]);
}
result
}
fn truncate_password_utf8(password: &[u8]) -> Vec<u8> {
let mut result = password.to_vec();
if result.len() > 127 {
let mut end = 127;
while end > 0 && (result[end] & 0xC0) == 0x80 {
end -= 1;
}
result.truncate(end);
}
result
}
pub fn authenticate_owner_password(
owner_password: &[u8],
user_key: &[u8],
owner_key: &[u8],
permissions: i32,
file_id: &[u8],
revision: u32,
key_length: usize,
encrypt_metadata: bool,
owner_encryption: Option<&[u8]>,
) -> Option<Vec<u8>> {
if revision >= 5 {
return authenticate_owner_password_r5_r6(
owner_password,
owner_key,
user_key,
revision,
owner_encryption,
);
}
let password = if owner_password.is_empty() {
return None;
} else {
owner_password
};
let padded_password = pad_password(password);
let mut hasher = Md5::new();
hasher.update(&padded_password);
let mut hash = hasher.finalize().to_vec();
if revision >= 3 {
for _ in 0..50 {
let mut h = Md5::new();
h.update(&hash[..key_length.min(16)]);
hash = h.finalize().to_vec();
}
}
let rc4_key_len = key_length.min(16);
let rc4_key = &hash[..rc4_key_len];
let user_password_padded = if revision == 2 {
super::rc4::rc4_crypt(rc4_key, owner_key)
} else {
let mut result = owner_key.to_vec();
for i in (0..=19).rev() {
let mut modified_key = rc4_key.to_vec();
for byte in &mut modified_key {
*byte ^= i as u8;
}
result = super::rc4::rc4_crypt(&modified_key, &result);
}
result
};
authenticate_user_password(
&user_password_padded,
user_key,
owner_key,
permissions,
file_id,
revision,
key_length,
encrypt_metadata,
None, )
}
fn authenticate_owner_password_r5_r6(
password: &[u8],
owner_key: &[u8],
user_key: &[u8],
revision: u32,
owner_encryption: Option<&[u8]>,
) -> Option<Vec<u8>> {
if owner_key.len() < 48 || user_key.len() < 48 {
return None;
}
let password = saslprep_password(password);
let password = truncate_password_utf8(&password);
let owner_validation_salt = &owner_key[32..40];
let owner_key_salt = &owner_key[40..48];
let u_value = &user_key[..48];
let hash = if revision >= 6 {
algorithm_2b(&password, owner_validation_salt, u_value)
} else {
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(owner_validation_salt);
hasher.update(u_value);
hasher.finalize().to_vec()
};
if !constant_time_compare(&hash[..32], &owner_key[..32]) {
return None;
}
if revision >= 6 {
let oe = owner_encryption?;
if oe.len() < 32 {
return None;
}
let intermediate_key = algorithm_2b(&password, owner_key_salt, u_value);
let iv = [0u8; 16];
super::aes::aes256_decrypt_no_padding(&intermediate_key[..32], &iv, &oe[..32]).ok()
} else {
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(owner_key_salt);
hasher.update(u_value);
Some(hasher.finalize().to_vec())
}
}
fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut result = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
result |= x ^ y;
}
result == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pad_password() {
let password = b"test";
let padded = pad_password(password);
assert_eq!(padded.len(), 32);
assert_eq!(&padded[..4], b"test");
assert_eq!(&padded[4..], &PADDING[..28]);
}
#[test]
fn test_pad_password_long() {
let password = b"this is a very long password that exceeds 32 bytes";
let padded = pad_password(password);
assert_eq!(padded.len(), 32);
assert_eq!(&padded[..], &password[..32]);
}
#[test]
fn test_pad_password_exact() {
let password = &[0u8; 32];
let padded = pad_password(password);
assert_eq!(padded.len(), 32);
assert_eq!(&padded[..], password);
}
#[test]
fn test_constant_time_compare_equal() {
let a = b"test1234test1234";
let b = b"test1234test1234";
assert!(constant_time_compare(a, b));
}
#[test]
fn test_constant_time_compare_not_equal() {
let a = b"test1234test1234";
let b = b"test1234test1235";
assert!(!constant_time_compare(a, b));
}
#[test]
fn test_constant_time_compare_different_length() {
let a = b"test";
let b = b"testing";
assert!(!constant_time_compare(a, b));
}
#[test]
fn test_compute_encryption_key() {
let password = b"user";
let owner_key = &[0u8; 32];
let permissions = -1;
let file_id = b"test_file_id";
let revision = 2;
let key_length = 5;
let key = compute_encryption_key(
password,
owner_key,
permissions,
file_id,
revision,
key_length,
true,
);
assert_eq!(key.len(), key_length);
}
#[test]
fn test_owner_password_hash_r2() {
let owner = b"owner";
let user = b"user";
let revision = 2;
let key_length = 5;
let owner_hash = compute_owner_password_hash(owner, user, revision, key_length);
assert_eq!(owner_hash.len(), 32);
}
#[test]
fn test_owner_password_hash_r3() {
let owner = b"owner";
let user = b"user";
let revision = 3;
let key_length = 16;
let owner_hash = compute_owner_password_hash(owner, user, revision, key_length);
assert_eq!(owner_hash.len(), 32);
}
#[test]
fn test_owner_password_hash_empty_owner() {
let user = b"user";
let revision = 3;
let key_length = 16;
let hash1 = compute_owner_password_hash(b"", user, revision, key_length);
let hash2 = compute_owner_password_hash(user, user, revision, key_length);
assert_eq!(hash1, hash2);
}
#[test]
fn test_user_password_hash_r2() {
let key = [0u8; 5]; let file_id = b"test_file_id";
let revision = 2;
let user_hash = compute_user_password_hash(&key, file_id, revision);
assert_eq!(user_hash.len(), 32);
}
#[test]
fn test_user_password_hash_r3() {
let key = [0u8; 16]; let file_id = b"test_file_id";
let revision = 3;
let user_hash = compute_user_password_hash(&key, file_id, revision);
assert_eq!(user_hash.len(), 32);
}
#[test]
fn test_encryption_roundtrip_r2() {
let owner_pass = b"owner123";
let user_pass = b"user123";
let file_id = b"test_file_id_123";
let permissions = -1i32;
let revision = 2;
let key_length = 5;
let owner_hash = compute_owner_password_hash(owner_pass, user_pass, revision, key_length);
let encryption_key = compute_encryption_key(
user_pass,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
);
let user_hash = compute_user_password_hash(&encryption_key, file_id, revision);
let auth_result = authenticate_user_password(
user_pass,
&user_hash,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
None,
);
assert!(auth_result.is_some());
assert_eq!(auth_result.unwrap(), encryption_key);
}
#[test]
fn test_encryption_roundtrip_r3() {
let owner_pass = b"owner456";
let user_pass = b"user456";
let file_id = b"test_file_id_456";
let permissions = -1i32;
let revision = 3;
let key_length = 16;
let owner_hash = compute_owner_password_hash(owner_pass, user_pass, revision, key_length);
let encryption_key = compute_encryption_key(
user_pass,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
);
let user_hash = compute_user_password_hash(&encryption_key, file_id, revision);
let auth_result = authenticate_user_password(
user_pass,
&user_hash,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
None,
);
assert!(auth_result.is_some());
assert_eq!(auth_result.unwrap(), encryption_key);
}
#[test]
fn test_authenticate_user_password_short_user_key() {
let short_user_key = vec![0u8; 10];
let owner_key = vec![0u8; 32];
let result = authenticate_user_password(
b"",
&short_user_key,
&owner_key,
-1,
b"file_id",
2,
5,
true,
None,
);
assert!(result.is_none());
}
#[test]
fn test_saslprep_ascii_passthrough() {
let password = b"hello123";
let result = saslprep_password(password);
assert_eq!(result, b"hello123");
}
#[test]
fn test_saslprep_unicode_normalization() {
let password = "\u{FF21}".as_bytes();
let result = saslprep_password(password);
assert_eq!(result, b"A");
}
#[test]
fn test_authenticate_user_r5_correct_password() {
let password = b"test";
let validation_salt = [0x01u8; 8];
let key_salt = [0x02u8; 8];
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(validation_salt);
let hash = hasher.finalize();
let mut user_key = hash.to_vec();
user_key.extend_from_slice(&validation_salt);
user_key.extend_from_slice(&key_salt);
assert_eq!(user_key.len(), 48);
let result = authenticate_user_password(
password, &user_key, &[0u8; 48], -1, b"", 5, 32, true, None,
);
assert!(result.is_some());
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(key_salt);
let expected_key = hasher.finalize().to_vec();
assert_eq!(result.unwrap(), expected_key);
}
#[test]
fn test_authenticate_user_r5_wrong_password() {
let password = b"test";
let validation_salt = [0x01u8; 8];
let key_salt = [0x02u8; 8];
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(validation_salt);
let hash = hasher.finalize();
let mut user_key = hash.to_vec();
user_key.extend_from_slice(&validation_salt);
user_key.extend_from_slice(&key_salt);
let result =
authenticate_user_password(b"wrong", &user_key, &[0u8; 48], -1, b"", 5, 32, true, None);
assert!(result.is_none());
}
#[test]
fn test_authenticate_user_r5_short_u_value() {
let result = authenticate_user_password(
b"test", &[0u8; 40], &[0u8; 48], -1, b"", 5, 32, true, None,
);
assert!(result.is_none());
}
#[test]
fn test_authenticate_owner_password_r2_roundtrip() {
let owner_pass = b"owner123";
let user_pass = b"user123";
let file_id = b"test_file_id_123";
let permissions = -1i32;
let revision = 2;
let key_length = 5;
let owner_hash = compute_owner_password_hash(owner_pass, user_pass, revision, key_length);
let encryption_key = compute_encryption_key(
user_pass,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
);
let user_hash = compute_user_password_hash(&encryption_key, file_id, revision);
let result = authenticate_owner_password(
owner_pass,
&user_hash,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
None,
);
assert!(result.is_some());
assert_eq!(result.unwrap(), encryption_key);
}
#[test]
fn test_authenticate_owner_password_r3_roundtrip() {
let owner_pass = b"owner456";
let user_pass = b"user456";
let file_id = b"test_file_id_456";
let permissions = -1i32;
let revision = 3;
let key_length = 16;
let owner_hash = compute_owner_password_hash(owner_pass, user_pass, revision, key_length);
let encryption_key = compute_encryption_key(
user_pass,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
);
let user_hash = compute_user_password_hash(&encryption_key, file_id, revision);
let result = authenticate_owner_password(
owner_pass,
&user_hash,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
None,
);
assert!(result.is_some());
assert_eq!(result.unwrap(), encryption_key);
}
#[test]
fn test_authenticate_owner_password_wrong_password() {
let owner_pass = b"owner123";
let user_pass = b"user123";
let file_id = b"test_file_id_123";
let permissions = -1i32;
let revision = 3;
let key_length = 16;
let owner_hash = compute_owner_password_hash(owner_pass, user_pass, revision, key_length);
let encryption_key = compute_encryption_key(
user_pass,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
);
let user_hash = compute_user_password_hash(&encryption_key, file_id, revision);
let result = authenticate_owner_password(
b"wrong",
&user_hash,
&owner_hash,
permissions,
file_id,
revision,
key_length,
true,
None,
);
assert!(result.is_none());
}
#[test]
fn test_authenticate_owner_password_r5_roundtrip() {
let password = b"ownerpass";
let owner_validation_salt = [0x11u8; 8];
let owner_key_salt = [0x22u8; 8];
let user_key = [0xAAu8; 48];
let mut hasher = Sha256::new();
hasher.update(password.as_slice());
hasher.update(owner_validation_salt);
hasher.update(&user_key[..48]);
let hash = hasher.finalize();
let mut owner_key = hash.to_vec();
owner_key.extend_from_slice(&owner_validation_salt);
owner_key.extend_from_slice(&owner_key_salt);
let result = authenticate_owner_password(
password, &user_key, &owner_key, -1, b"", 5, 32, true, None,
);
assert!(result.is_some());
let mut hasher = Sha256::new();
hasher.update(password.as_slice());
hasher.update(owner_key_salt);
hasher.update(&user_key[..48]);
let expected_key = hasher.finalize().to_vec();
assert_eq!(result.unwrap(), expected_key);
}
#[test]
fn test_authenticate_owner_password_r5_wrong_password() {
let password = b"ownerpass";
let owner_validation_salt = [0x11u8; 8];
let owner_key_salt = [0x22u8; 8];
let user_key = [0xAAu8; 48];
let mut hasher = Sha256::new();
hasher.update(password.as_slice());
hasher.update(owner_validation_salt);
hasher.update(&user_key[..48]);
let hash = hasher.finalize();
let mut owner_key = hash.to_vec();
owner_key.extend_from_slice(&owner_validation_salt);
owner_key.extend_from_slice(&owner_key_salt);
let result = authenticate_owner_password(
b"wrong", &user_key, &owner_key, -1, b"", 5, 32, true, None,
);
assert!(result.is_none());
}
}