Skip to content

Commit 4b7b76c

Browse files
authored
Add files via upload
offline encrypter tool
1 parent 6bc208d commit 4b7b76c

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>EncrypterMod — Offline AES-GCM Encryptor</title>
6+
<meta name="viewport" content="width=device-width,initial-scale=1" />
7+
<style>
8+
body{font-family:system-ui, -apple-system, "Segoe UI", Roboto, Arial;max-width:900px;margin:24px auto;padding:0 18px;color:#111}
9+
label{display:block;margin-top:12px;font-weight:600}
10+
input, textarea, select{width:100%;padding:8px;margin-top:6px;border-radius:6px;border:1px solid #ddd;box-sizing:border-box}
11+
button{padding:10px 14px;border-radius:8px;border:0;background:#0b6efd;color:#fff;font-weight:600;cursor:pointer;margin-top:10px}
12+
pre{background:#f6f8fa;padding:12px;border-radius:8px;overflow:auto;white-space:pre-wrap;word-break:break-word}
13+
.row{display:flex;gap:10px}
14+
.col{flex:1}
15+
.note{color:#666;font-size:13px}
16+
</style>
17+
</head>
18+
<body>
19+
<h1>EncrypterMod — Offline AES-GCM Encryptor</h1>
20+
<p class="note">Encrypts plaintext into IV+ciphertext+tag blob compatible with the mod/decryptor. Works fully offline in the browser.</p>
21+
22+
<label>Username (used in ID)</label>
23+
<input id="username" placeholder="yourname123">
24+
25+
<label>Key format</label>
26+
<select id="keyFormat">
27+
<option value="text">Raw text key (UTF-8) — paste exactly what's in config/key.json</option>
28+
<option value="base64">Base64 key (URL-safe or normal)</option>
29+
</select>
30+
31+
<label>Key</label>
32+
<input id="keyInput" type="password" placeholder="Paste AES key (raw text or base64)">
33+
34+
<label>Message to encrypt</label>
35+
<textarea id="plaintext" rows="4" placeholder="Type message here..."></textarea>
36+
37+
<div style="margin-top:10px">
38+
<button id="genKey">Generate random 32-byte key (Base64)</button>
39+
<button id="encryptBtn">Encrypt & Generate Chat Line</button>
40+
<button id="clearBtn" style="background:#6c757d;margin-left:8px">Clear</button>
41+
</div>
42+
43+
<div id="outputArea" style="margin-top:14px;display:none">
44+
<label>Chat line (copy this into Minecraft chat)</label>
45+
<pre id="outputPre"></pre>
46+
<button id="copyBtn">Copy to clipboard</button>
47+
</div>
48+
49+
<div class="note" style="margin-top:12px">
50+
IMPORTANT: If your mod key in <code>config/encrypter/key.json</code> is a textual string like <code>1234567890abcdef1234567890abcdef</code>, choose <strong>Raw text key</strong> and paste that exact string. Do not paste screenshots / OCR text.
51+
</div>
52+
53+
<script>
54+
/* helpers */
55+
function uint8ToBase64Url(uint8) {
56+
// safe conversion -> use chunks to avoid call limit
57+
let CHUNK = 0x8000;
58+
let index = 0;
59+
let result = '';
60+
while (index < uint8.length) {
61+
let slice = uint8.subarray(index, Math.min(index + CHUNK, uint8.length));
62+
result += String.fromCharCode.apply(null, slice);
63+
index += CHUNK;
64+
}
65+
let b64 = btoa(result);
66+
return b64.replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
67+
}
68+
function base64UrlToUint8(base64Url) {
69+
let b64 = base64Url.replace(/-/g,'+').replace(/_/g,'/');
70+
while (b64.length % 4 !== 0) b64 += '=';
71+
const binary = atob(b64);
72+
const len = binary.length;
73+
const bytes = new Uint8Array(len);
74+
for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
75+
return bytes;
76+
}
77+
function hexToUint8(hex) {
78+
hex = hex.replace(/\s+/g,'');
79+
if (hex.length % 2 !== 0) throw new Error('Invalid hex length');
80+
const arr = new Uint8Array(hex.length/2);
81+
for (let i=0;i<hex.length;i+=2) arr[i/2] = parseInt(hex.substring(i,i+2),16);
82+
return arr;
83+
}
84+
function uuidv4() {
85+
// crypto-based v4
86+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,c=>{
87+
const r = crypto.getRandomValues(new Uint8Array(1))[0] & 15;
88+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
89+
return v.toString(16);
90+
});
91+
}
92+
93+
/* import key helpers */
94+
async function importKeyFromInput(keyStr, format) {
95+
let keyBytes;
96+
if (format === 'text') {
97+
keyBytes = new TextEncoder().encode(keyStr);
98+
} else { // base64
99+
keyBytes = base64UrlToUint8(keyStr);
100+
}
101+
if (![16,24,32].includes(keyBytes.length)) {
102+
throw new Error('Key must be 16, 24 or 32 bytes (after decoding). Found: ' + keyBytes.length);
103+
}
104+
return crypto.subtle.importKey('raw', keyBytes.buffer, {name:'AES-GCM'}, false, ['encrypt']);
105+
}
106+
107+
/* main encrypt function */
108+
document.getElementById('encryptBtn').addEventListener('click', async () => {
109+
try {
110+
const username = document.getElementById('username').value.trim();
111+
let keyStr = document.getElementById('keyInput').value.trim();
112+
const format = document.getElementById('keyFormat').value;
113+
let plaintext = document.getElementById('plaintext').value;
114+
if (plaintext == null || plaintext.length === 0) throw new Error('Message empty');
115+
if (!keyStr) throw new Error('No key provided');
116+
if (!username) throw new Error('Provide username');
117+
118+
// import key
119+
const keyObj = await importKeyFromInput(keyStr, format);
120+
121+
// create IV
122+
const iv = crypto.getRandomValues(new Uint8Array(12));
123+
124+
// encrypt (explicit tagLength)
125+
const enc = new TextEncoder().encode(plaintext);
126+
const cipherBuffer = await crypto.subtle.encrypt({name:'AES-GCM', iv:iv, tagLength:128}, keyObj, enc);
127+
const cipherBytes = new Uint8Array(cipherBuffer);
128+
129+
// compose output IV + ciphertext+tag
130+
const out = new Uint8Array(iv.length + cipherBytes.length);
131+
out.set(iv, 0);
132+
out.set(cipherBytes, iv.length);
133+
134+
const blob = uint8ToBase64Url(out);
135+
136+
const id = username + '_' + uuidv4();
137+
const finalLine = `[EncrypterMod] [ID: ${id}] ${blob}`;
138+
139+
document.getElementById('outputPre').textContent = finalLine;
140+
document.getElementById('outputArea').style.display = 'block';
141+
} catch (e) {
142+
alert('Error: ' + (e && e.message ? e.message : e));
143+
}
144+
});
145+
146+
/* copy/download/gen key */
147+
document.getElementById('copyBtn').addEventListener('click', async () => {
148+
try {
149+
await navigator.clipboard.writeText(document.getElementById('outputPre').textContent);
150+
alert('Copied!');
151+
} catch {
152+
alert('Clipboard copy failed — select the text and copy manually.');
153+
}
154+
});
155+
document.getElementById('genKey').addEventListener('click', () => {
156+
const rand = crypto.getRandomValues(new Uint8Array(32));
157+
const b64 = uint8ToBase64Url(rand);
158+
document.getElementById('keyFormat').value = 'base64';
159+
document.getElementById('keyInput').value = b64;
160+
alert('Generated 32-byte key (Base64). Use Raw text only if your mod uses printable ASCII key.');
161+
});
162+
document.getElementById('clearBtn').addEventListener('click', () => {
163+
document.getElementById('plaintext').value = '';
164+
document.getElementById('keyInput').value = '';
165+
document.getElementById('username').value = '';
166+
document.getElementById('outputArea').style.display = 'none';
167+
});
168+
</script>
169+
</body>
170+
</html>

0 commit comments

Comments
 (0)