Compare commits

..

No commits in common. "master" and "v0.1" have entirely different histories.
master ... v0.1

24 changed files with 257 additions and 11386 deletions

View File

@ -1,22 +0,0 @@
env:
browser: true
es2021: true
node: true
extends: eslint:recommended
overrides: []
parserOptions:
ecmaVersion: latest
sourceType: module
rules:
indent:
- error
- tab
linebreak-style:
- error
- unix
quotes:
- error
- double
semi:
- error
- always

View File

@ -1,50 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# ESLint is a tool for identifying and reporting on patterns
# found in ECMAScript/JavaScript code.
# More details at https://github.com/eslint/eslint
# and https://eslint.org
name: ESLint
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '33 14 * * 2'
jobs:
eslint:
name: Run eslint scanning
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install ESLint
run: |
npm install eslint@8.10.0
npm install @microsoft/eslint-formatter-sarif@2.1.7
- name: Run ESLint
run: npx eslint .
--config .eslintrc.yml
--ext .js,.jsx,.ts,.tsx
--format @microsoft/eslint-formatter-sarif
--output-file eslint-results.sarif
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: eslint-results.sarif
wait-for-processing: true

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
tags
node_modules/
dist/

View File

@ -1,9 +0,0 @@
Copyright 2023 dogeystamp <dogeystamp@disroot.org>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,41 +0,0 @@
# encryptme
encryptme is a website that provides cryptography tools based on the browser's SubtleCrypto API.
It aims to be simple to use, but also allow users to tinker with more advanced options if needed.
![AES encryption page](./media/aes_enc.jpg)
Currently, the following algorithms are implemented:
* [AES encryption/decryption](https://dogeystamp.github.io/encryptme/aes.html)
This uses PBKDF2 to convert a password to a key, then uses AES
to encrypt a given message.
## Installation
Clone the repo:
```
git clone https://github.com/dogeystamp/encryptme
```
Install packages:
```
npm install
```
## Running
Start development server:
```
npm run start
```
Or, compile to `dist/`:
```
npm run build
```

23
dec.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>encryptme: Decryption</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script src="encryption.js"></script>
<h1>encryptme</h1>
<label for="msg">Ciphertext: </label>
<textarea id="msg" form="form"></textarea>
<label for="password">Password: </label>
<input id="password" type="password">
<button id="decrypt">Decrypt</button>
<textarea id="plaintext" readonly></textarea>
<script>
document.getElementById("decrypt").addEventListener("click", dec);
</script>
</body>
</html>

23
enc.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>encryptme: Encryption</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script src="encryption.js"></script>
<h1>encryptme</h1>
<label for="msg">Message: </label>
<textarea id="msg" form="form"></textarea>
<label for="password">Password: </label>
<input id="password" type="password">
<button id="encrypt">Encrypt</button>
<textarea id="ciphertext" readonly></textarea>
<script>
document.getElementById("encrypt").addEventListener("click", enc);
</script>
</body>
</html>

124
encryption.js Normal file
View File

@ -0,0 +1,124 @@
function getMsg() {
return msg = document.getElementById("msg").value;
}
function getMsgEncoding () {
let enc = new TextEncoder();
return enc.encode(getMsg());
}
function getKeyMaterial () {
let pass = document.getElementById("password").value;
let enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(pass),
"PBKDF2",
false,
["deriveKey"]
);
}
function getKey (keyMaterial, salt) {
return window.crypto.subtle.deriveKey(
{
"name": "PBKDF2",
"hash": "SHA-256",
"salt": salt,
"iterations": 300000
},
keyMaterial,
{
"name": "AES-GCM",
"length": 256
},
true,
["encrypt", "decrypt"]
);
}
function bufTo64 (buf) {
let bytes = new Uint8Array(buf);
let ascii = ''
for (var i = 0; i < bytes.byteLength; i++) {
ascii += String.fromCharCode(bytes[i]);
}
return btoa(ascii);
}
function b64ToBuf (b64) {
let ascii = atob(b64);
let buf = new ArrayBuffer(ascii.length);
let bytes = new Uint8Array(buf);
for (var i = 0; i < ascii.length; i++) {
bytes[i] = ascii.charCodeAt(i);
}
return buf;
}
function concatBuf(buf1, buf2) {
let tmp = new Uint8Array(buf1.byteLength + buf2.byteLength);
tmp.set(new Uint8Array(buf1), 0);
tmp.set(new Uint8Array(buf2), buf1.byteLength);
return tmp.buffer;
}
async function exportKey (key) {
let k = await window.crypto.subtle.exportKey("raw", key);
return bufTo64(k);
}
async function enc () {
outBox = document.getElementById("ciphertext");
outBox.innerHTML = '';
let keyMaterial = await getKeyMaterial();
let salt = window.crypto.getRandomValues(new Uint8Array(16));
let key = await getKey(keyMaterial, salt);
let iv = window.crypto.getRandomValues(new Uint8Array(16));
let msgEncoded = getMsgEncoding();
ciphertext = await window.crypto.subtle.encrypt(
{
"name": "AES-GCM",
"iv": iv
},
key,
msgEncoded
);
let output = concatBuf(concatBuf(ciphertext, salt), iv);
outBox.innerHTML = `${bufTo64(output)}`;
let keyExp = await exportKey (key);
}
async function dec () {
outBox = document.getElementById("plaintext");
outBox.innerHTML = '';
let msgEncoded = b64ToBuf(getMsg());
let ciphertext = new Uint8Array(msgEncoded.slice(0, -32));
let iv = new Uint8Array(msgEncoded.slice(-16));
let salt = new Uint8Array(msgEncoded.slice(-32, -16));
let keyMaterial = await getKeyMaterial();
let key = await getKey(keyMaterial, salt);
try {
let plaintext = await window.crypto.subtle.decrypt(
{
"name": "AES-GCM",
"iv": iv
},
key,
ciphertext
);
let dec = new TextDecoder();
outBox.innerHTML = `${dec.decode(plaintext)}`;
} catch (e) {
window.alert("Decryption error: incorrect password?");
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

9912
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
{
"name": "encryptme",
"version": "0.5.1",
"description": "Simple online cryptography app.",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.prod.js",
"lint": "eslint --ext .js,.jsx src *.js",
"start": "webpack serve --open --config webpack.dev.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/dogeystamp/encryptme.git"
},
"keywords": [
"website",
"cryptography",
"encryption",
"aes",
"webapp",
"aes-256",
"aes-gcm",
"decryption",
"encryption-decryption",
"cryptography-tools",
"cryptography-utilities"
],
"author": "dogeystamp",
"license": "BSD-2-Clause",
"bugs": {
"url": "https://github.com/dogeystamp/encryptme/issues"
},
"homepage": "https://github.com/dogeystamp/encryptme#readme",
"devDependencies": {
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2",
"eslint": "^8.33.0",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.7.2",
"sitemap-webpack-plugin": "^1.1.1",
"style-loader": "^3.3.1",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "^5.8.0"
}
}

View File

@ -1,413 +0,0 @@
/*
Copyright 2023 dogeystamp <dogeystamp@disroot.org>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import { generateHeader } from "./templates.js";
import "./style.css";
generateHeader();
import { TabList } from "./interface.js";
import { bufToB64, b64ToBuf } from "./util.js";
let tabs = new TabList({});
let encForm = tabs.createForm({label: "Encryption"});
let encMsg = encForm.createTextArea({
label: "Message",
placeholder: "Type a secret message",
});
let encPass = encForm.createPasswordInput({
label: "Password",
placeholder: "Enter your password",
enabledFunc: function() {return !encManualKey.value;}
});
let encPbkdf2Iters = encForm.createNumberInput({
label: "PBKDF2 iterations",
minValue: 1,
step: 1,
value: 300000,
advanced: true,
enabledFunc: function() {return !encManualKey.value;}
});
let encSalt = encForm.createMediumTextBox({
label: "PBKDF2 salt",
dataType: "b64",
advanced: true,
enabled: false,
enabledFunc: function() {return encManualSalt.value && !encManualKey.value;}
});
let encManualSalt = encForm.createCheckBox({
label: "Use fixed salt instead of random",
advanced: true
});
let encKeySize = encForm.createDropDown({
label: "AES key size",
advanced: true,
options: [
{
name: "128 bits",
value: 128
},
{
name: "256 bits",
value: "256"
},
]
});
let encKey = encForm.createMediumTextBox({
label: "Key",
dataType: "b64",
advanced: true,
enabled: false,
enabledFunc: function() {return encManualKey.value;}
});
let encManualKey = encForm.createCheckBox({
label: "Use fixed key instead of password",
advanced: true
});
let encIV = encForm.createMediumTextBox({
label: "IV",
dataType: "b64",
advanced: true,
enabledFunc: function() {return encManualIV.value;},
visibleFunc: function() {return ["AES-GCM", "AES-CBC"].includes(encMode.value);}
});
let encManualIV = encForm.createCheckBox({
label: "Use fixed IV instead of random",
advanced: true,
visibleFunc: function() {return ["AES-GCM", "AES-CBC"].includes(encMode.value);}
});
let encCounter = encForm.createMediumTextBox({
label: "Counter",
dataType: "b64",
advanced: true,
enabledFunc: function() {return encManualCounter.value;},
visibleFunc: function() {return encMode.value === "AES-CTR";}
});
let encManualCounter = encForm.createCheckBox({
label: "Use fixed counter instead of random",
advanced: true,
visibleFunc: function() {return encMode.value === "AES-CTR";}
});
let encMode = encForm.createDropDown({
label: "AES mode",
advanced: true,
options: [
{
name: "AES-GCM (Galois/Counter Mode)",
value: "AES-GCM"
},
{
name: "AES-CBC (Cipher Block Chaining)",
value: "AES-CBC"
},
{
name: "AES-CTR (Counter)",
value: "AES-CTR"
},
]
});
let encButton = encForm.createButton({label: "Encrypt"});
let encOut = encForm.createOutput({
label: "Output",
dataType: "json-b64",
});
let encOutRaw = encForm.createOutput({
label: "Raw ciphertext",
dataType: "b64",
advanced: true
});
let decForm = tabs.createForm({label: "Decryption"});
let decMsg = decForm.createTextArea({
label: "Encrypted message",
placeholder: "Paste the encrypted output",
dataType: "json-b64",
});
let decPass = decForm.createPasswordInput({
label: "Password",
placeholder: "Enter your password",
enabledFunc: function() {return !decManualKey.value;}
});
let decKey = decForm.createMediumTextBox({
label: "Key",
dataType: "b64",
advanced: true,
enabled: false,
enabledFunc: function() {return decManualKey.value;}
});
let decManualKey = decForm.createCheckBox({
label: "Use fixed key instead of password",
advanced: true
});
let decButton = decForm.createButton({label: "Decrypt"});
let decOut = decForm.createOutput({label: "Output"});
tabs.mountForms();
function getKeyMaterial(password) {
let enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveKey"]
);
}
function getKey(keyMaterial, salt, pbkdf2Iters, encMode, keySize) {
return window.crypto.subtle.deriveKey(
{
"name": "PBKDF2",
"hash": "SHA-256",
"salt": salt,
"iterations": pbkdf2Iters
},
keyMaterial,
{
"name": encMode,
"length": keySize
},
true,
["encrypt", "decrypt"]
);
}
async function aesGcmEnc(key, iv, msgEncoded) {
return window.crypto.subtle.encrypt(
{
"name": "AES-GCM",
"iv": iv
},
key,
msgEncoded
);
}
async function aesCbcEnc(key, iv, msgEncoded) {
return window.crypto.subtle.encrypt(
{
"name": "AES-CBC",
"iv": iv
},
key,
msgEncoded
);
}
async function aesCtrEnc(key, counter, msgEncoded) {
return window.crypto.subtle.encrypt(
{
"name": "AES-CTR",
"counter": counter,
"length": 64
},
key,
msgEncoded
);
}
encButton.handle.addEventListener("click", async function() {
let keyMaterial = await getKeyMaterial(encPass.value);
let key;
let salt = encSalt.value;
let pbkdf2Iters = encPbkdf2Iters.value;
if (pbkdf2Iters === undefined) return;
if (pbkdf2Iters > 1000000) {
encPbkdf2Iters.alertBox("alert-info", `PBKDF2 is using ${pbkdf2Iters} iterations: this might take a long time...`);
}
if (encManualKey.value) {
key = await window.crypto.subtle.importKey(
"raw",
encKey.value,
{"name": encMode.value},
true,
["encrypt", "decrypt"]
);
} else {
if (encSalt.enabledFunc()) {
salt = encSalt.value;
} else {
salt = window.crypto.getRandomValues(new Uint8Array(16));
encSalt.value = salt;
}
key = await getKey(keyMaterial, salt, pbkdf2Iters, encMode.value, Number(encKeySize.value));
encKey.value = await window.crypto.subtle.exportKey("raw", key);
}
let iv;
if (["AES-GCM", "AES-CBC"].includes(encMode.value)) {
if (encManualIV.value) {
iv = encIV.value;
} else {
iv = window.crypto.getRandomValues(new Uint8Array(16));
encIV.value = iv;
}
}
let counter;
if (encMode.value === "AES-CTR") {
if (encManualCounter.value) {
counter = encCounter.value;
} else {
counter = window.crypto.getRandomValues(new Uint8Array(16));
encCounter.value = counter;
}
}
let enc = new TextEncoder();
let msgEncoded = enc.encode(encMsg.value);
let ciphertext;
switch (encMode.value) {
case "AES-GCM":
ciphertext = await aesGcmEnc(key, iv, msgEncoded);
break;
case "AES-CBC":
ciphertext = await aesCbcEnc(key, iv, msgEncoded);
break;
case "AES-CTR":
ciphertext = await aesCtrEnc(key, counter, msgEncoded);
break;
default:
encMode.handleError(Error(`Mode '${encMode.value}' is not implemented.`));
return;
}
encOutRaw.value = ciphertext;
encOut.value = {
"ciphertext": bufToB64(ciphertext),
"salt": bufToB64(salt),
"iv": bufToB64(iv),
"counter": bufToB64(counter),
"encMode": encMode.value,
"encKeySize": encKeySize.value,
"pbkdf2Iters": pbkdf2Iters,
};
});
async function aesGcmDec(key, iv, ciphertext) {
return window.crypto.subtle.decrypt(
{
"name": "AES-GCM",
"iv": iv
},
key,
ciphertext
);
}
async function aesCbcDec(key, iv, ciphertext) {
return window.crypto.subtle.decrypt(
{
"name": "AES-CBC",
"iv": iv
},
key,
ciphertext
);
}
async function aesCtrDec(key, counter, ciphertext) {
return window.crypto.subtle.decrypt(
{
"name": "AES-CTR",
"counter": counter,
"length": 64
},
key,
ciphertext
);
}
decButton.handle.addEventListener("click", async function() {
let msgEncoded = decMsg.value;
let ciphertext, iv, counter, salt, encMode, pbkdf2Iters, encKeySize;
try {
ciphertext = new b64ToBuf(msgEncoded.ciphertext);
iv = new Uint8Array(b64ToBuf(msgEncoded.iv));
counter = new Uint8Array(b64ToBuf(msgEncoded.counter));
salt = new Uint8Array(b64ToBuf(msgEncoded.salt));
encMode = msgEncoded.encMode;
encKeySize = msgEncoded.encKeySize;
if (!["128", "256"].includes(encKeySize)) {
throw Error(`Invalid AES key size: '${encKeySize}'`);
}
pbkdf2Iters = msgEncoded.pbkdf2Iters;
if (pbkdf2Iters < 1 || pbkdf2Iters%1 !== 0) {
throw Error(`Invalid PBKDF2 iterations setting: ${pbkdf2Iters}`);
} else if (pbkdf2Iters > 1000000) {
decMsg.alertBox("alert-info", `PBKDF2 is using ${pbkdf2Iters} iterations: this might take a long time...`);
}
} catch (e) {
decMsg.handleError(e, "Invalid encrypted payload.");
}
if (ciphertext === undefined
|| iv === undefined
|| salt === undefined
|| pbkdf2Iters === undefined
) {
return;
}
let keyMaterial = await getKeyMaterial(decPass.value);
let key;
if (decManualKey.value) {
try {
key = await window.crypto.subtle.importKey(
"raw",
decKey.value,
{"name": encMode},
true,
["encrypt", "decrypt"]
);
} catch (e) {
decMsg.handleError(e);
}
} else {
key = await getKey(keyMaterial, salt, pbkdf2Iters, encMode, Number(encKeySize));
decKey.value = await window.crypto.subtle.exportKey("raw", key);
}
let plaintext;
try {
switch (encMode) {
case "AES-GCM":
plaintext = await aesGcmDec(key, iv, ciphertext);
break;
case "AES-CBC":
plaintext = await aesCbcDec(key, iv, ciphertext);
break;
case "AES-CTR":
plaintext = await aesCtrDec(key, counter, ciphertext);
break;
default:
throw Error(`Mode '${encMode.value}' is not implemented.`);
}
} catch (e) {
if (e.message !== "" && e.message !== undefined) {
decMsg.handleError(e, "Error during decryption.");
} else {
decMsg.handleError(Error("Could not decrypt; is your password/key correct?"));
}
}
let dec = new TextDecoder();
decOut.value = `${dec.decode(plaintext)}`;
});

View File

@ -1,3 +0,0 @@
import { generateHeader } from "./templates.js";
import "./style.css";
generateHeader();

View File

@ -1,546 +0,0 @@
/*
Copyright 2023 dogeystamp <dogeystamp@disroot.org>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import { b64ToBuf, bufToB64 } from "./util.js";
class InterfaceElement {
rootNodes = [];
constructor({fragment, enabledFunc, visibleFunc}) {
if (fragment === undefined) {
this.fragment = new DocumentFragment();
} else {
this.fragment = fragment;
}
if (enabledFunc === undefined) {
this.enabledFunc = function(){ return true; };
} else {
this.enabledFunc = enabledFunc;
}
if (visibleFunc === undefined) {
this.visibleFunc = function(){ return true; };
} else {
this.visibleFunc = visibleFunc;
}
}
update() {
this.enabled = this.enabledFunc();
this.hidden = !this.visibleFunc();
}
scanNodes() {
this.rootNodes = [];
for (const node of this.fragment.children) {
this.rootNodes.push(node);
}
}
mount(par) {
this.scanNodes();
par.append(this.fragment);
}
#hidden = false;
get hidden() {
return this.#hidden;
}
set hidden(x) {
if (this.#hidden === x) return;
this.#hidden = x;
for (const node of this.rootNodes) {
if (this.hidden) {
node.classList.add("visualhidden");
node.classList.add("hidden");
} else {
node.classList.remove("hidden");
setTimeout(function() {
node.classList.remove("visualhidden");
}, 20);
}
}
if (this.hidden === true) this.clearAlerts();
}
#enabled = true;
get enabled() {
return this.#enabled;
}
set enabled(x) {
this.#enabled = x;
this.handle.disabled = !this.#enabled;
}
}
function dataTypeSupports(params, validTypes) {
if (params.dataType === undefined) {
params.dataType = validTypes[0];
}
if (!validTypes.includes(params.dataType)) {
throw `Element can not support '${params.dataType}' data type`;
}
}
class Form extends InterfaceElement {
constructor({tag, par=document.body, label, mounted=false}) {
super({});
if (tag === undefined) {
this.handle = document.createElement("div");
} else {
this.handle = tag;
}
this.fragment.appendChild(this.handle);
this.elements = [];
this.clearAlerts = this.clearAlerts.bind(this);
if (label !== undefined) {
this.createHeader({label: label});
}
let advancedToggle = this.createCheckBox({label: "Advanced settings"});
advancedToggle.handle.addEventListener("change", function() {
this.advanced = advancedToggle.value;
}.bind(this));
if (mounted) {
this.mount(par);
}
}
#hidden = false;
get hidden() {
return this.#hidden;
}
set hidden(x) {
this.#hidden = x;
for (const element of this.elements) {
if (element.advanced === true) {
element.hidden = !this.advanced || this.hidden;
} else {
element.hidden = this.hidden;
}
}
if (this.hidden === true) this.clearAlerts();
}
#advanced = false;
get advanced() {
return this.#advanced;
}
set advanced(x) {
this.#advanced = x;
for (const element of this.elements) {
if (element.advanced === true) {
element.update();
}
}
}
clearAlerts() {
for (const element of this.elements) {
element.clearAlerts();
}
}
createElem(params) {
params.form = this;
let elem = new FormElement(params);
elem.mount(this.handle);
this.elements.push(elem);
this.rootNodes.push(...elem.rootNodes);
if (elem.advanced) {
elem.hidden = !this.advanced;
}
if (this.hidden) elem.hidden = true;
return elem;
}
createHeader(params) {
params.tag = document.createElement("h2");
dataTypeSupports(params, ["none"]);
let labelTag = document.createTextNode(params.label);
params.tag.appendChild(labelTag);
params.label = undefined;
return this.createElem(params);
}
createTextBox(params) {
params.tag = document.createElement("input");
dataTypeSupports(params, ["plaintext", "b64", "json-b64"]);
return this.createElem(params);
}
createMediumTextBox(params) {
params.tag = document.createElement("textarea");
params.tag.classList.add("mediumbox");
dataTypeSupports(params, ["plaintext", "b64", "json-b64"]);
return this.createElem(params);
}
createPasswordInput(params) {
params.tag = document.createElement("input");
params.tag.setAttribute("type", "password");
dataTypeSupports(params, ["plaintext"]);
return this.createElem(params);
}
createNumberInput(params) {
params.tag = document.createElement("input");
params.tag.setAttribute("type", "number");
dataTypeSupports(params, ["number"]);
if (params.maxValue !== undefined) params.tag.max = params.maxValue;
if (params.minValue !== undefined) params.tag.min = params.minValue;
if (params.step !== undefined) params.tag.step = params.step;
if (params.required !== undefined) params.tag.required = params.required;
return this.createElem(params);
}
createDropDown(params) {
// example for params.options:
/*
[
{
value: "volvo"
name: "Volvo"
},
{
value: "benz"
name: "Mercedes Benz"
}
]
*/
params.fragment = new DocumentFragment();
params.tag = document.createElement("select");
params.labelTag = document.createElement("label");
params.labelTag.appendChild(document.createTextNode(params.label));
params.fragment.appendChild(params.labelTag);
params.fragment.appendChild(params.tag);
dataTypeSupports(params, ["category"]);
for (const option of params.options) {
let optTag = document.createElement("option");
optTag.value = option.value;
optTag.appendChild(document.createTextNode(option.name));
params.tag.appendChild(optTag);
}
params.tag.addEventListener("change", function() {
for (const elem of this.elements) {
elem.update();
}
}.bind(this));
return this.createElem(params);
}
createTextArea(params) {
params.tag = document.createElement("textarea");
dataTypeSupports(params, ["plaintext", "b64", "json-b64"]);
return this.createElem(params);
}
createButton(params) {
params.fragment = new DocumentFragment();
params.tag = document.createElement("button");
params.labelTag = document.createTextNode(params.label);
params.tag.appendChild(params.labelTag);
params.fragment.appendChild(params.tag);
dataTypeSupports(params, ["none"]);
return this.createElem(params);
}
createCheckBox(params) {
params.fragment = new DocumentFragment();
params.tag = document.createElement("input");
params.tag.setAttribute("type", "checkbox");
params.labelTag = document.createElement("label");
params.labelTag.appendChild(document.createTextNode(params.label));
let li = document.createElement("li");
li.classList.add("checkbox-container");
params.fragment.appendChild(li);
li.appendChild(params.tag);
li.appendChild(params.labelTag);
dataTypeSupports(params, ["bool"]);
params.tag.addEventListener("change", function() {
for (const elem of this.elements) {
elem.update();
}
}.bind(this));
return this.createElem(params);
}
createOutput(params) {
params.tag = document.createElement("textarea");
params.tag.setAttribute("readonly", true);
dataTypeSupports(params, ["plaintext", "b64", "json-b64"]);
return this.createElem(params);
}
}
class FormElement extends InterfaceElement {
constructor({tag, fragment, advanced=false, form,
value, dataType, placeholder,
labelTag, label="",
enabledFunc,
visibleFunc
}) {
let oriVisibleFunc = visibleFunc;
super({
fragment,
enabledFunc,
visibleFunc: function() {
let res;
if (oriVisibleFunc) {
res = oriVisibleFunc();
} else {
res = true;
}
if (form !== undefined) {
if (advanced) {
if (form.advanced) return res;
else return false;
}
}
return res;
}
});
this.form = form;
this.labelText = label;
if (labelTag === undefined) {
this.labelTag = document.createElement("label");
this.labelTag.appendChild(document.createTextNode(this.labelText));
this.fragment.appendChild(this.labelTag);
this.fragment.appendChild(tag);
} else {
this.labelTag = labelTag;
}
this.clearAlerts = this.clearAlerts.bind(this);
this.handle = tag;
this.dataType = dataType;
this.advanced = advanced;
if (value !== undefined) this.value = value;
if (placeholder !== undefined) this.handle.placeholder = placeholder;
}
get value() {
this.clearAlerts();
switch (this.dataType) {
case "number":
if (this.handle.checkValidity() == false) {
this.alertBox("alert-error", this.handle.validationMessage);
return undefined;
}
return Number(this.handle.value);
case "plaintext":
return this.handle.value;
case "b64":
try {
return b64ToBuf(this.handle.value);
} catch (e) {
this.handleError(Error("Invalid base64 value."));
return undefined;
}
case "json-b64": {
let jsonString;
try {
jsonString = atob(this.handle.value);
} catch (e) {
this.handleError(Error("Invalid base64 value."));
return undefined;
}
try {
return JSON.parse(jsonString);
} catch (e) {
this.handleError(Error("Invalid JSON encoding."));
return undefined;
}
}
case "bool":
return this.handle.checked;
case "category":
return this.handle.value;
default:
return undefined;
}
}
set value(x) {
switch (this.dataType) {
case "number":
case "plaintext":
this.handle.value = x;
break;
case "b64":
this.handle.value = bufToB64(x);
break;
case "json-b64":
this.handle.value = btoa(JSON.stringify(x));
break;
case "bool":
this.handle.checked = x;
break;
case "category":
this.handle.value = x;
break;
}
}
alerts = [];
alertBox(type, message, title) {
// type is alert-error or alert-info
if (this.handle === undefined) {
throw "can not add alert: still undefined";
}
if (this.hidden === true) {
throw "can not add alert: hidden";
}
if (title === undefined) {
switch (type) {
case "alert-info":
title = "Info: ";
break;
case "alert-error":
title = "Error: ";
break;
default:
title = "";
break;
}
}
let box = document.createElement("div");
box.classList.add(type);
box.classList.add("alert");
box.appendChild(document.createTextNode(message));
if (title !== "") {
let titleTag = document.createElement("strong");
titleTag.appendChild(document.createTextNode(title));
box.prepend(titleTag);
}
this.handle.after(box);
this.alerts.push(box);
}
handleError(e, extraInfo="") {
if (extraInfo !== "") {
extraInfo = ` (${extraInfo})`;
}
this.alertBox("alert-error", e.message + extraInfo);
console.error(e);
}
clearAlerts() {
for (const box of this.alerts) {
box.remove();
}
this.alerts = [];
}
}
class Tab extends InterfaceElement {
constructor({form, label=""}) {
super({});
this.form = form;
this.handle = document.createElement("button");
this.fragment.appendChild(this.handle);
this.handle.appendChild(document.createTextNode(label));
}
}
class TabList extends InterfaceElement {
constructor({tag, par=document.body}) {
super({});
this.par = par;
if (tag === undefined) {
this.handle = document.createElement("div");
this.handle.classList.add("tabList");
} else {
this.handle = tag;
}
this.fragment.appendChild(this.handle);
this.mount(par);
}
tabs = [];
#activeForm;
set activeForm(x) {
this.#activeForm = x;
for (const tab of this.tabs) {
if (tab.form !== x) {
tab.form.hidden = true;
tab.handle.classList.remove("active");
} else {
tab.form.hidden = false;
tab.handle.classList.add("active");
}
}
}
get activeForm() {
return this.#activeForm;
}
createForm(params) {
if (params.par === undefined) params.par = this.par;
let form = new Form(params);
let tab = new Tab({
label: params.label,
form: form
});
this.tabs.push(tab);
tab.handle.addEventListener("click", function() {
this.activeForm = form;
}.bind(this));
this.handle.appendChild(tab.fragment);
if (this.activeForm === undefined) this.activeForm = form;
else form.hidden = true;
return form;
}
mountForms() {
for (const tab of this.tabs) {
tab.form.mount(this.par);
}
}
}
export { TabList, FormElement, Form };

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>encryptme: AES encryption/decryption</title>
</head>
<body>
<h1>AES</h1>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>encryptme</title>
</head>
<body>
<h2>Tools</h2>
<h3>Encryption/decryption</h3>
<a href="aes.html">AES</a>
</body>
</html>

