Skip to content

Commit b8c206d

Browse files
committed
Add encrypt/decrypt files, refactoring
1 parent 1a1a071 commit b8c206d

File tree

10 files changed

+612
-171
lines changed

10 files changed

+612
-171
lines changed

Cargo.lock

Lines changed: 418 additions & 88 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rust_crypt"
3-
version = "0.1.0"
3+
version = "1.0.0"
44
authors = ["Abdellatif EL MIZEB <[email protected]>"]
55
edition = "2021"
66
include = ["LICENSE", "**/*.rs", "Cargo.toml"]
@@ -25,6 +25,7 @@ log = "0.4.27"
2525
# You only need serde if you want app persistence:
2626
serde = { version = "1.0.219", features = ["derive"] }
2727
chrono = "0.4.41"
28+
rfd = "0.15.3"
2829

2930
# native:
3031
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

src/app.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
use eframe::egui;
22

33
mod views;
4+
mod encryption;
45

5-
#[derive(serde::Deserialize, serde::Serialize, PartialEq)]
6-
pub enum EncryptionMethod {
7-
Caesar,
8-
XOR,
9-
}
6+
use encryption::{EncryptionMethod};
107

118
use views::{
129
decrypt_file::decrypt_file_view,
@@ -134,8 +131,8 @@ impl eframe::App for TemplateApp {
134131
match self.current_view {
135132
CurrentView::EncryptText => encrypt_text_view(ui, self),
136133
CurrentView::DecryptText => decrypt_text_view(ui, self),
137-
CurrentView::EncryptFile => encrypt_file_view(ui),
138-
CurrentView::DecryptFile => decrypt_file_view(ui),
134+
CurrentView::EncryptFile => encrypt_file_view(ui, self),
135+
CurrentView::DecryptFile => decrypt_file_view(ui, self),
139136
CurrentView::About => about_view(ui),
140137
CurrentView::Settings => settings_view(ui),
141138
}

src/app/encryption/caesar.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
pub fn caesar_decrypt(text: &str, key: &str) -> String {
2+
let shift = key.len() as u8 % 26;
3+
text.chars()
4+
.map(|c| {
5+
if c.is_ascii_lowercase() {
6+
(((26 + c as u8 - b'a' - shift) % 26) + b'a') as char
7+
} else if c.is_ascii_uppercase() {
8+
(((26 + c as u8 - b'A' - shift) % 26) + b'A') as char
9+
} else {
10+
c
11+
}
12+
})
13+
.collect()
14+
}
15+
16+
pub fn caesar_encrypt(text: &str, key: &str) -> String {
17+
let shift = key.len() as u8 % 26;
18+
text.chars()
19+
.map(|c| {
20+
if c.is_ascii_lowercase() {
21+
(((c as u8 - b'a' + shift) % 26) + b'a') as char
22+
} else if c.is_ascii_uppercase() {
23+
(((c as u8 - b'A' + shift) % 26) + b'A') as char
24+
} else {
25+
c
26+
}
27+
})
28+
.collect()
29+
}
30+
31+
pub fn caesar_encrypt_file(input: &str, key: &str) -> String {
32+
caesar_encrypt(input, key)
33+
}

src/app/encryption/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pub mod caesar;
2+
pub mod xor;
3+
4+
pub use caesar::{caesar_encrypt, caesar_decrypt, caesar_encrypt_file};
5+
pub use xor::{xor_encrypt, xor_decrypt, xor_encrypt_file};
6+
7+
use serde::{Deserialize, Serialize};
8+
9+
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
10+
pub enum EncryptionMethod {
11+
Caesar,
12+
XOR,
13+
}

src/app/encryption/xor.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
pub fn xor_decrypt(hex: &str, key: &str) -> String {
2+
if key.is_empty() {
3+
return "Error: key is empty".to_owned();
4+
}
5+
6+
let key_bytes = key.as_bytes();
7+
let bytes = (0..hex.len())
8+
.step_by(2)
9+
.filter_map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok())
10+
.collect::<Vec<u8>>();
11+
12+
bytes
13+
.iter()
14+
.enumerate()
15+
.map(|(i, &b)| b ^ key_bytes[i % key_bytes.len()])
16+
.map(|b| b as char)
17+
.collect()
18+
}
19+
20+
pub fn xor_encrypt(text: &str, key: &str) -> String {
21+
if key.is_empty() {
22+
return "Error: key is empty".to_owned();
23+
}
24+
let key_bytes = key.as_bytes();
25+
text.as_bytes()
26+
.iter()
27+
.enumerate()
28+
.map(|(i, &b)| b ^ key_bytes[i % key_bytes.len()])
29+
.map(|b| format!("{:02x}", b))
30+
.collect()
31+
}
32+
33+
pub fn xor_encrypt_file(input: &str, key: &str) -> String {
34+
xor_encrypt(input, key)
35+
}

src/app/views/decrypt_file.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,54 @@
1-
use egui::Ui;
1+
use crate::app::{encryption, TemplateApp};
2+
use egui::{Ui, ComboBox};
3+
use rfd::FileDialog;
4+
use std::fs;
5+
use encryption::{EncryptionMethod, caesar_decrypt, xor_decrypt};
26

3-
pub fn decrypt_file_view(ui: &mut Ui) {
7+
pub fn decrypt_file_view(ui: &mut Ui, app: &mut TemplateApp) {
48
ui.label("📂 Decrypt File");
5-
ui.label("Feature coming soon: allow user to select a file, then decrypt it.");
9+
10+
ui.horizontal(|ui| {
11+
ui.label("Encryption Key:");
12+
ui.text_edit_singleline(&mut app.encryption_key);
13+
});
14+
15+
ui.horizontal(|ui| {
16+
ui.label("Encryption Method");
17+
ComboBox::from_id_salt("file_decryption_method_select")
18+
.selected_text(match app.selected_encryption {
19+
EncryptionMethod::Caesar => "Caesar Cipher",
20+
EncryptionMethod::XOR => "XOR Cipher",
21+
})
22+
.show_ui(ui, |ui| {
23+
ui.selectable_value(&mut app.selected_encryption, EncryptionMethod::Caesar, "Caesar Cipher");
24+
ui.selectable_value(&mut app.selected_encryption, EncryptionMethod::XOR, "XOR Cipher");
25+
});
26+
});
27+
28+
if ui.button("Select File to Decrypt").clicked() {
29+
if let Some(path) = FileDialog::new().pick_file() {
30+
if let Ok(data) = fs::read_to_string(&path) {
31+
let decrypted = match app.selected_encryption {
32+
EncryptionMethod::Caesar => caesar_decrypt(&data, &app.encryption_key),
33+
EncryptionMethod::XOR => xor_decrypt(&data, &app.encryption_key),
34+
};
35+
36+
let output_path = path.with_file_name(format!(
37+
"decrypted_{}",
38+
path.file_name().unwrap().to_string_lossy()
39+
));
40+
41+
if let Err(err) = fs::write(&output_path, decrypted) {
42+
app.output_text = format!("Failed to write output: {}", err);
43+
} else {
44+
app.output_text = format!("Decryption successful. Saved to:\n{}", output_path.display());
45+
}
46+
} else {
47+
app.output_text = "Failed to read the selected file.".to_owned();
48+
}
49+
}
50+
}
51+
52+
ui.label("Status:");
53+
ui.text_edit_multiline(&mut app.output_text);
654
}

src/app/views/decrypt_text.rs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use crate::app::{TemplateApp, EncryptionMethod};
1+
use crate::app::{encryption, TemplateApp};
22
use egui::{Ui, ComboBox};
3+
use encryption::{EncryptionMethod, caesar_decrypt, xor_decrypt};
34

45
pub fn decrypt_text_view(ui: &mut Ui, app: &mut TemplateApp) {
56
ui.label("🔓 Decrypt Text");
@@ -35,39 +36,3 @@ pub fn decrypt_text_view(ui: &mut Ui, app: &mut TemplateApp) {
3536
ui.label("Output:");
3637
ui.text_edit_multiline(&mut app.output_text);
3738
}
38-
39-
// Simple Caesar cipher decryption (reverse of encryption)
40-
fn caesar_decrypt(text: &str, key: &str) -> String {
41-
let shift = key.len() as u8 % 26;
42-
text.chars()
43-
.map(|c| {
44-
if c.is_ascii_lowercase() {
45-
(((26 + c as u8 - b'a' - shift) % 26) + b'a') as char
46-
} else if c.is_ascii_uppercase() {
47-
(((26 + c as u8 - b'A' - shift) % 26) + b'A') as char
48-
} else {
49-
c
50-
}
51-
})
52-
.collect()
53-
}
54-
55-
// XOR cipher decryption from hex input
56-
fn xor_decrypt(hex: &str, key: &str) -> String {
57-
if key.is_empty() {
58-
return "Error: key is empty".to_owned();
59-
}
60-
61-
let key_bytes = key.as_bytes();
62-
let bytes = (0..hex.len())
63-
.step_by(2)
64-
.filter_map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok())
65-
.collect::<Vec<u8>>();
66-
67-
bytes
68-
.iter()
69-
.enumerate()
70-
.map(|(i, &b)| b ^ key_bytes[i % key_bytes.len()])
71-
.map(|b| b as char)
72-
.collect()
73-
}

src/app/views/encrypt_file.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,54 @@
1-
use egui::Ui;
1+
use crate::app::{encryption, TemplateApp};
2+
use egui::{Ui, ComboBox};
3+
use rfd::FileDialog;
4+
use std::fs;
5+
use encryption::{EncryptionMethod, caesar_encrypt_file, xor_encrypt_file};
26

3-
pub fn encrypt_file_view(ui: &mut Ui) {
7+
pub fn encrypt_file_view(ui: &mut Ui, app: &mut TemplateApp) {
48
ui.label("📁 Encrypt File");
5-
ui.label("Feature coming soon: allow user to select a file, then encrypt it.");
9+
10+
ui.horizontal(|ui| {
11+
ui.label("Encryption Key:");
12+
ui.text_edit_singleline(&mut app.encryption_key);
13+
});
14+
15+
ui.horizontal(|ui| {
16+
ui.label("Encryption Method");
17+
ComboBox::from_id_salt("file_encryption_method_select")
18+
.selected_text(match app.selected_encryption {
19+
EncryptionMethod::Caesar => "Caesar Cipher",
20+
EncryptionMethod::XOR => "XOR Cipher",
21+
})
22+
.show_ui(ui, |ui| {
23+
ui.selectable_value(&mut app.selected_encryption, EncryptionMethod::Caesar, "Caesar Cipher");
24+
ui.selectable_value(&mut app.selected_encryption, EncryptionMethod::XOR, "XOR Cipher");
25+
});
26+
});
27+
28+
if ui.button("Select File to Encrypt").clicked() {
29+
if let Some(path) = FileDialog::new().pick_file() {
30+
if let Ok(data) = fs::read_to_string(&path) {
31+
let encrypted = match app.selected_encryption {
32+
EncryptionMethod::Caesar => caesar_encrypt_file(&data, &app.encryption_key),
33+
EncryptionMethod::XOR => xor_encrypt_file(&data, &app.encryption_key),
34+
};
35+
36+
let output_path = path.with_file_name(format!(
37+
"encrypted_{}",
38+
path.file_name().unwrap().to_string_lossy()
39+
));
40+
41+
if let Err(err) = fs::write(&output_path, encrypted) {
42+
app.output_text = format!("Failed to write output: {}", err);
43+
} else {
44+
app.output_text = format!("Encryption successful. Saved to:\n{}", output_path.display());
45+
}
46+
} else {
47+
app.output_text = "Failed to read the selected file.".to_owned();
48+
}
49+
}
50+
}
51+
52+
ui.label("Status:");
53+
ui.text_edit_multiline(&mut app.output_text);
654
}

src/app/views/encrypt_text.rs

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use crate::app::{TemplateApp, EncryptionMethod};
1+
use crate::app::{encryption, TemplateApp};
22
use egui::{Ui, ComboBox};
3+
use encryption::{EncryptionMethod, caesar_encrypt, xor_encrypt};
34

45
pub fn encrypt_text_view(ui: &mut Ui, app: &mut TemplateApp) {
56
ui.label("🔒 Encrypt Text");
@@ -33,34 +34,4 @@ pub fn encrypt_text_view(ui: &mut Ui, app: &mut TemplateApp) {
3334

3435
ui.label("Output:");
3536
ui.text_edit_multiline(&mut app.output_text);
36-
}
37-
38-
// Simple Caesar cipher encryption shifting letters by key length (mod 26)
39-
fn caesar_encrypt(text: &str, key: &str) -> String {
40-
let shift = key.len() as u8 % 26;
41-
text.chars()
42-
.map(|c| {
43-
if c.is_ascii_lowercase() {
44-
(((c as u8 - b'a' + shift) % 26) + b'a') as char
45-
} else if c.is_ascii_uppercase() {
46-
(((c as u8 - b'A' + shift) % 26) + b'A') as char
47-
} else {
48-
c
49-
}
50-
})
51-
.collect()
52-
}
53-
54-
// XOR cipher encryption with repeating key bytes (output as hex string)
55-
fn xor_encrypt(text: &str, key: &str) -> String {
56-
if key.is_empty() {
57-
return "Error: key is empty".to_owned();
58-
}
59-
let key_bytes = key.as_bytes();
60-
text.as_bytes()
61-
.iter()
62-
.enumerate()
63-
.map(|(i, &b)| b ^ key_bytes[i % key_bytes.len()])
64-
.map(|b| format!("{:02x}", b))
65-
.collect()
66-
}
37+
}

0 commit comments

Comments
 (0)