Version .3

Some changes to UI, file encryption, the pacman game and the readme.
This commit is contained in:
Tyler
2025-04-27 23:22:56 -10:00
committed by GitHub
parent 9e45c34365
commit 265dff3329
14 changed files with 492 additions and 228 deletions
+131 -42
View File
@@ -1,83 +1,172 @@
# PacCrypt WebApp # PacCrypt WebApp
**PacCrypt** is a web-based application designed to provide secure encoding, encryption, and password generation. It allows users to easily encrypt and decrypt text and files, with both basic and advanced encryption options. It also features a password generator and a simple Pac-Man game as an Easter egg! **PacCrypt** is a web-based platform that allows you to securely encrypt/decrypt text and files, generate passwords, and even enjoy a hidden Pac-Man game!
Built using Python (Flask), JavaScript, and AES-GCM encryption.
## Features Official Website: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev)
- **Basic and Advanced Encryption**: Choose between simple encryption (Caesar Cipher) or more secure AES-GCM encryption. ---
- **File Encryption/Decryption**: Encrypt or decrypt files with a password.
- **Password Generator**: Generate secure random passwords with customizable length and complexity.
- **Pac-Man Game**: A fun Easter egg! Play a Pac-Man game when you type "pacman" in the text area.
- **Copy to Clipboard**: Copy generated passwords or encrypted results with one click.
- **Responsive Design**: Fully responsive web design that works across different screen sizes.
## Installation ## ✨ Features
### Prerequisites - 🔒 **Basic and Advanced Encryption** (Text and Files)
- 🔑 **Password Generator**
- 🄹️ **Pac-Man Easter Egg** (Type `pacman` to unlock!)
- 📱 **Responsive Design** (Mobile Friendly)
-**One-Click Start Scripts** (Dev and Production modes)
- 🎨 **Modern Animated UI** (Dark Mode + Green Neon Theme)
---
## 👨‍💻 Installation
### 📋 Prerequisites
- **Python 3.7+** - **Python 3.7+**
- **Nginx** (for reverse proxy and SSL configuration for hosting) - **Flask 3+**
- **Cryptography 42+**
- **Waitress 2.1+**
- **Nginx** (Recommended for production)
Official PacCrypt website: paccrypt.unnaturalll.dev ---
### Steps to Set Up Locally: ### ⚡ Quick Setup
1. Clone the repository: 1. Clone the repository:
```bash
git clone https://github.com/TySP-Dev/PacCrypt.git git clone https://github.com/TySP-Dev/PacCrypt.git
cd paccrypt-webapp cd paccrypt-webapp-final
```
2. Create and activate a virtual environment: 2. Create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
3. Install the required Python dependencies: ```bash
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
```
3. Install required Python packages:
```bash
pip install -r requirements.txt pip install -r requirements.txt
```
4. Run the Flask app: 4. Start the app:
python app.py
5. Open http://127.0.0.1:5000 to access the app locally. **Windows**:
```bash
start_dev.bat # For Development
start_prod.bat # For Production
```
## Usage **Linux / Mac**:
```bash
chmod +x start_dev.sh start_prod.sh
./start_dev.sh # For Development
./start_prod.sh # For Production
```
### Encryption and Decryption 5. Access the app at:
[http://127.0.0.1:5000](http://127.0.0.1:5000)
#### For text encryption/decryption: ---
-- Select the encryption type (Basic or Advanced). ## 🚀 Usage Guide
-- Choose whether to Encrypt or Decrypt. ### 🔒 Text Encryption/Decryption
-- Enter text in the Input Text area. - Select **Encryption Type** (Basic or Advanced)
- Enter text
- Provide password (Advanced only)
- Choose **Encrypt** or **Decrypt**
- Click **Submit**
-- Enter a password (if using advanced encryption). ### 📁 File Encryption/Decryption
-- Click submit. - Select **Advanced** encryption
- Upload a file
- Provide password
- Choose **Encrypt** or **Decrypt**
- Click **Submit**
#### For file encryption/decryption: ### 🔑 Password Generator
-- Select encryption type **Advanced.** - Click **Generate** to create a secure password
- Click **Copy** to save it to clipboard
-- Choose whether to Encrypt or Decrypt. ### 🎮 Pac-Man Easter Egg
-- Upload a file. - Type **`pacman`** into the input box to unlock the hidden Pac-Man game!
-- Enter a password for encryption/decryption. ---
-- Click submit. ## 🛡️ Hosting with Nginx (optional)
### Password Generation: Recommended for secure public deployment.
Click the Generate button to create a random password, then use the Copy button to copy it to your clipboard. Example minimal Nginx config:
### Pac-Man Game (Easter Egg): ```nginx
server {
listen 80;
server_name yourdomain.com;
Type the word "pacman" in the input box to unlock the Pac-Man game! location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
### Contributing: > Tip: Set up SSL with Let's Encrypt for HTTPS security! 🔐
Feel free to open an issue or submit a pull request for improvements, bug fixes, or new features! ---
### License ## 📂 Project Structure
This project is open source and available under the MIT License. ```
paccrypt-webapp-final/
├── app.py
├── requirements.txt
├── templates/
│ ├── index.html
│ ├── 404.html
│ └── 403.html
│ └── 500.html
├── static/
│ ├── css/
│ │ └── styles.css
│ ├── js/
│ │ └── script.js
│ ├── img/
│ │ └── PacCrypt.png
│ └── audio/
│ └── chomp.mp3
├── start_dev.bat
├── start_prod.bat
├── start_dev.sh
├── start_prod.sh
├── README.md
```
---
## 🤝 Contributing
Contributions are welcome!
- Add new features
- Fix bugs
- Improve performance
- Expand the Pac-Man Easter Egg 🎮
---
## 📄 License
This project is licensed under the **MIT License**.
---
+31 -7
View File
@@ -1,15 +1,17 @@
## DEV DEV DEV
import os
from flask import Flask, render_template, request, jsonify from flask import Flask, render_template, request, jsonify
import html import html
import os
import base64 import base64
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.hashes import SHA256 from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from waitress import serve
app = Flask(__name__) app = Flask(__name__)
# Basic Encoder/Decoder # ====== Your App Code ======
ALPHABET = list('abcdefghijklmnopqrstuvwxyz') ALPHABET = list('abcdefghijklmnopqrstuvwxyz')
def simple_encode(text: str) -> str: def simple_encode(text: str) -> str:
@@ -24,7 +26,6 @@ def simple_decode(text: str) -> str:
for c in text.lower() for c in text.lower()
) )
# Advanced Encrypt/Decrypt using AES-GCM
def derive_key(password: str, salt: bytes) -> bytes: def derive_key(password: str, salt: bytes) -> bytes:
kdf = PBKDF2HMAC( kdf = PBKDF2HMAC(
algorithm=SHA256(), algorithm=SHA256(),
@@ -56,7 +57,6 @@ def advanced_decrypt(token_b64: str, password: str) -> str:
except Exception: except Exception:
return "[Error] Invalid password or corrupted data!" return "[Error] Invalid password or corrupted data!"
# Combined Route for Page & AJAX
@app.route("/", methods=["GET", "POST"]) @app.route("/", methods=["GET", "POST"])
def index(): def index():
if request.method == 'POST': if request.method == 'POST':
@@ -83,6 +83,30 @@ def index():
encryption_type="advanced" encryption_type="advanced"
) )
# ====== Smart Server Startup ======
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(e):
return render_template('500.html'), 500
@app.errorhandler(403)
def forbidden(e):
return render_template('403.html'), 403
if __name__ == "__main__": if __name__ == "__main__":
# Use Waitress to serve the app in production PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true"
serve(app, host="0.0.0.0", port=5000)
if PRODUCTION:
from waitress import serve
print("[INFO] Running in PRODUCTION mode with Waitress.")
serve(app, host="0.0.0.0", port=5000)
else:
print("[INFO] Running in DEVELOPMENT mode with Flask server.")
app.run(debug=True, host="0.0.0.0", port=5000)
## DEV DEV DEV
+6 -3
View File
@@ -1,5 +1,8 @@
### **requirements.txt** ### **requirements.txt**
Flask==2.1.2 flask==3.0.3
cryptography==3.4.8 cryptography==42.0.5
nginx==1.21.0 # Only needed for Nginx integration, not installed via pip waitress==2.1.2
# nginx - Only needed for Nginx integration, not installed via pip
# Run pip install -r requirements.txt
+5
View File
@@ -0,0 +1,5 @@
@echo off
echo Starting PacCrypt in DEVELOPMENT mode...
set PRODUCTION=false
python app.py
pause
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
echo "Starting PacCrypt in DEVELOPMENT mode..."
export PRODUCTION=false
python3 app.py
+5
View File
@@ -0,0 +1,5 @@
@echo off
echo Starting PacCrypt in PRODUCTION mode...
set PRODUCTION=true
python app.py
pause
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
echo "Starting PacCrypt in PRODUCTION mode..."
export PRODUCTION=true
python3 app.py
Binary file not shown.
+60 -47
View File
@@ -9,23 +9,25 @@
body { body {
font-family: 'Poppins', sans-serif; font-family: 'Poppins', sans-serif;
background-color: #121212; background-color: #121212;
color: #f0f0f0; color: #00ff99;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
justify-content: center; /* Vertically center content */ justify-content: center;
align-items: center; /* Horizontally center content */ align-items: center;
padding: 20px; padding: 20px;
} }
/* ===== Header ===== */ /* ===== Header ===== */
header { header {
text-align: center; text-align: center;
padding: 30px 20px; padding: 25px;
background-color: #1c1c1c; background-color: #1c1c1c;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5); border-radius: 12px;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
margin-bottom: 30px;
} }
header h1 { header h1 {
@@ -35,7 +37,7 @@ header {
} }
header p { header p {
font-size: 1.1em; font-size: 1.2em;
color: #00ff99; color: #00ff99;
} }
@@ -49,13 +51,12 @@ main {
max-width: 800px; max-width: 800px;
padding: 20px; padding: 20px;
gap: 30px; gap: 30px;
justify-content: center;
} }
/* ===== Section Card Styling ===== */ /* ===== Section Card Styling ===== */
.card { .card {
background-color: #1e1e1e; background-color: #1e1e1e;
padding: 20px 25px; padding: 25px;
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
border-radius: 12px; border-radius: 12px;
@@ -68,7 +69,7 @@ main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 15px; gap: 20px;
width: 100%; width: 100%;
} }
@@ -92,7 +93,8 @@ textarea {
resize: none; resize: none;
} }
input[type="password"] { input[type="password"],
#password {
min-height: 50px; min-height: 50px;
} }
@@ -123,7 +125,7 @@ select:focus {
box-shadow: 0 0 8px rgba(0, 255, 153, 0.8); box-shadow: 0 0 8px rgba(0, 255, 153, 0.8);
} }
/* ===== Match input and output textarea sizes ===== */ /* ===== Match input and output sizes ===== */
#input-text, #input-text,
#output-text { #output-text {
width: 80%; width: 80%;
@@ -138,8 +140,8 @@ select:focus {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 8px; gap: 15px;
margin-top: 8px; margin-top: 15px;
width: 100%; width: 100%;
} }
@@ -152,8 +154,8 @@ button {
font-size: 1em; font-size: 1em;
cursor: pointer; cursor: pointer;
transition: 0.3s; transition: 0.3s;
width: 100%; /* Makes buttons stretch to fill container */ width: 100%;
max-width: 200px; /* Restricts button width */ max-width: 200px;
} }
button:hover { button:hover {
@@ -161,7 +163,7 @@ button {
color: #121212; color: #121212;
} }
/* ===== Toggle Buttons ===== */ /* ===== Toggle Buttons (Encode/Decode, Encrypt/Decrypt) ===== */
.radio-group { .radio-group {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -174,13 +176,15 @@ button {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 8px 18px; padding: 1px 1px;
border: 2px solid #00ff99; border: 2px solid #00ff99;
border-radius: 8px; border-radius: 8px;
background-color: #2c2f33; background-color: #2c2f33;
color: #00ff99; color: #00ff99;
cursor: pointer; cursor: pointer;
transition: 0.3s; transition: 0.3s;
position: relative;
box-shadow: none;
} }
.radio-button:hover { .radio-button:hover {
@@ -188,21 +192,27 @@ button {
color: #121212; color: #121212;
} }
/* Hide the actual radio input */
.radio-button input { .radio-button input {
display: none; display: none;
} }
/* When selected, make the ENTIRE BUTTON glow */
.radio-button input:checked + span { .radio-button input:checked + span {
background-color: #00ff99; background-color: #2c2f33;
color: #121212; color: #00ff99;
padding: 8px 18px; box-shadow: 0 0 15px rgba(0, 255, 153, 0.7);
border-radius: 8px; border-radius: 8px;
display: inline-block; padding: 8px 18px;
display: inline-flex;
align-items: center;
justify-content: center;
} }
/* ===== Remove File Button ===== */ /* ===== Remove File Button ===== */
#remove-file-btn { #remove-file-btn {
display: none; /* only shows when a file is selected */ display: none;
margin-top: 8px; margin-top: 8px;
padding: 8px 16px; padding: 8px 16px;
border: 2px solid #ff5555; border: 2px solid #ff5555;
@@ -221,14 +231,19 @@ button {
/* ===== Toast Notifications ===== */ /* ===== Toast Notifications ===== */
.toast { .toast {
visibility: hidden; visibility: hidden;
min-width: 250px; width: 80%;
max-width: 500px;
min-height: 50px;
background-color: #333; background-color: #333;
color: #00ff99; color: #00ff99;
text-align: center; text-align: center;
border-radius: 6px; border-radius: 8px;
padding: 10px; padding: 14px;
margin-top: 8px; margin: 10px auto 0 auto;
font-size: 0.9em; font-size: 1em;
display: flex;
align-items: center;
justify-content: center;
animation: fadein 0.5s, fadeout 0.5s 2.5s; animation: fadein 0.5s, fadeout 0.5s 2.5s;
} }
@@ -274,11 +289,14 @@ button {
/* ===== Footer ===== */ /* ===== Footer ===== */
footer { footer {
text-align: center; text-align: center;
padding: 18px; padding: 25px;
background-color: #1c1c1c; background-color: #1c1c1c;
color: #00ff99; color: #00ff99;
margin-top: auto; border-radius: 12px;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
margin-top: 30px;
width: 100%; width: 100%;
max-width: 800px;
} }
footer a { footer a {
@@ -290,22 +308,17 @@ footer {
color: #ff0066; color: #ff0066;
} }
/* ===== Password Input Field ===== */ /* ===== Responsive Tweaks ===== */
#password-input { @media (max-width: 600px) {
display: flex; /* Password input is visible by default */ input,
margin-top: 15px; textarea,
flex-direction: column; select,
gap: 10px; #input-text,
width: 100%; #output-text,
max-width: 500px; #password-field,
} #password,
#file-password {
#password-input input { width: 100%;
padding: 12px; max-width: 90%;
font-size: 1em;
border: 2px solid #00ff99;
border-radius: 8px;
background-color: #2c2f33;
color: #00ff99;
width: 100%; /* Ensure the password field takes full width */
} }
}
+85 -106
View File
@@ -1,41 +1,31 @@
// ===== AES Encryption ===== // ===== AES Advanced Encryption =====
async function encryptAdvanced(message, password) { async function encryptAdvanced(message, password) {
// Create a random salt for key derivation
const salt = crypto.getRandomValues(new Uint8Array(16)); const salt = crypto.getRandomValues(new Uint8Array(16));
// Derive a key from the password using PBKDF2 and the salt
const key = await deriveKey(password, salt); const key = await deriveKey(password, salt);
// Create a random initialization vector (IV)
const iv = crypto.getRandomValues(new Uint8Array(12)); const iv = crypto.getRandomValues(new Uint8Array(12));
// Encode the message as a Uint8Array
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const encodedMessage = encoder.encode(message); const encodedMessage = encoder.encode(message);
// Encrypt the message using AES-GCM
const encryptedMessage = await crypto.subtle.encrypt( const encryptedMessage = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv }, { name: 'AES-GCM', iv: iv },
key, key,
encodedMessage encodedMessage
); );
// Combine salt, IV, and encrypted message
const encryptedArray = new Uint8Array(salt.length + iv.length + encryptedMessage.byteLength); const encryptedArray = new Uint8Array(salt.length + iv.length + encryptedMessage.byteLength);
encryptedArray.set(salt); encryptedArray.set(salt);
encryptedArray.set(iv, salt.length); encryptedArray.set(iv, salt.length);
encryptedArray.set(new Uint8Array(encryptedMessage), salt.length + iv.length); encryptedArray.set(new Uint8Array(encryptedMessage), salt.length + iv.length);
// Convert the result to base64 to send to the server return btoa(String.fromCharCode(...encryptedArray));
return btoa(String.fromCharCode.apply(null, encryptedArray));
} }
// Derive a key from the password using PBKDF2
async function deriveKey(password, salt) { async function deriveKey(password, salt) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password); const passwordBuffer = encoder.encode(password);
const key = await crypto.subtle.importKey( const keyMaterial = await crypto.subtle.importKey(
'raw', 'raw',
passwordBuffer, passwordBuffer,
{ name: 'PBKDF2' }, { name: 'PBKDF2' },
@@ -47,37 +37,31 @@ async function deriveKey(password, salt) {
{ {
name: 'PBKDF2', name: 'PBKDF2',
salt: salt, salt: salt,
iterations: 100000, iterations: 200000,
hash: 'SHA-256', hash: 'SHA-256',
}, },
key, keyMaterial,
{ name: 'AES-GCM', length: 256 }, { name: 'AES-GCM', length: 256 },
false, false,
['encrypt', 'decrypt'] ['encrypt', 'decrypt']
); );
} }
// ===== AES Decryption =====
async function decryptAdvanced(encryptedData, password) { async function decryptAdvanced(encryptedData, password) {
// Decode the base64-encoded encrypted data const encryptedArray = new Uint8Array(atob(encryptedData).split("").map(c => c.charCodeAt(0)));
const encryptedArray = new Uint8Array(atob(encryptedData).split("").map(char => char.charCodeAt(0)));
// Extract salt, IV, and encrypted message from the encrypted data
const salt = encryptedArray.slice(0, 16); const salt = encryptedArray.slice(0, 16);
const iv = encryptedArray.slice(16, 28); const iv = encryptedArray.slice(16, 28);
const encryptedMessage = encryptedArray.slice(28); const encryptedMessage = encryptedArray.slice(28);
// Derive the key from the password and salt
const key = await deriveKey(password, salt); const key = await deriveKey(password, salt);
// Decrypt the message using AES-GCM
const decryptedMessage = await crypto.subtle.decrypt( const decryptedMessage = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv }, { name: 'AES-GCM', iv: iv },
key, key,
encryptedMessage encryptedMessage
); );
// Decode the decrypted message to text
const decoder = new TextDecoder(); const decoder = new TextDecoder();
return decoder.decode(decryptedMessage); return decoder.decode(decryptedMessage);
} }
@@ -85,81 +69,54 @@ async function decryptAdvanced(encryptedData, password) {
// ===== UI Toggles ===== // ===== UI Toggles =====
function toggleEncryptionOptions() { function toggleEncryptionOptions() {
const type = document.getElementById("encryption-type").value; const type = document.getElementById("encryption-type").value;
const pwdContainer = document.getElementById("password-input"); document.getElementById("password-input").style.display = (type === 'advanced') ? 'flex' : 'none';
pwdContainer.style.display = (type === 'advanced') ? 'flex' : 'none';
if (type === 'basic') removeFile(); if (type === 'basic') removeFile();
toggleInputMode(); toggleInputMode();
document.getElementById("encrypt-label").textContent = document.getElementById("encrypt-label").textContent = (type === 'basic') ? "Encode" : "Encrypt";
(type === 'basic') ? "Encode" : "Encrypt"; document.getElementById("decrypt-label").textContent = (type === 'basic') ? "Decode" : "Decrypt";
document.getElementById("decrypt-label").textContent =
(type === 'basic') ? "Decode" : "Decrypt";
} }
// ===== Remove File Button =====
function removeFile() { function removeFile() {
document.getElementById("file-input").value = ""; // Clear the file input document.getElementById("file-input").value = "";
document.getElementById("remove-file-btn").style.display = 'none'; // Hide the remove file button document.getElementById("remove-file-btn").style.display = 'none';
toggleInputMode(); // Reapply the input mode logic toggleInputMode();
document.getElementById("file-password-input").style.display = 'none'; // Hide the file password input
} }
// ===== Input vs. File Toggle =====
function toggleInputMode() { function toggleInputMode() {
const textValue = document.getElementById("input-text").value.trim(); const textValue = document.getElementById("input-text").value.trim();
const fileSelected = document.getElementById("file-input").files.length > 0; const fileSelected = document.getElementById("file-input").files.length > 0;
const isAdvanced = document.getElementById("encryption-type").value === 'advanced'; const isAdvanced = document.getElementById("encryption-type").value === 'advanced';
// Show/hide text area based on file selection document.getElementById("text-section").style.display = fileSelected ? 'none' : 'flex';
document.getElementById("text-section").style.display = document.getElementById("file-section").style.display = (isAdvanced && !textValue) ? 'flex' : 'none';
fileSelected ? 'none' : 'flex'; document.getElementById("remove-file-btn").style.display = fileSelected ? 'inline-block' : 'none';
// Show/hide file input section when in advanced mode and no text input is given
document.getElementById("file-section").style.display =
(isAdvanced && !textValue) ? 'flex' : 'none';
// Show/hide the remove file button
document.getElementById("remove-file-btn").style.display =
fileSelected ? 'inline-block' : 'none';
// ALWAYS show the password input in advanced mode
if (isAdvanced) { if (isAdvanced) {
document.getElementById("password-input").style.display = 'flex'; document.getElementById("password-input").style.display = 'flex';
} else {
document.getElementById("password-input").style.display = 'none';
}
// Show the dedicated password input for file encryption if a file is selected
if (fileSelected) {
document.getElementById("file-password-input").style.display = 'flex'; // Show password input for files
} else {
document.getElementById("file-password-input").style.display = 'none'; // Hide when no file is selected
} }
} }
// ===== Validate and Submit Form ===== // ===== Form Submission =====
async function handleSubmit(event) { async function handleSubmit(event) {
event.preventDefault(); event.preventDefault();
// If the encryption type is advanced, ensure password is provided
const password = document.getElementById("password").value; const password = document.getElementById("password").value;
const filePassword = document.getElementById("file-password") ? document.getElementById("file-password").value : '';
const encryptionType = document.getElementById("encryption-type").value; const encryptionType = document.getElementById("encryption-type").value;
if (encryptionType === 'advanced' && !password && !filePassword) { if (encryptionType === 'advanced' && !password) {
alert("Password is required for advanced encryption."); alert("Password is required for advanced encryption.");
return; return;
} }
// Prepare the form data
const payload = { const payload = {
"encryption-type": encryptionType, "encryption-type": encryptionType,
operation: document.querySelector('input[name="operation"]:checked').value, operation: document.querySelector('input[name="operation"]:checked').value,
message: document.getElementById("input-text").value, message: document.getElementById("input-text").value,
password: password, password: password
"file-password": filePassword
}; };
// Handle file upload encryption/decryption
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
if (fileInput.files.length > 0) { if (fileInput.files.length > 0) {
const op = document.querySelector('input[name="operation"]:checked').value; const op = document.querySelector('input[name="operation"]:checked').value;
@@ -168,7 +125,6 @@ async function handleSubmit(event) {
return; return;
} }
// Handle text encryption/decryption
try { try {
const resp = await fetch("/", { const resp = await fetch("/", {
method: "POST", method: "POST",
@@ -185,30 +141,35 @@ async function handleSubmit(event) {
// ===== File Encryption / Decryption ===== // ===== File Encryption / Decryption =====
function encryptFile() { function encryptFile() {
const f = document.getElementById("file-input"); const f = document.getElementById("file-input");
const pwd = document.getElementById("file-password").value; const pwd = document.getElementById("password").value;
if (!pwd) return alert("Please enter a password!"); if (!pwd) return alert("Please enter a password!");
if (!f.files.length) return alert("Please select a file!"); if (!f.files.length) return alert("Please select a file!");
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async (e) => { reader.onload = async (e) => {
const raw = e.target.result; const raw = new Uint8Array(e.target.result);
let encryptedMessage = await encryptAdvanced(raw, pwd); const base64Raw = btoa(String.fromCharCode(...raw));
downloadFile(encryptedMessage, f.files[0].name + ".enc"); const encrypted = await encryptAdvanced(base64Raw, pwd);
downloadFile(encrypted, f.files[0].name + ".enc");
}; };
reader.readAsText(f.files[0]); reader.readAsArrayBuffer(f.files[0]);
} }
function decryptFile() { function decryptFile() {
const f = document.getElementById("file-input"); const f = document.getElementById("file-input");
const pwd = document.getElementById("file-password").value; const pwd = document.getElementById("password").value;
if (!pwd) return alert("Please enter a password!"); if (!pwd) return alert("Please enter a password!");
if (!f.files.length) return alert("Please select a file!"); if (!f.files.length) return alert("Please select a file!");
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async (e) => { reader.onload = async (e) => {
try { try {
const enc = e.target.result; const encryptedText = e.target.result;
const decryptedMessage = await decryptAdvanced(enc, pwd); const decryptedBase64 = await decryptAdvanced(encryptedText, pwd);
downloadFile(decryptedMessage, f.files[0].name.replace(/\.enc$/, '')); const byteString = atob(decryptedBase64);
} catch { const byteArray = new Uint8Array([...byteString].map(c => c.charCodeAt(0)));
downloadFileBinary(byteArray, f.files[0].name.replace(/\.enc$/, ''));
} catch (err) {
alert("Decryption failed: wrong password or corrupted file."); alert("Decryption failed: wrong password or corrupted file.");
} }
}; };
@@ -225,6 +186,16 @@ function downloadFile(content, filename) {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
function downloadFileBinary(byteArray, filename) {
const blob = new Blob([byteArray], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// ===== Password Generator ===== // ===== Password Generator =====
function generateRandomPassword() { function generateRandomPassword() {
const length = 30; const length = 30;
@@ -240,13 +211,12 @@ function generateRandomPassword() {
function copyToClipboard(elementId, toastId) { function copyToClipboard(elementId, toastId) {
const copyText = document.getElementById(elementId); const copyText = document.getElementById(elementId);
copyText.select(); copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices copyText.setSelectionRange(0, 99999);
document.execCommand("copy"); document.execCommand("copy");
// Show toast notification
const toast = document.getElementById(toastId); const toast = document.getElementById(toastId);
toast.classList.add("show"); toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 2000); // Remove toast after 2 seconds setTimeout(() => toast.classList.remove("show"), 2000);
} }
// ===== Pacman Easter Egg ===== // ===== Pacman Easter Egg =====
@@ -277,6 +247,28 @@ function resetGame() {
startPacman(); startPacman();
} }
// ===== Clear All =====
function clearAll() {
document.getElementById("input-text").value = "";
document.getElementById("output-text").value = "";
document.getElementById("file-input").value = "";
document.getElementById("password").value = "";
document.getElementById("pacman-section").style.display = "none";
document.getElementById("encoding-section").style.display = "block";
removeFile();
toggleInputMode();
}
// ===== Initialize =====
document.addEventListener("DOMContentLoaded", () => {
toggleEncryptionOptions();
toggleInputMode();
document.getElementById("input-text").addEventListener("input", checkForPacman);
});
// ===== Pacman Game Variables & Logic ===== // ===== Pacman Game Variables & Logic =====
let canvas, ctx, pacman, enemy, walls, dots, score; let canvas, ctx, pacman, enemy, walls, dots, score;
let pacmanSpeed = 40, enemySpeed = 20, cellSize = 40, dotSize = 5; let pacmanSpeed = 40, enemySpeed = 20, cellSize = 40, dotSize = 5;
@@ -441,18 +433,27 @@ function drawChar(ch,color) {
} }
function eatDots() { function eatDots() {
dots = dots.filter(d=>{ const chompSound = document.getElementById("chomp-sound");
const dx = d.c*cellSize+cellSize/2, dy = d.r*cellSize+cellSize/2;
if (Math.abs(pacman.x-dx)<pacman.size && Math.abs(pacman.y-dy)<pacman.size) { dots = dots.filter(d => {
const dx = d.c * cellSize + cellSize / 2;
const dy = d.r * cellSize + cellSize / 2;
if (Math.abs(pacman.x - dx) < pacman.size && Math.abs(pacman.y - dy) < pacman.size) {
score++; score++;
return false; if (chompSound) {
chompSound.currentTime = 0; // Reset sound
chompSound.volume = 0.4;
chompSound.play();
}
return false; // Remove dot
} }
return true; return true;
}); });
ctx.fillStyle="white";
dots.forEach(d=>{ ctx.fillStyle = "white";
dots.forEach(d => {
ctx.beginPath(); ctx.beginPath();
ctx.arc(d.c*cellSize+cellSize/2, d.r*cellSize+cellSize/2, dotSize,0,Math.PI*2); ctx.arc(d.c * cellSize + cellSize / 2, d.r * cellSize + cellSize / 2, dotSize, 0, Math.PI * 2);
ctx.fill(); ctx.fill();
}); });
} }
@@ -472,25 +473,3 @@ function checkGameOver() {
clearInterval(gameInterval); clearInterval(gameInterval);
} }
} }
// ===== Clear All Functionality =====
function clearAll() {
document.getElementById("input-text").value = "";
document.getElementById("output-text").value = "";
document.getElementById("file-input").value = "";
document.getElementById("password").value = "";
document.getElementById("file-password").value = "";
document.getElementById("pacman-section").style.display = "none";
document.getElementById("encoding-section").style.display = "block";
removeFile();
toggleInputMode();
}
// ===== Initialize =====
document.addEventListener("DOMContentLoaded", () => {
toggleEncryptionOptions();
toggleInputMode();
document.getElementById("input-text").addEventListener("input", checkForPacman);
});
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>403 - PacCrypt</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="dark">
<header class="card" style="margin-bottom: 20px;">
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<section class="card" style="padding: 50px 30px;">
<h2 style="color: #00ff99; font-size: 2.5em;">403 - Forbidden</h2>
<p style="margin-top: 20px; font-size: 1.2em; color: #cccccc;">
Looks like this area is locked behind a secret ghost door! 🛡️👻
</p>
<div class="button-group" style="margin-top: 30px;">
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a>
</div>
</section>
</main>
<footer class="card" style="margin-top: 20px;">
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - PacCrypt</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="dark">
<header>
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<section class="card">
<h2>404 - Page Not Found</h2>
<p>Oops! The page you're looking for doesn't exist.</p>
<div class="button-group" style="margin-top: 20px;">
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a>
</div>
</section>
</main>
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>500 - PacCrypt</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="dark">
<header class="card" style="margin-bottom: 20px;">
<h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p>
</header>
<main>
<section class="card" style="padding: 50px 30px;">
<h2 style="color: #00ff99; font-size: 2.5em;">500 - Server Error</h2>
<p style="margin-top: 20px; font-size: 1.2em; color: #cccccc;">
Uh oh! The ghosts chomped the server. 🧟‍♂️
</p>
<div class="button-group" style="margin-top: 30px;">
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a>
</div>
</section>
</main>
<footer class="card" style="margin-top: 20px;">
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body>
</html>
+34 -22
View File
@@ -4,29 +4,31 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PacCrypt</title> <title>PacCrypt</title>
<!-- Favicon Link -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}">
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Styles and Scripts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<script defer src="{{ url_for('static', filename='js/script.js') }}"></script> <script defer src="{{ url_for('static', filename='js/script.js') }}"></script>
</head> </head>
<body class="dark"> <body class="dark">
<!-- Header -->
<header> <header>
<h1>PacCrypt</h1> <h1>PacCrypt</h1>
<p>Secure Encoding, Encryption and Password Generation</p> <p>Secure Encoding, Encryption and Password Generation</p>
</header> </header>
<main> <main>
<!-- Password Generator Section -->
<section id="password-section" class="card"> <section id="password-section" class="card">
<h2>Password Generator</h2> <h2>Password Generator</h2>
<div class="form-group"> <div class="form-group">
<input <input type="text" id="password-field" placeholder="Generated password will appear here" readonly />
type="text"
id="password-field"
placeholder="Generated password will appear here"
readonly
/>
<div class="button-group"> <div class="button-group">
<button type="button" onclick="generateRandomPassword()">Generate</button> <button type="button" onclick="generateRandomPassword()">Generate</button>
<button type="button" onclick="copyToClipboard('password-field', 'password-toast')">Copy</button> <button type="button" onclick="copyToClipboard('password-field', 'password-toast')">Copy</button>
@@ -35,6 +37,7 @@
</div> </div>
</section> </section>
<!-- Pacman Game Section -->
<section id="pacman-section" class="card" style="display: none;"> <section id="pacman-section" class="card" style="display: none;">
<div class="pacman-wrapper"> <div class="pacman-wrapper">
<canvas id="pacmanCanvas" width="800" height="600"></canvas> <canvas id="pacmanCanvas" width="800" height="600"></canvas>
@@ -46,15 +49,19 @@
</div> </div>
</section> </section>
<!-- Text Encoder/Decoder & File Encrypt/Decrypt Section -->
<section id="encoding-section" class="card"> <section id="encoding-section" class="card">
<h2>Text Encoder / Decoder & File Encryption</h2> <h2>Text Encoder / Decoder & File Encryption</h2>
<form id="main-form" class="form-group" method="POST" onsubmit="handleSubmit(event)"> <form id="main-form" class="form-group" method="POST" onsubmit="handleSubmit(event)">
<!-- Encryption Type Dropdown -->
<label for="encryption-type">Select Encryption Type:</label> <label for="encryption-type">Select Encryption Type:</label>
<select id="encryption-type" name="encryption-type" onchange="toggleEncryptionOptions()"> <select id="encryption-type" name="encryption-type" onchange="toggleEncryptionOptions()">
<option value="basic">Basic (Less Secure)</option> <option value="basic">Basic (Less Secure)</option>
<option value="advanced" selected>Advanced (More Secure)</option> <option value="advanced" selected>Advanced (More Secure)</option>
</select> </select>
<!-- Encrypt / Decrypt Radio Buttons -->
<div id="encryption-options" class="radio-group"> <div id="encryption-options" class="radio-group">
<label class="radio-button"> <label class="radio-button">
<input type="radio" name="operation" value="encrypt" id="encrypt-radio" checked /> <input type="radio" name="operation" value="encrypt" id="encrypt-radio" checked />
@@ -66,41 +73,45 @@
</label> </label>
</div> </div>
<!-- Text Area Input -->
<div id="text-section" class="form-group"> <div id="text-section" class="form-group">
<textarea <textarea id="input-text" name="message" placeholder="Enter text here..." oninput="toggleInputMode()"></textarea>
id="input-text"
name="message"
placeholder="Enter text here..."
oninput="toggleInputMode()"
></textarea>
<div id="password-input">
<input type="password" id="password" name="password" placeholder="Enter Password" />
</div>
</div> </div>
<!-- Password Input (shared for file + text) -->
<div id="password-input" class="form-group">
<input type="password" id="password" name="password" placeholder="Enter Password" />
</div>
<!-- File Upload Section -->
<div id="file-section" class="form-group" style="display: none;"> <div id="file-section" class="form-group" style="display: none;">
<input type="file" id="file-input" onchange="toggleInputMode()" /> <input type="file" id="file-input" onchange="toggleInputMode()" />
<button type="button" id="remove-file-btn" onclick="removeFile()">Remove File</button> <button type="button" id="remove-file-btn" onclick="removeFile()">Remove File</button>
</div> </div>
<div id="file-password-input" style="display: none;"> <!-- Submit Button -->
<input type="password" id="file-password" name="file-password" placeholder="Enter Password for File" /> <div class="button-group">
<button type="submit" class="submit-button">Submit</button>
</div> </div>
<button type="button" class="submit-button" onclick="handleSubmit(event)">Submit</button>
</form> </form>
<!-- Output Text Area -->
<div style="height: 20px;"></div> <div style="height: 20px;"></div>
<textarea id="output-text" readonly placeholder="Result will appear here">{{ result }}</textarea> <textarea id="output-text" readonly placeholder="Result will appear here">{{ result }}</textarea>
<!-- Output Controls -->
<div class="button-group"> <div class="button-group">
<button type="button" onclick="copyToClipboard('output-text', 'output-toast')">Copy Output</button> <button type="button" onclick="copyToClipboard('output-text', 'output-toast')">Copy Output</button>
<button type="button" onclick="clearAll()">Clear All</button> <button type="button" onclick="clearAll()">Clear All</button>
</div> </div>
<!-- Output Toast Notification -->
<div id="output-toast" class="toast">Copied to Clipboard!</div> <div id="output-toast" class="toast">Copied to Clipboard!</div>
</section> </section>
</main> </main>
<!-- Footer -->
<footer> <footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p> <p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link"> <a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
@@ -108,5 +119,6 @@
alt="GitHub Logo" width="100" /> alt="GitHub Logo" width="100" />
</a> </a>
</footer> </footer>
</body> </body>
</html> </html>