initial push

This commit is contained in:
root 2024-07-16 17:40:41 -06:00
commit bb3877633a
24 changed files with 1562 additions and 0 deletions

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "encrypted_images"
version = "1.3.0"
edition = "2021"
description = "Encrypt Text to Images and decrypt text from images."
authors = ["Bruce Bates | https://encryptedimages.art"]
license = "MIT"
[dependencies]
openssl = { version = "0.10.57", features = ["vendored"] }
base64 = "0.21.4"
image = { version = "0.23", features = ["png"] }
native-dialog = "0.6.4"
hmac = "0.12.1"
sha2 = "0.10.7"
hex-literal = "0.4.1"
encoding = "0.2"
subtle = "2.4"
rand = "0.8.5"

350
README.md Normal file

File diff suppressed because one or more lines are too long

7
SUPPORT Normal file
View File

@ -0,0 +1,7 @@
Support this developers open source efforts:
bitcoin: bc1pf0s98gva7la6r9srepywwr4llqea8t0r4fj8qkdqqac0f9y5624sxpsmqq
ethereum: 0x78046716a783B94240ff6b4Ba6580F6D5690423A
cardano: addr1q8dxlqu824ghzn3extm8zjqzmr2u5fy4p0jpaqs6w8jpt4wkpgtm6wkc0me3gmgfgdadnpzdcj24yz3znme5w7kvm8vs0thea2
solana: HG1fpdqHkxSUyT85V6LGu8dbC3BK4JixiivjYLHVsazQ
tezos: tz2TL7ke3xyxWi4JbS6egscmpEP9Z9XrV86y

View File

@ -0,0 +1,15 @@
use encrypted_images::encryption::images::create_img;
fn main() {
let ciphertext = "VkdocGN5QkpjeUJRYkE9PZNY2MOW01NWpSxCtFG6acHuAWun+CElPQ/IIwd0gy+D+IiBqB/5+qo8Jr9bMBOwoih3amCtjXlkAlRKHX5fhqI=";
let style = "h";
let watermark = "bitcoin";
let r = Some(100);
let g = Some(134);
let b = Some(137);
if let Some(encoded_image) = create_img(ciphertext, style, watermark, r, g, b, None, None, None) {
println!("Encoded image: {}", encoded_image);
} else {
println!("Image creation or encoding failed.");
}
}

View File

