Skip to content

Commit c2a0b61

Browse files
committed
[feat] more how to pages: encryption, signing, generating keypairs, crypto primer, hashing
1 parent 74371c4 commit c2a0b61

17 files changed

Lines changed: 1499 additions & 9 deletions

docs/_includes/head_custom.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,17 @@
174174
{% elsif page.title == "How TLS Connections Work" %}
175175
<title>TEDI | Learn How TLS Connections work</title>
176176

177-
178-
177+
{% elsif page.title == "Hash Generator" %}
178+
<title>TEDI | Hash a string with SHA-1, SHA-256, SHA-384, SHA-512</title>
179+
180+
{% elsif page.title == "Cryptography Primer" %}
181+
<title>TEDI | Cryptography primer for signing, hashing, and encryption</title>
182+
183+
{% elsif page.title == "Encrypt and Decrypt" %}
184+
<title>TEDI | Encrypt and decrypt with AES and RSA algorithms</title>
179185

186+
{% elsif page.title == "Sign and Verify Tester" %}
187+
<title>TEDI | Digitally sign and verify messages using RSA, HMAC, Ed25519, ECDSA</title>
180188

181189

182190
{% else %}

docs/assets/js/encryption.js

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
document.addEventListener('DOMContentLoaded', function () {
2+
const input = document.getElementById('cryptoInput');
3+
const actionButton = document.getElementById('cryptoAction');
4+
const output = document.getElementById('cryptoOutput');
5+
const generateIVBtn = document.getElementById('generateIVBtn');
6+
const generateRSAKeyBtn = document.getElementById('generateRSAKeyBtn');
7+
const ivInput = document.getElementById('cryptoIV');
8+
const aesSecretInput = document.getElementById('aesSecret');
9+
const rsaPublicKeyInput = document.getElementById('rsaPublicKey');
10+
const rsaPrivateKeyInput = document.getElementById('rsaPrivateKey');
11+
const aesKeyFields = document.getElementById('aesKeyFields');
12+
const rsaKeyFields = document.getElementById('rsaKeyFields');
13+
14+
const modeRadios = document.querySelectorAll('input[name="mode"]');
15+
const algorithmSelect = document.getElementById('cryptoAlgorithm');
16+
17+
const symmetricAlgorithms = [
18+
{ value: "AES-128-CBC", label: "AES-128-CBC" },
19+
{ value: "AES-192-CBC", label: "AES-192-CBC" },
20+
{ value: "AES-256-CBC", label: "AES-256-CBC" },
21+
{ value: "AES-256-GCM", label: "AES-256-GCM (WebCrypto)" }
22+
];
23+
24+
const asymmetricAlgorithms = [
25+
{ value: "RSA-OAEP", label: "RSA-OAEP" }
26+
];
27+
28+
// Setup initial algorithm list
29+
updateAlgorithmOptions('symmetric');
30+
31+
// Update algorithm options and fields when Mode changes
32+
modeRadios.forEach(radio => {
33+
radio.addEventListener('change', () => {
34+
const mode = document.querySelector('input[name="mode"]:checked').value;
35+
updateAlgorithmOptions(mode);
36+
37+
if (mode === 'symmetric') {
38+
aesKeyFields.style.display = 'flex';
39+
rsaKeyFields.style.display = 'none';
40+
} else {
41+
aesKeyFields.style.display = 'none';
42+
rsaKeyFields.style.display = 'flex';
43+
}
44+
45+
ivInput.value = '';
46+
});
47+
});
48+
49+
function updateAlgorithmOptions(mode) {
50+
algorithmSelect.innerHTML = '';
51+
const options = (mode === 'symmetric') ? symmetricAlgorithms : asymmetricAlgorithms;
52+
options.forEach(opt => {
53+
const option = document.createElement('option');
54+
option.value = opt.value;
55+
option.textContent = opt.label;
56+
algorithmSelect.appendChild(option);
57+
});
58+
}
59+
60+
// Fix initial visibility on page load
61+
(function setInitialModeFields() {
62+
const initialMode = document.querySelector('input[name="mode"]:checked')?.value || 'symmetric';
63+
updateAlgorithmOptions(initialMode);
64+
65+
if (initialMode === 'symmetric') {
66+
aesKeyFields.style.display = 'flex';
67+
rsaKeyFields.style.display = 'none';
68+
} else {
69+
aesKeyFields.style.display = 'none';
70+
rsaKeyFields.style.display = 'flex';
71+
}
72+
})();
73+
74+
generateIVBtn?.addEventListener('click', () => {
75+
const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 96 bits for GCM
76+
ivInput.value = Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join('');
77+
output.value = "✅ Random IV generated.";
78+
});
79+
80+
generateRSAKeyBtn?.addEventListener('click', async () => {
81+
try {
82+
output.value = "⏳ Generating RSA keypair. Please wait this make take 5-10 seconds...";
83+
await new Promise(resolve => setTimeout(resolve, 100)); // Let browser update output field first
84+
85+
const rsaKeypair = KEYUTIL.generateKeypair("RSA", 2048);
86+
const privateKeyPEM = KEYUTIL.getPEM(rsaKeypair.prvKeyObj, "PKCS8PRV");
87+
const publicKeyPEM = KEYUTIL.getPEM(rsaKeypair.pubKeyObj);
88+
89+
rsaPublicKeyInput.value = publicKeyPEM;
90+
rsaPrivateKeyInput.value = privateKeyPEM;
91+
92+
output.value = "✅ New RSA Public and Private Keys generated.";
93+
} catch (err) {
94+
output.value = `❌ Error generating RSA key: ${err.message}`;
95+
}
96+
});
97+
98+
actionButton.addEventListener('click', async (e) => {
99+
e.preventDefault();
100+
101+
actionButton.disabled = true;
102+
const originalText = actionButton.textContent;
103+
actionButton.textContent = "Running...";
104+
105+
const mode = document.querySelector('input[name="mode"]:checked')?.value;
106+
const operation = document.querySelector('input[name="operation"]:checked')?.value;
107+
const algorithm = document.getElementById('cryptoAlgorithm').value;
108+
const encoding = document.querySelector('input[name="outputEncoding"]:checked')?.value || 'hex';
109+
110+
const text = input.value.trim();
111+
let key = '';
112+
113+
if (mode === 'symmetric') {
114+
key = aesSecretInput.value.trim();
115+
} else if (mode === 'asymmetric') {
116+
key = (operation === 'encrypt')
117+
? rsaPublicKeyInput.value.trim()
118+
: rsaPrivateKeyInput.value.trim();
119+
}
120+
121+
let ivHex = ivInput.value.trim();
122+
123+
if (!text || !key) {
124+
output.value = "❌ Please provide both text and key.";
125+
actionButton.disabled = false;
126+
actionButton.textContent = originalText;
127+
return;
128+
}
129+
130+
if (algorithm.startsWith('AES')) {
131+
if (operation === 'encrypt' && !ivHex) {
132+
const generatedIV = window.crypto.getRandomValues(new Uint8Array(12));
133+
ivHex = Array.from(generatedIV).map(b => b.toString(16).padStart(2, '0')).join('');
134+
ivInput.value = ivHex;
135+
output.value = "✅ Random IV auto-generated.";
136+
}
137+
if (operation === 'decrypt' && !ivHex) {
138+
output.value = "❌ IV is required for decryption.";
139+
actionButton.disabled = false;
140+
actionButton.textContent = originalText;
141+
return;
142+
}
143+
}
144+
145+
try {
146+
let result = '';
147+
148+
if (mode === 'symmetric') {
149+
result = (operation === 'encrypt')
150+
? await symmetricEncrypt(text, key, algorithm, encoding, ivHex)
151+
: await symmetricDecrypt(text, key, algorithm, ivHex);
152+
} else if (mode === 'asymmetric') {
153+
result = (operation === 'encrypt')
154+
? rsaEncrypt(text, key, encoding)
155+
: rsaDecrypt(text, key);
156+
}
157+
158+
output.value = result;
159+
} catch (err) {
160+
output.value = `❌ Error: ${err.message}`;
161+
} finally {
162+
actionButton.disabled = false;
163+
actionButton.textContent = originalText;
164+
}
165+
});
166+
167+
async function symmetricEncrypt(plainText, password, algorithm, encoding, ivHex) {
168+
if (algorithm.includes('CBC')) {
169+
const keySizeBits = parseInt(algorithm.split('-')[1]); // 128/192/256
170+
return aesCbcEncrypt(plainText, password, encoding, ivHex, keySizeBits);
171+
} else if (algorithm.includes('GCM')) {
172+
return aesGcmEncrypt(plainText, password, encoding, ivHex, 32); // AES-256-GCM
173+
}
174+
throw new Error('Unsupported symmetric algorithm.');
175+
}
176+
177+
async function symmetricDecrypt(cipherInput, password, algorithm, ivHex) {
178+
if (algorithm.includes('CBC')) {
179+
const keySizeBits = parseInt(algorithm.split('-')[1]); // 128/192/256
180+
return aesCbcDecrypt(cipherInput, password, ivHex, keySizeBits);
181+
} else if (algorithm.includes('GCM')) {
182+
return aesGcmDecrypt(cipherInput, password, ivHex);
183+
}
184+
throw new Error('Unsupported symmetric algorithm.');
185+
}
186+
187+
function aesCbcEncrypt(plainText, password, encoding, ivHex, keySizeBits) {
188+
const iv = CryptoJS.enc.Hex.parse(ivHex);
189+
const key = CryptoJS.PBKDF2(password, iv, {
190+
keySize: keySizeBits / 32,
191+
iterations: 1000
192+
});
193+
194+
const options = {
195+
mode: CryptoJS.mode.CBC,
196+
padding: CryptoJS.pad.Pkcs7,
197+
iv: iv
198+
};
199+
200+
const encrypted = CryptoJS.AES.encrypt(plainText, key, options);
201+
const ciphertext = encrypted.ciphertext;
202+
return (encoding === 'base64') ? CryptoJS.enc.Base64.stringify(ciphertext) : ciphertext.toString(CryptoJS.enc.Hex);
203+
}
204+
205+
function aesCbcDecrypt(cipherInput, password, ivHex, keySizeBits) {
206+
let cipherData;
207+
try {
208+
cipherData = /^[0-9a-f]+$/i.test(cipherInput)
209+
? CryptoJS.enc.Hex.parse(cipherInput)
210+
: CryptoJS.enc.Base64.parse(cipherInput);
211+
} catch {
212+
throw new Error('Invalid ciphertext format.');
213+
}
214+
215+
const iv = CryptoJS.enc.Hex.parse(ivHex);
216+
const key = CryptoJS.PBKDF2(password, iv, {
217+
keySize: keySizeBits / 32,
218+
iterations: 1000
219+
});
220+
221+
const options = {
222+
mode: CryptoJS.mode.CBC,
223+
padding: CryptoJS.pad.Pkcs7,
224+
iv: iv
225+
};
226+
227+
const decrypted = CryptoJS.AES.decrypt({ ciphertext: cipherData }, key, options);
228+
const plaintext = decrypted.toString(CryptoJS.enc.Utf8);
229+
if (!plaintext) {
230+
throw new Error('Decryption failed. Incorrect password, IV, or corrupted data.');
231+
}
232+
return plaintext;
233+
}
234+
235+
async function aesGcmEncrypt(plainText, password, encoding, ivHex, keyLengthBytes) {
236+
const enc = new TextEncoder();
237+
const iv = hexStringToUint8Array(ivHex);
238+
239+
const keyMaterial = await window.crypto.subtle.importKey(
240+
'raw',
241+
await pbkdf2(password, iv, keyLengthBytes),
242+
{ name: 'AES-GCM' },
243+
false,
244+
['encrypt']
245+
);
246+
247+
const encrypted = await window.crypto.subtle.encrypt(
248+
{ name: 'AES-GCM', iv: iv },
249+
keyMaterial,
250+
enc.encode(plainText)
251+
);
252+
253+
return (encoding === 'base64') ? arrayBufferToBase64(encrypted) : arrayBufferToHex(encrypted);
254+
}
255+
256+
async function aesGcmDecrypt(cipherInput, password, ivHex) {
257+
const raw = isHex(cipherInput)
258+
? hexStringToUint8Array(cipherInput)
259+
: base64ToUint8Array(cipherInput);
260+
261+
const iv = hexStringToUint8Array(ivHex);
262+
263+
const keyMaterial = await window.crypto.subtle.importKey(
264+
'raw',
265+
await pbkdf2(password, iv, 32),
266+
{ name: 'AES-GCM' },
267+
false,
268+
['decrypt']
269+
);
270+
271+
const decrypted = await window.crypto.subtle.decrypt(
272+
{ name: 'AES-GCM', iv: iv },
273+
keyMaterial,
274+
raw
275+
);
276+
277+
return new TextDecoder().decode(decrypted);
278+
}
279+
280+
function rsaEncrypt(plainText, pemKey, encoding) {
281+
const keyObj = KEYUTIL.getKey(pemKey);
282+
const pubKeyObj = keyObj.isPrivate ? KEYUTIL.getKey(KEYUTIL.getPEM(keyObj, "PKCS8PUB")) : keyObj;
283+
const encHex = KJUR.crypto.Cipher.encrypt(plainText, pubKeyObj);
284+
return encoding === 'base64' ? hextob64(encHex) : encHex;
285+
}
286+
287+
function rsaDecrypt(cipherHexOrB64, pemPrivateKey) {
288+
const prv = KEYUTIL.getKey(pemPrivateKey);
289+
const cipherHex = isBase64(cipherHexOrB64) ? b64tohex(cipherHexOrB64) : cipherHexOrB64;
290+
return KJUR.crypto.Cipher.decrypt(cipherHex, prv);
291+
}
292+
293+
function isBase64(str) {
294+
return /^[A-Za-z0-9+/]+={0,2}$/.test(str.replace(/\s+/g, ''));
295+
}
296+
297+
function isHex(str) {
298+
return /^[0-9a-fA-F]+$/.test(str);
299+
}
300+
301+
function arrayBufferToHex(buffer) {
302+
return Array.from(new Uint8Array(buffer))
303+
.map(b => b.toString(16).padStart(2, '0'))
304+
.join('');
305+
}
306+
307+
function arrayBufferToBase64(buffer) {
308+
const binary = String.fromCharCode(...new Uint8Array(buffer));
309+
return btoa(binary);
310+
}
311+
312+
function base64ToUint8Array(base64) {
313+
const binary = atob(base64);
314+
const bytes = new Uint8Array(binary.length);
315+
for (let i = 0; i < binary.length; i++) {
316+
bytes[i] = binary.charCodeAt(i);
317+
}
318+
return bytes;
319+
}
320+
321+
function hexStringToUint8Array(hexString) {
322+
if (hexString.length % 2 !== 0) {
323+
throw new Error('Invalid hex string');
324+
}
325+
const arrayBuffer = new Uint8Array(hexString.length / 2);
326+
for (let i = 0; i < hexString.length; i += 2) {
327+
arrayBuffer[i / 2] = parseInt(hexString.substr(i, 2), 16);
328+
}
329+
return arrayBuffer;
330+
}
331+
332+
async function pbkdf2(password, salt, lengthBytes) {
333+
const enc = new TextEncoder();
334+
const keyMaterial = await window.crypto.subtle.importKey(
335+
'raw',
336+
enc.encode(password),
337+
{ name: 'PBKDF2' },
338+
false,
339+
['deriveBits', 'deriveKey']
340+
);
341+
const keyBits = await window.crypto.subtle.deriveBits(
342+
{
343+
name: 'PBKDF2',
344+
salt: salt,
345+
iterations: 1000,
346+
hash: 'SHA-256'
347+
},
348+
keyMaterial,
349+
lengthBytes * 8
350+
);
351+
return keyBits;
352+
}
353+
});

0 commit comments

Comments
 (0)