View File

@ -1,171 +0,0 @@
body {
max-width: 650px;
margin: 40px auto;
padding: 0 10px;
color: #444444;
background: #eeeeee;
transition: all 0.5s;
}
body, button {
font: 18px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
h1, h2 {
margin-left: -0.5em;
}
textarea {
width: 100%;
height: 15em;
padding-top: 1em;
resize: none;
}
input {
height: 100%;
}
input:not([type]), input[type=password] {
font-family: monospace;
}
.mediumbox {
width: 20em;
height: 5em;
}
label, input, button, textarea {
display: block;
margin-top: 1em;
border: none;
border-radius: 5px;
transition-duration: 0.2s;
}
input, textarea, button {
border: 1px solid #44444444;
}
input:focus, textarea:focus, button:focus {
outline: none;
}
input:focus, textarea:focus {
border: 1px solid #444444aa;
}
textarea:disabled, input:disabled {
background: #ffffff33;
}
.checkbox-container {
list-style: none;
height: fit-content;
}
.checkbox-container label {
display: inline;
margin-left: 0.5em;
}
.checkbox-container input {
display: inline;
height: 1em;
transform: scale(1.25);
}
.alert {
border-radius: 3px;
margin-top: 0.5em;
padding: 0.75em;
background: #ffffff44;
width: 75%;
}
.alert-error {
background: #ffaaaa44;
}
.alert-info {
background: #aaaaff44;
}
p {
text-align: justify;
}
button {
transition-duration: 0.05s;
border: 1px solid #44444444;
}
button:focus {
border: 1px solid #44444477;
}
button:hover {
border: 1px solid #444444aa;
}
button:active {
opacity: 50%;
}
.tabList {
border-radius: 5px;
}
.tabList button {
display: inline;
width: 6em;
margin: 0;
border: 1px solid #44444444;
margin-right: 0.2em;
}
.tabList button:hover {
background: #aaaaaa44;
}
.tabList button.active {
border: none;
background: #0077ff44;
}
.page-header a {
color: #000000;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
body {
color: #c9d1d9;
background: #0d1117;
}
h1, h2, h3, h4, h5, h6 {
color: #c9d1d9;
}
a:link {
color: #58a6ff;
}
a:visited {
color: #8e96f0;
}
textarea, input, button {
background: #1d2127;
color: #c9d1d9;
}
.page-header a {
color: #c9d1d9;
}
}
@media only screen and (max-width: 1000px) {
body {
max-width: 80%;
margin: 40px auto;
}
}
.visualhidden {
opacity: 0;
}
.hidden {
display: none;
}

View File

@ -1,11 +0,0 @@
function generateHeader() {
let header = document.createElement("div");
header.classList.add("page-header");
header.innerHTML = `
<a href="index.html"><h1>encryptme</h1></a>
`;
document.body.prepend(header);
}
export { generateHeader };

View File

@ -1,34 +0,0 @@
/*
Copyright 2023 dogeystamp <dogeystamp@disroot.org>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
function b64ToBuf (b64) {
let ascii = atob(b64);
let buf = new ArrayBuffer(ascii.length);
let bytes = new Uint8Array(buf);
for (var i = 0; i < ascii.length; i++) {
bytes[i] = ascii.charCodeAt(i);
}
return buf;
}
function bufToB64 (buf) {
let bytes = new Uint8Array(buf);
let ascii = "";
for (var i = 0; i < bytes.byteLength; i++) {
ascii += String.fromCharCode(bytes[i]);
}
return btoa(ascii);
}
export { b64ToBuf, bufToB64 };

87
style.css Normal file
View File

@ -0,0 +1,87 @@
body {
max-width: 650px;
margin: 40px auto;
padding: 0 10px;
color: #444444;
background: #eeeeee;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
0% {opacity: 0;}
100% {opacity: 1;}
}
h1, h2, h3, h4, h5, h6 {
color: #666666;
}
body, button {
font: 18px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
textarea {
width: 100%;
height: 15em;
padding-top: 1em;
resize: none;
}
label, input, button, textarea {
display: block;
margin-top: 1em;
border: none;
border-radius: 5px;
transition-duration: 0.2s;
}
input, textarea, button {
border: 1px solid #44444444;
}
input:focus, textarea:focus, button:focus {
outline: none;
}
input:focus, textarea:focus {
border: 1px solid #444444aa;
}
p {
text-align: justify;
}
button {
transition-duration: 0.05s;
border: 1px solid #44444444;
}
button:hover {
border: 1px solid #444444aa;
}
button:active {
opacity: 50%;
}
@media (prefers-color-scheme: dark) {
body {
color: #c9d1d9;
background: #0d1117;
}
h1, h2, h3, h4, h5, h6 {
color: #c9d1d9;
}
a:link {
color: #58a6ff;
}
a:visited {
color: #8e96f0;
}
textarea, input, button {
background: #1d2127;
color: #c9d1d9;
}
}

View File

@ -1,78 +0,0 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const SitemapPlugin = require("sitemap-webpack-plugin").default;
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const pages = [
{
id: "index",
desc: "Easy to use and simple online tools for encryption and decryption.",
changefreq: "weekly",
priority: 1.0,
},
{
id: "aes",
desc: "Secure and simple tool for AES, with control over all advanced options like key size, salt, AES mode, and others.",
changefreq: "weekly",
priority: 0.7,
},
];
module.exports = {
entry: pages.reduce((config, page) => {
config[page.id] = `./src/${page.id}.js`;
return config;
}, {}),
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
optimization: {
splitChunks: {
chunks: "all",
},
},
plugins: [
new MiniCssExtractPlugin({
filename: "[contenthash].css",
chunkFilename: "[id].[contenthash].css",
}),
new SitemapPlugin({
base: "https://encryptme.net",
paths: pages.map(
(page) => ({
path: `/${page.id}.html`,
changefreq: page.changefreq,
priority: page.priority,
})
),
options: {
lastmod: true,
},
})
].concat(
pages.map(
(page) =>
new HtmlWebpackPlugin({
inject: "body",
title: `encryptme: ${page.title}`,
meta: {
viewport: "width=device-width, initial-scale=1, shrink-to-fit=no",
description: page.desc
},
filename: `${page.id}.html`,
template: `./src/pages/${page.id}.html`,
chunks: [page.id],
})
)
),
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
};

View File

@ -1,10 +0,0 @@
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
static: "./dist",
},
});

View File

@ -1,13 +0,0 @@
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = merge(common, {
mode: "production",
optimization: {
minimizer: [
"...",
new CssMinimizerPlugin(),
]
}
});