#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(test), no_std)]
#![doc = include_str!("../README.md")]
#[cfg(feature = "hashstrings")]
mod hashstrings;
#[cfg(feature = "hashstrings")]
pub use hashstrings::*;
#[cfg(not(test))]
extern crate core;
extern crate alloc;
use alloc::ffi::CString;
use alloc::string::String;
use alloc::vec::Vec;
use core::any::TypeId;
use core::ops::{Deref, DerefMut};
use core::slice;
use rand::rngs::Xoshiro256PlusPlus;
use rand::seq::SliceRandom;
use rand::{Rng, SeedableRng};
use zeroize::Zeroize;
#[doc(hidden)]
pub mod __private {
pub use alloc::ffi::CString;
pub use alloc::string::String;
pub use alloc::vec;
}
pub struct Encrusted<T>
where
T: Encrust,
T::Storage: Zeroize,
{
data: T::Storage,
seed: u64,
}
impl<T> Encrusted<T>
where
T: Encrust,
T::Storage: Zeroize,
{
pub fn new(data: T, seed: u64) -> Self {
let mut data = data.to_storage();
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(seed);
<T as Encrust>::toggle_encrust(&mut data, &mut encrust_rng);
Self { data, seed }
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub const unsafe fn from_encrusted_data(data: T::Storage, seed: u64) -> Self {
Self { data, seed }
}
pub fn reseed(&mut self, new_seed: u64) {
{
let mut decruster = Xoshiro256PlusPlus::seed_from_u64(self.seed);
<T as Encrust>::toggle_encrust(&mut self.data, &mut decruster);
}
self.seed = new_seed;
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(self.seed);
<T as Encrust>::toggle_encrust(&mut self.data, &mut encrust_rng);
}
#[doc(alias("expose", "unlock"))]
pub fn decrust(&mut self) -> DecrustGuard<'_, T> {
DecrustGuard::new(self)
}
}
impl<T> Drop for Encrusted<T>
where
T: Encrust,
T::Storage: Zeroize,
{
fn drop(&mut self) {
self.data.zeroize();
self.seed.zeroize();
}
}
pub struct DecrustGuard<'decrusted, T>
where
T: Encrust,
T::Storage: Zeroize,
{
encrusted_data: &'decrusted mut Encrusted<T>,
}
impl<'decrusted, T> DecrustGuard<'decrusted, T>
where
T: Encrust,
T::Storage: Zeroize,
{
fn new(encrusted_data: &'decrusted mut Encrusted<T>) -> Self {
let mut decruster = Xoshiro256PlusPlus::seed_from_u64(encrusted_data.seed);
<T as Encrust>::toggle_encrust(&mut encrusted_data.data, &mut decruster);
Self { encrusted_data }
}
}
impl<T> Drop for DecrustGuard<'_, T>
where
T: Encrust,
T::Storage: Zeroize,
{
fn drop(&mut self) {
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(self.encrusted_data.seed);
<T as Encrust>::toggle_encrust(&mut self.encrusted_data.data, &mut encrust_rng);
}
}
impl<T> Deref for DecrustGuard<'_, T>
where
T: Encrust,
T::Storage: Zeroize,
{
type Target = T::Ref;
fn deref(&self) -> &Self::Target {
unsafe { <T as Encrust>::as_ref(&self.encrusted_data.data) }
}
}
impl<T> DerefMut for DecrustGuard<'_, T>
where
T: Encrust,
T::Storage: Zeroize,
{
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { <T as Encrust>::as_mut_ref(&mut self.encrusted_data.data) }
}
}
pub trait Encrust {
type Storage;
type Ref: ?Sized;
fn to_storage(self) -> Self::Storage;
unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref;
unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref;
fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng);
}
pub trait InPlaceEncrust {
fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng);
}
impl<T> Encrust for T
where
T: InPlaceEncrust,
{
type Storage = Self;
type Ref = Self;
fn to_storage(self) -> Self::Storage {
self
}
unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
storage
}
unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
storage
}
fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
<T as InPlaceEncrust>::toggle_encrust(storage, encrust_rng);
}
}
macro_rules! encrust_int {
( $( $t:ty ),* ) => {
$(
impl InPlaceEncrust for $t {
fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng) {
let mut bytes = self.to_le_bytes();
for chunk in bytes.chunks_mut(8) {
let key = encrust_rng.next_u64().to_le_bytes();
for (byte, byte_key) in chunk.iter_mut().zip(key.iter()) {
*byte ^= byte_key;
}
}
*self = Self::from_le_bytes(bytes);
}
}
)*
};
}
encrust_int!(
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
);
impl<T, const N: usize> InPlaceEncrust for [T; N]
where
T: InPlaceEncrust + 'static,
{
fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng) {
slice_toggle_encrust::<T>(self, encrust_rng);
}
}
impl Encrust for String {
type Storage = Vec<u8>;
type Ref = str;
fn to_storage(self) -> Self::Storage {
self.into_bytes()
}
unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
unsafe { str::from_utf8_unchecked(storage) }
}
unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
unsafe { str::from_utf8_unchecked_mut(storage) }
}
fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
slice_toggle_encrust::<u8>(storage, encrust_rng);
}
}
impl Encrust for CString {
type Storage = Vec<u8>;
type Ref = [u8];
fn to_storage(self) -> Self::Storage {
self.into_bytes_with_nul()
}
unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
storage.as_ref()
}
unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
storage.as_mut()
}
fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
slice_toggle_encrust::<u8>(storage, encrust_rng);
}
}
impl<T> Encrust for Vec<T>
where
T: InPlaceEncrust + 'static,
{
type Storage = Self;
type Ref = [T];
fn to_storage(self) -> Self::Storage {
self
}
unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
storage.as_ref()
}
unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
storage.as_mut()
}
fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
slice_toggle_encrust::<T>(storage, encrust_rng);
}
}
macro_rules! toggle_encrust_for_integer {
($ty:ty, $type_id:ident, $slice:ident, $rng:ident) => {{
const ARRAY_SIZE: usize = core::mem::size_of::<u64>() / core::mem::size_of::<$ty>();
assert!($type_id == TypeId::of::<$ty>());
assert!(core::mem::size_of::<u64>() >= core::mem::size_of::<[$ty; ARRAY_SIZE]>());
let slice = slice::from_raw_parts_mut($slice.as_mut_ptr().cast::<$ty>(), $slice.len());
for chunk in slice.chunks_mut(ARRAY_SIZE) {
let key = $rng.next_u64().to_le_bytes();
let key: [$ty; ARRAY_SIZE] = core::array::from_fn(|i| {
let start = i * core::mem::size_of::<$ty>();
<$ty>::from_le_bytes(
key[start..start + core::mem::size_of::<$ty>()]
.try_into()
.unwrap(),
)
});
for (num, key) in chunk.iter_mut().zip(key.iter()) {
*num ^= key;
}
}
}};
}
fn slice_toggle_encrust<T>(encrust_slice: &mut [T::Storage], encrust_rng: &mut impl Rng)
where
T: Encrust,
T::Storage: 'static,
{
let type_id: TypeId = TypeId::of::<T::Storage>();
if type_id == TypeId::of::<u8>() {
let encrust_slice = unsafe {
slice::from_raw_parts_mut(encrust_slice.as_mut_ptr().cast::<u8>(), encrust_slice.len())
};
u8_slice_toggle_encrust(encrust_slice, encrust_rng);
} else if type_id == TypeId::of::<i8>() {
unsafe {
toggle_encrust_for_integer!(i8, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<i16>() {
unsafe {
toggle_encrust_for_integer!(i16, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<i32>() {
unsafe {
toggle_encrust_for_integer!(i32, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<i64>() {
unsafe {
toggle_encrust_for_integer!(i64, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<isize>() {
unsafe {
toggle_encrust_for_integer!(isize, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<i128>() {
let encrust_slice = unsafe {
slice::from_raw_parts_mut(
encrust_slice.as_mut_ptr().cast::<i128>(),
encrust_slice.len(),
)
};
let mut key = [0u8; 16];
for num in encrust_slice {
encrust_rng.fill_bytes(&mut key);
*num ^= i128::from_le_bytes(key);
}
} else if type_id == TypeId::of::<u16>() {
unsafe {
toggle_encrust_for_integer!(u16, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<u32>() {
unsafe {
toggle_encrust_for_integer!(u32, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<u64>() {
unsafe {
toggle_encrust_for_integer!(u64, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<usize>() {
unsafe {
toggle_encrust_for_integer!(usize, type_id, encrust_slice, encrust_rng);
}
} else if type_id == TypeId::of::<u128>() {
let encrust_slice = unsafe {
slice::from_raw_parts_mut(
encrust_slice.as_mut_ptr().cast::<u128>(),
encrust_slice.len(),
)
};
let mut key = [0u8; 16];
for num in encrust_slice {
encrust_rng.fill_bytes(&mut key);
*num ^= u128::from_le_bytes(key);
}
} else {
for element in encrust_slice {
<T as Encrust>::toggle_encrust(element, encrust_rng);
}
}
}
fn u8_slice_toggle_encrust(encrust_slice: &mut [u8], encrust_rng: &mut impl Rng) {
let mut shuffle_indices: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
for chunk in encrust_slice.chunks_mut(16) {
let mut key = encrust_rng.next_u64().to_le_bytes();
for byte in &mut key {
*byte &= 0b00_01_10_01;
}
let (n_shuffle_indices, is_odd) = if chunk.len() < 16 {
let (shuffle_count, is_odd) = if (chunk.len() % 2) == 0 {
(chunk.len(), false)
} else {
(chunk.len() - 1, true)
};
for (i, elem) in shuffle_indices.iter_mut().enumerate().take(shuffle_count) {
*elem = u8::try_from(i).unwrap();
}
shuffle_indices[0..shuffle_count].shuffle(encrust_rng);
(shuffle_count, is_odd)
} else {
shuffle_indices.shuffle(encrust_rng);
(chunk.len(), false)
};
for (pair, key) in shuffle_indices[..n_shuffle_indices].chunks(2).zip(key) {
let i = usize::from(pair[0]);
let j = usize::from(pair[1]);
chunk[i] = chunk[i].wrapping_add(key);
chunk[j] = chunk[j].wrapping_sub(key);
chunk.swap(i, j);
}
if is_odd {
*chunk.last_mut().unwrap() ^= key.last().unwrap();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_STRING: &str = "The quick brown fox jumps over the lazy dog😊";
const SEED: u64 = 0x2357_bd11_1317_1d1f;
macro_rules! test_ints {
( $( $t:ty => [$($value:expr),+ $(,)?] ),* $(,)? ) => {
$(
$(
{
let mut encrusted = Encrusted::<$t>::new($value, SEED);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, $value);
}
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, $value);
}
}
)+
#[cfg(feature = "macros")]
{
$(
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
let mut encrusted_data: $t = $value;
encrusted_data.toggle_encrust(&mut encrust_rng);
let mut encrusted = unsafe {
Encrusted::<$t>::from_encrusted_data(encrusted_data, SEED)
};
assert_ne!(encrusted.data, $value);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, $value);
}
assert_ne!(encrusted.data, $value);
)+
}
)*
};
}
macro_rules! test_int_arrays {
( $( $t:ty ),* ) => {
$(
let zero_array: [$t; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
{
let mut encrusted = Encrusted::<[$t; 9]>::new(zero_array, SEED);
assert_ne!(encrusted.data, zero_array);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, zero_array);
}
assert_ne!(encrusted.data, zero_array);
}
#[cfg(feature = "macros")]
{
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
let mut encrusted_data: [$t; 9] = zero_array;
encrusted_data.toggle_encrust(&mut encrust_rng);
let mut encrusted = unsafe {
Encrusted::<[$t; 9]>::from_encrusted_data(encrusted_data, SEED)
};
assert_ne!(encrusted.data, zero_array);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, zero_array);
}
assert_ne!(encrusted.data, zero_array);
}
)*
};
}
#[test]
fn test_ints() {
test_ints!(
u8 => [0u8, 1u8, u8::MAX],
i8 => [0i8, 1i8, -1i8, i8::MIN, i8::MAX],
u16 => [0u16, 1u16, u16::MAX],
i16 => [0i16, 1i16, -1i16, i16::MIN, i16::MAX],
u32 => [0u32, 1u32, u32::MAX],
i32 => [0i32, 1i32, -1i32, i32::MIN, i32::MAX],
u64 => [0u64, 1u64, u64::MAX],
i64 => [0i64, 1i64, -1i64, i64::MIN, i64::MAX],
u128 => [0u128, 1u128, u128::MAX],
i128 => [0i128, 1i128, -1i128, i128::MIN, i128::MAX],
usize => [0usize, 1usize, usize::MAX],
isize => [0isize, 1isize, -1isize, isize::MIN, isize::MAX],
);
}
#[test]
fn test_int_arrays() {
test_int_arrays!(
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
);
}
#[test]
fn test_strings() {
let mut encrusted = Encrusted::new(TEST_STRING.to_owned(), SEED);
assert_ne!(encrusted.data, TEST_STRING.as_bytes());
{
let decrusted = encrusted.decrust();
assert_eq!(&*decrusted, TEST_STRING);
}
assert_ne!(encrusted.data, TEST_STRING.as_bytes());
}
#[test]
fn test_string_lengths_around_u8_chunk_boundaries() {
for len in [0, 1, 2, 7, 8, 9, 15, 16, 17, 45] {
let string = "a".repeat(len);
let mut encrusted = Encrusted::new(string.clone(), SEED);
{
let decrusted = encrusted.decrust();
assert_eq!(&*decrusted, string);
}
{
let decrusted = encrusted.decrust();
assert_eq!(&*decrusted, string);
}
}
}
#[cfg(feature = "macros")]
#[test]
fn test_strings_from_encrusted() {
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
let mut encrusted_string = TEST_STRING.to_owned().into_bytes();
let mut encrusted = unsafe {
<String as Encrust>::toggle_encrust(&mut encrusted_string, &mut encrust_rng);
Encrusted::<String>::from_encrusted_data(encrusted_string, SEED)
};
assert_ne!(encrusted.data, TEST_STRING.as_bytes());
{
let decrusted = encrusted.decrust();
assert_eq!(&*decrusted, TEST_STRING);
}
assert_ne!(encrusted.data, TEST_STRING.as_bytes());
}
#[test]
fn test_arrays() {
let orig_array: [u8; 45] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
];
let mut encrusted = Encrusted::new(orig_array, SEED);
assert_ne!(encrusted.data, orig_array);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, orig_array);
}
assert_ne!(encrusted.data, orig_array);
}
#[test]
fn test_arrays_from_encrusted() {
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
let orig_array: [u8; 45] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
];
let mut encrusted_array = orig_array;
let mut encrusted = unsafe {
<[u8; 45] as Encrust>::toggle_encrust(&mut encrusted_array, &mut encrust_rng);
Encrusted::<[u8; 45]>::from_encrusted_data(encrusted_array, SEED)
};
assert_ne!(encrusted.data, orig_array);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, orig_array);
}
assert_ne!(encrusted.data, orig_array);
}
#[test]
fn test_vecs() {
let orig_vec = TEST_STRING.as_bytes().to_vec();
let mut encrusted = Encrusted::new(orig_vec.clone(), SEED);
assert_ne!(encrusted.data, orig_vec);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, orig_vec);
}
assert_ne!(encrusted.data, orig_vec);
}
#[cfg(feature = "macros")]
#[test]
fn test_vecs_from_encrusted() {
let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
let orig_vec = TEST_STRING.as_bytes().to_vec();
let mut encrusted_vec = orig_vec.clone();
let mut encrusted = unsafe {
<Vec<u8> as Encrust>::toggle_encrust(&mut encrusted_vec, &mut encrust_rng);
Encrusted::<Vec<u8>>::from_encrusted_data(encrusted_vec, SEED)
};
assert_ne!(encrusted.data, orig_vec);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, orig_vec);
}
assert_ne!(encrusted.data, orig_vec);
}
#[test]
fn test_reseed() {
let num = 828_627_825_u64;
let mut encrusted = Encrusted::new(num, SEED);
let new_seed = SEED ^ 0xffff_ffff_ffff_ffff;
encrusted.reseed(new_seed);
{
let decrusted = encrusted.decrust();
assert_eq!(*decrusted, num);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Zeroize)]
struct CustomInPlaceEncrust(u32);
impl InPlaceEncrust for CustomInPlaceEncrust {
fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng) {
self.0 ^= encrust_rng.next_u32();
}
}
#[test]
fn vec_of_custom_type_uses_fallback_slice_path() {
let values = vec![
CustomInPlaceEncrust(0),
CustomInPlaceEncrust(1),
CustomInPlaceEncrust(u32::MAX),
CustomInPlaceEncrust(0xfeed_beef),
];
let values_clone = values.clone();
let mut encrusted = Encrusted::new(values, SEED);
assert_ne!(encrusted.data, values_clone);
let decrusted = encrusted.decrust();
assert_eq!(&*decrusted, &values_clone);
}
#[cfg(feature = "macros")]
#[test]
fn ensure_encrust_has_not_changed() {
let mut test_string = unsafe {
Encrusted::<String>::from_encrusted_data(
vec![
114u8, 87u8, 102u8, 107u8, 117u8, 113u8, 32u8, 110u8, 32u8, 97u8, 33u8, 84u8,
128u8, 118u8, 99u8, 105u8, 112u8, 92u8, 110u8, 106u8, 32u8, 120u8, 128u8,
109u8, 142u8, 87u8, 56u8, 91u8, 124u8, 16u8, 130u8, 93u8, 119u8, 84u8, 24u8,
125u8, 91u8, 121u8, 122u8, 40u8, 106u8, 96u8, 161u8, 175u8, 224u8, 94u8, 146u8,
],
#[allow(
clippy::unreadable_literal,
reason = "Arbitrary number chosen at random with no further meaning."
)]
5233902475398815152u64,
)
};
let decrusted_test_string = test_string.decrust();
assert_eq!(decrusted_test_string.as_bytes(), TEST_STRING.as_bytes());
}
}