@ -0,0 +1,15 @@
use encrypted_images::encryption::images::create_img;
fn main() {
let ciphertext = "VkdocGN5QkpjeUJRYkE9PZNY2MOW01NWpSxCtFG6acHuAWun+CElPQ/IIwd0gy+D+IiBqB/5+qo8Jr9bMBOwoih3amCtjXlkAlRKHX5fhqI=";
let style = "h";
let watermark = "bitcoin";
let r = Some(46);
let g = Some(115);
let b = Some(82);
if let Some(encoded_image) = create_img(ciphertext, style, watermark, r, g, b, None, None, None) {
println!("Encoded image: {}", encoded_image);
} else {
println!("Image creation or encoding failed.");
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
use encrypted_images::encryption::images::create_img;
fn main() {
let ciphertext = "VkdocGN5QkpjeUJRYkE9PZNY2MOW01NWpSxCtFG6acHuAWun+CElPQ/IIwd0gy+D+IiBqB/5+qo8Jr9bMBOwoih3amCtjXlkAlRKHX5fhqI=";
let style = "h2";
let watermark = "bitcoin";
let r = Some(100);
let g = Some(134);
let b = Some(137);
if let Some(encoded_image) = create_img(ciphertext, style, watermark, r, g, b, None, None, None) {
println!("Encoded image: {}", encoded_image);
} else {
println!("Image creation or encoding failed.");
}
}

11
examples/decode_image.rs Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
use encrypted_images::decryption::text::decrypts;
fn main() {
let encrypted_text = "VkdocGN5QkpjeUJRYkE9PZNY2MOW01NWpSxCtFG6acHuAWun+CElPQ/IIwd0gy+D+IiBqB/5+qo8Jr9bMBOwoih3amCtjXlkAlRKHX5fhqI=";
if let Some(decrypted_text) = decrypts(encrypted_text, None) {
println!("Decrypted text: {}", decrypted_text);
} else {
println!("Decryption failed.");
}
}

View File

@ -0,0 +1,11 @@
use encrypted_images::decryption::text::decrypts;
fn main() {
let encrypted_text = "OWFNTGpvaGFMbWtTUkE9PcjB/klKI3ix+Z0uVuYbd3zRqaTjMgxotQu4hz1FRSfPWRQMOBhLSI6+KFPl8qldeCPoUYvezvVMOScWll9OzCA=";
let key = Some("16characterslong");
if let Some(decrypted_text) = decrypts(encrypted_text, key) {
println!("Decrypted text: {}", decrypted_text);
} else {
println!("Decryption failed.");
}
}

View File

@ -0,0 +1,16 @@
use encrypted_images::encryption::text::encrypts;
use encrypted_images::encryption::images::create_img;
fn main() {
let key = Some("your_secret_key");
let ciphertext = "This is a secret message.";
let style = "h";
let encrypted = encrypts(ciphertext, key.clone(), None).unwrap();
let watermark = "bitcoin";
let image_data = create_img(&encrypted, style, watermark, None, None, None, None, None, None);
match image_data {
Some(encoded_image) => println!("Encoded Image:\n{}", encoded_image),
None => println!("Failed to create the image."),
}
}

View File

@ -0,0 +1,7 @@
use encrypted_images::encryption::text::encrypts;
fn main() {
let plaintext = "This Is Plain Text";
let encrypted_text = encrypts(plaintext, None, None).unwrap();
println!("Encrypted text: {}", encrypted_text);
}

View File

@ -0,0 +1,9 @@
use encrypted_images::encryption::text::encrypts;
fn main() {
let plaintext = "This Is Plain Text";
let key = Some("16characterslong");
let strength = Some("advanced");
let encrypted_text = encrypts(plaintext, key, strength).unwrap();
println!("Encrypted text: {}", encrypted_text);
}

149
src/char_mappings/maps.rs Normal file
View File

@ -0,0 +1,149 @@
pub mod mappings {
use std::collections::HashMap;
pub(crate) fn get_color(char: char) -> Option<(u8, u8, u8)> {
match char {
'a' => Some((204, 180, 194)),
'A' => Some((255, 255, 255)),
'b' => Some((197, 186, 201)),
'B' => Some((221, 206, 212)),
'c' => Some((181, 185, 193)),
'C' => Some((184, 201, 223)),
'd' => Some((224, 218, 192)),
'D' => Some((185, 191, 195)),
'e' => Some((181, 197, 198)),
'E' => Some((193, 206, 255)),
'f' => Some((252, 193, 211)),
'F' => Some((183, 192, 229)),
'g' => Some((180, 191, 192)),
'G' => Some((187, 219, 189)),
'h' => Some((195, 187, 234)),
'H' => Some((182, 216, 189)),
'i' => Some((197, 183, 248)),
'I' => Some((200, 182, 204)),
'j' => Some((255, 235, 196)),
'J' => Some((194, 186, 228)),
'k' => Some((199, 238, 239)),
'K' => Some((208, 247, 234)),
'l' => Some((244, 214, 189)),
'L' => Some((187, 243, 239)),
'm' => Some((188, 231, 238)),
'M' => Some((187, 197, 227)),
'n' => Some((186, 240, 191)),
'N' => Some((187, 198, 206)),
'o' => Some((205, 193, 184)),
'O' => Some((191, 187, 197)),
'p' => Some((194, 200, 206)),
'P' => Some((195, 183, 229)),
'q' => Some((182, 219, 196)),
'Q' => Some((238, 216, 184)),
'r' => Some((199, 181, 208)),
'R' => Some((239, 231, 198)),
's' => Some((189, 188, 230)),
'S' => Some((242, 192, 230)),
't' => Some((199, 199, 199)),
'T' => Some((188, 190, 230)),
'u' => Some((230, 180, 253)),
'U' => Some((241, 247, 247)),
'v' => Some((242, 190, 199)),
'V' => Some((230, 247, 234)),
'w' => Some((197, 186, 249)),
'W' => Some((194, 247, 249)),
'x' => Some((242, 182, 246)),
'X' => Some((188, 222, 193)),
'y' => Some((188, 194, 183)),
'Y' => Some((197, 195, 197)),
'z' => Some((187, 249, 240)),
'Z' => Some((233, 231, 242)),
'0' => Some((195, 184, 218)),
'1' => Some((232, 180, 196)),
'2' => Some((191, 193, 196)),
'3' => Some((185, 186, 186)),
'4' => Some((191, 247, 180)),
'5' => Some((187, 199, 248)),
'6' => Some((248, 198, 184)),
'7' => Some((243, 195, 184)),
'8' => Some((232, 192, 208)),
'9' => Some((239, 197, 183)),
'/' => Some((199, 187, 241)),
'+' => Some((195, 216, 223)),
'=' => Some((193, 211, 184)),
_ => None,
}
}
pub(crate) fn numbers_to_letter(r: u8, g: u8, b: u8) -> Option<char> {
let color_map: HashMap<(u8, u8, u8), char> = [
((204, 180, 194), 'a'),
((255, 255, 255), 'A'),
((197, 186, 201), 'b'),
((221, 206, 212), 'B'),
((181, 185, 193), 'c'),
((184, 201, 223), 'C'),
((224, 218, 192), 'd'),
((185, 191, 195), 'D'),
((181, 197, 198), 'e'),
((193, 206, 255), 'E'),
((252, 193, 211), 'f'),
((183, 192, 229), 'F'),
((180, 191, 192), 'g'),
((187, 219, 189), 'G'),
((195, 187, 234), 'h'),
((182, 216, 189), 'H'),
((197, 183, 248), 'i'),
((200, 182, 204), 'I'),
((255, 235, 196), 'j'),
((194, 186, 228), 'J'),
((199, 238, 239), 'k'),
((208, 247, 234), 'K'),
((244, 214, 189), 'l'),
((187, 243, 239), 'L'),
((188, 231, 238), 'm'),
((187, 197, 227), 'M'),
((186, 240, 191), 'n'),
((187, 198, 206), 'N'),
((205, 193, 184), 'o'),
((191, 187, 197), 'O'),
((194, 200, 206), 'p'),
((195, 183, 229), 'P'),
((182, 219, 196), 'q'),
((238, 216, 184), 'Q'),
((199, 181, 208), 'r'),
((239, 231, 198), 'R'),
((189, 188, 230), 's'),
((242, 192, 230), 'S'),
((199, 199, 199), 't'),
((188, 190, 230), 'T'),
((230, 180, 253), 'u'),
((241, 247, 247), 'U'),
((242, 190, 199), 'v'),
((230, 247, 234), 'V'),
((197, 186, 249), 'w'),
((194, 247, 249), 'W'),
((242, 182, 246), 'x'),
((188, 222, 193), 'X'),
((188, 194, 183), 'y'),
((197, 195, 197), 'Y'),
((187, 249, 240), 'z'),
((233, 231, 242), 'Z'),
((195, 184, 218), '0'),
((232, 180, 196), '1'),
((191, 193, 196), '2'),
((185, 186, 186), '3'),
((191, 247, 180), '4'),
((187, 199, 248), '5'),
((248, 198, 184), '6'),
((243, 195, 184), '7'),
((232, 192, 208), '8'),
((239, 197, 183), '9'),
((199, 187, 241), '/'),
((195, 216, 223), '+'),
((193, 211, 184), '='),
]
.iter()
.cloned()
.collect();
match color_map.get(&(r, g, b)) {
Some(&letter) => Some(letter),
None => None,
}
}
}

1
src/char_mappings/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod maps;

100
src/decryption/images.rs Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
src/decryption/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod text;
pub mod images;

56
src/decryption/text.rs Normal file
View File

@ -0,0 +1,56 @@
/// Decrypts an encoded result using an optional decryption key.
///
/// This function takes an encoded result and an optional decryption key, and attempts to decrypt
/// the result using AES-128 CBC decryption. It also verifies the integrity of the data using HMAC.
///
/// Timing Attack Protection:
/// The decryption process is designed to protect against timing attacks, ensuring secure
/// operation. It is suitable for standard encryption needs. Please see notes inside the encrypts
/// function to understand additional security. Default encryption settings are not maximum security.
///
/// # Arguments
///
/// * `encoded_result` - The Base64-encoded result to be decrypted.
/// * `key` - An optional decryption key. If not provided, the default key "welovenfts" is used.
///
/// # Returns
///
/// An `Option<String>` containing the decrypted plaintext if successful, or `None` if decryption
/// fails or if the HMAC verification fails.
///
/// # Examples
///
/// ```
/// use encrypted_images::decryption::text::decrypts;
///
/// let encoded_result = "VkdocGMybHpiWGxqYnc9PbUWoPUFfy9Izm1wkCFZ8gSMWr6EUGW6UwYpnaounDkYmLNDjqWyvjcus2atCStKBOJSCnosjApRrcJrm44hatuaJHSYONbHNOmpk3Rja/xH";
/// let key = "welovenfts";
/// let decrypted_data = decrypts(encoded_result, Some(key));
/// assert!(decrypted_data.is_some());
/// ```
use subtle::ConstantTimeEq;
use openssl::symm::{decrypt, Cipher};
use crate::encryption::text::hmac::calculate_hmac;
use base64::{Engine as _, engine::{self, general_purpose}, alphabet};
const CUSTOM_ENGINE: engine::GeneralPurpose =
engine::GeneralPurpose::new(&alphabet::STANDARD, general_purpose::PAD);
pub fn decrypts(encoded_result: &str, key: Option<&str>) -> Option<String> {
let key = key.unwrap_or("welovenfts");
let mut padded_key = key.as_bytes().to_vec();
while padded_key.len() < 16 {
padded_key.push(b'\0');
}
let result_bytes = CUSTOM_ENGINE.decode(encoded_result).ok()?;
let iv = &result_bytes[..16];
let hmac = &result_bytes[16..48];
let ciphertext = &result_bytes[48..];
let hmac_calculated = calculate_hmac(ciphertext, &padded_key);
if hmac_calculated.ct_eq(hmac).unwrap_u8() == 1 {
let cipher = Cipher::aes_128_cbc();
let decrypted_data = decrypt(cipher, &padded_key, Some(iv), ciphertext).ok()?;
Some(String::from_utf8_lossy(&decrypted_data).to_string())
} else {
println!("Decryption Failed");
None
}
}

345
src/encryption/images.rs Normal file

File diff suppressed because one or more lines are too long

2
src/encryption/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod text;
pub mod images;

117
src/encryption/text.rs Normal file
View File

@ -0,0 +1,117 @@
/// Encrypts the input text with an optional encryption key with timing attack protections and
/// standard encryption security measures (which are optional).
///
/// The `input` parameter is the text to be encrypted.
///
/// The `key` parameter is an optional encryption key. If not provided, a default key is used.
///
/// The `strength` parameter is optional security level. Is set this value can be default or
/// advanced.
///
/// # Notes
///
/// - This function has been updated to include timing attack protections to enhance security.
/// - The function uses static IV (Initialization Vector) bytes by default to generate consistant
/// encrypted text output. While suitable for some situations ensure to use the advanced
/// strength for highly sensative data but note that everytime you encrypt with the advanced
/// method the output will look differnt.
/// - Again stressing that the default security option is not the ideal choice for scenarios requiring the
/// highest level of encryption security.
/// - Without advanced security there are potentials for comparison attacks that can occur this encryption.
/// - Only use standard settings for novelty usage.
///
/// # Examples
///
/// Encrypt a text with a custom key:
///
/// ```
/// use encrypted_images::encryption::text::encrypts;
///
/// let input = "ThisIsJustaTestString";
/// let key = "your_secret_key";
/// let encrypted = encrypts(input, Some(key), None);
///
/// assert!(encrypted.as_ref().unwrap().len() > 0);
/// ```
///
/// Encrypt a text without providing a custom key:
///
/// ```
/// use encrypted_images::encryption::text::encrypts;
///
/// let input = "ThisIsJustaTestString";
/// let encrypted = encrypts(input, None, None);
///
/// assert!(encrypted.as_ref().unwrap().len() > 0);
/// ```
///
/// Encrypt a text with advanced security:
///
/// ```
/// use encrypted_images::encryption::text::encrypts;
///
/// let input = "ThisIsJustaTestString";
/// let strength = "advanced";
/// let encrypted = encrypts(input, None, Some(strength));
///
/// assert!(encrypted.as_ref().unwrap().len() > 0);
/// ```
use rand::{Rng};
use rand::rngs::OsRng;
use openssl::symm::{encrypt, Cipher};
use crate::encryption::text::hmac::calculate_hmac;
use subtle::ConstantTimeEq;
use base64::{Engine as _, engine::{self, general_purpose}, alphabet};
const CUSTOM_ENGINE: engine::GeneralPurpose =
engine::GeneralPurpose::new(&alphabet::STANDARD, general_purpose::PAD);
pub fn encrypts(input: &str, key: Option<&str>, strength: Option<&str>) -> Option<String> {
let cipher = Cipher::aes_128_cbc();
let key = key.unwrap_or("welovenfts");
let strength = strength.unwrap_or("default");
let iv_bytes = &input.to_string()[..10];
let iv: String;
if strength == "default" {
iv = CUSTOM_ENGINE.encode(iv_bytes);
} else {
let num_bytes = 10; // Adjust this to the number of random bytes you need
let random_bytes = generate_random_bytes(num_bytes);
iv = CUSTOM_ENGINE.encode(random_bytes);
}
let mut padded_key = key.as_bytes().to_vec();
while padded_key.len() < 16 {
padded_key.push(b'\0');
}
padded_key.truncate(16);
let ciphertext = encrypt(cipher, &padded_key, Some(iv.as_bytes()), input.as_bytes()).unwrap();
let hmac = calculate_hmac(&ciphertext, &padded_key);
if hmac.ct_eq(&calculate_hmac(&ciphertext, &padded_key)).unwrap_u8() == 1 {
let mut result = iv.into_bytes();
result.extend_from_slice(&hmac);
result.extend_from_slice(&ciphertext);
let encoded_result = CUSTOM_ENGINE.encode(&result);
Some(encoded_result)
} else {
println!("Encryption HMAC validation failed");
None
}
}
pub mod hmac {
pub(crate) fn calculate_hmac(data: &[u8], key: &[u8]) -> Vec<u8> {
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;
let pkey = PKey::hmac(key).unwrap();
let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
signer.update(data).unwrap();
signer.sign_to_vec().unwrap()
}
}
fn generate_random_bytes(num_bytes: usize) -> Vec<u8> {
let mut rng = OsRng;
let mut random_bytes = vec![0u8; num_bytes];
rng.fill(&mut random_bytes[..]);
random_bytes
}

148
src/lib.rs Normal file
View File

@ -0,0 +1,148 @@
pub mod char_mappings;
pub mod encryption;
pub mod decryption;
#[cfg(test)]
mod tests {
use crate::char_mappings::maps::mappings::{get_color, numbers_to_letter};
use crate::encryption::text::encrypts;
use crate::decryption::text::decrypts;
use crate::encryption::images::create_img;
use crate::decryption::images::decode_image_and_extract_text;
use std::time::{Instant, Duration};
#[test]
fn test_char_conversion() {
// Define valid characters for testing
let valid_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";
for character in valid_characters.chars() {
// Get the color for the character
let color_result = get_color(character);
// Get the character back from the color
let character_result = numbers_to_letter(color_result.unwrap().0, color_result.unwrap().1, color_result.unwrap().2);
// Check if character_result matches the input character
assert_eq!(character_result.unwrap(), character);
}
// Test invalid characters
let invalid_characters = "!@#$%^&*()_";
for character in invalid_characters.chars() {
// Attempt to get a color for the invalid character
let color_result = get_color(character);
// Ensure the result is None for invalid characters
assert_eq!(color_result, None);
}
}
#[test]
fn test_encrypts_decrypts_with_key() {
// Input data to encrypt
let input = "ThisIsJustaTestString";
// Your encryption key (replace with the actual key you use)
let key = Some("your_secret_key");
// Encrypt the input
let encrypted = encrypts(input, key.clone(), None);
// Decrypt the encrypted data
let decrypted = decrypts(encrypted.as_ref().unwrap(), key);
// Assert that decryption matches the original input
assert_eq!(decrypted, Some(input.to_string()));
}
#[test]
fn test_encrypts_decrypts_without_key() {
// Input data to encrypt
let input = "ThisIsJustsTestString";
// Encrypt the input without a key
let encrypted = encrypts(input, None, None);
// Decrypt the encrypted data without a key
let decrypted = decrypts(encrypted.as_ref().unwrap(), None);
// Assert that decryption matches the original input
assert_eq!(decrypted, Some(input.to_string()));
}
#[test]
fn test_maximum_length() {
// Your encryption key
let key = Some("your_secret_key");
let mut length = 1844674406; // Start with an initial length
let max_length = 1844674407; // Set the maximum length
while length <= max_length {
let encrypted = encrypts(&"A".repeat(length), key.clone(), None);
let decrypted = decrypts(encrypted.as_ref().unwrap(), key.clone());
if decrypted.is_none() {
println!("Maximum size: {} characters", length);
break;
}
length += 1;
}
}
#[test]
fn test_decryption_timing() {
let encoded_result = "Your encrypted data"; // Replace with an actual encoded result
let key = Some("Your secret key"); // Replace with an actual key
// Measure the time for decryption
let start_time = Instant::now();
let _ = decrypts(encoded_result, key);
let end_time = Instant::now();
let decryption_time = end_time - start_time;
// Ensure that decryption time is within an acceptable range (e.g., less than 1 second)
assert!(decryption_time < Duration::from_secs(1));
}
#[test]
fn test_minimum_length() {
// minimum 10 characters required to encrypt
let input = "aaaaaaaaaa";
// Your encryption key
let key = Some("your_secret_key");
// Encrypt the input
let encrypted = encrypts(input, key.clone(), None);
// Decrypt the encrypted data
let decrypted = decrypts(encrypted.as_ref().unwrap(), key);
// Assert that decryption matches the original input
assert_eq!(decrypted, Some(input.to_string()));
}
#[test]
fn test_create_img_and_decode() {
let ciphertext = "ThisIsJustaTestString";
let watermark = "Bitcoin"; // Text watermark
let style = "h";
// Create the image
let encoded_image = create_img(ciphertext, style, watermark, None, None, None, None, None, None);
// Print out the encoded image for debugging
println!("Encoded Image: {:?}", encoded_image);
// Decode the image and extract text
let extracted_text = decode_image_and_extract_text(&encoded_image.unwrap());
// Print out the extracted text for debugging
println!("Extracted Text: {:?}", extracted_text);
// Check if extracted text matches the original ciphertext
assert_eq!(extracted_text, Some(ciphertext.to_string()));
}
#[test]
fn test_encrypts_decrypts() {
// Input data to encrypt
let input = "ThisIsJustaTestString";
// Your encryption key (replace with the actual key you use)
let key = Some("your_secret_key");
// Encrypt the input
let encrypted = encrypts(input, key.clone(), None);
// Print the encrypted string for testing
println!("Encrypted: {}", encrypted.as_ref().unwrap());
// Decrypt the encrypted data
let decrypted = decrypts(encrypted.as_ref().unwrap(), key);
// Assert that decryption matches the original input
assert_eq!(decrypted, Some(input.to_string()));
}
}