Choose a message number, then encrypt it. Hit Encrypt again and watch a different ciphertext appear for the same message — because a fresh random k is picked every time. Decryption always recovers the original.
<p class="hint">
ElGamal encryption uses small toy numbers here (p=23, g=5, private key x=6, public key h=g<sup>x</sup>=8). Pick a message (1–22), encrypt it, then encrypt again — you get a <b>different ciphertext</b> each time because a fresh random k is chosen. Decryption always recovers the original.
</p>
<div class="param-row">
<span class="param">p = 23</span>
<span class="param">g = 5</span>
<span class="param">x = 6 (private)</span>
<span class="param">h = g<sup>x</sup> mod p = 8 (public)</span>
</div>
<div class="controls">
<label for="msg">Message m (1–22):</label>
<input id="msg" type="number" min="1" max="22" value="7">
<button id="btnEncrypt" type="button">Encrypt</button>
<button id="btnAgain" type="button">Encrypt again</button>
</div>
<div id="steps" class="steps"></div>
<div id="status" class="status"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.param-row { display: flex; flex-wrap: wrap; gap: .4rem .9rem; margin: 0 0 .9rem; }
.param { font: 600 .82rem ui-monospace, monospace; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; padding: .15rem .45rem;
border-radius: 5px; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .9rem; }
label { font-size: .9rem; }
input[type=number] { width: 5rem; padding: .35rem .5rem; font-size: .9rem;
border: 1px solid #adb1b8; border-radius: 6px; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button:hover { background: #16294a; }
.steps { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .7rem; min-height: 3.5rem; }
.step { background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .45rem .7rem; font-size: .88rem; line-height: 1.55; }
.step b { color: #1d3557; }
.step.hi { background: #fff8e1; border-color: #f0c040; }
.step.ok { background: #e6f4ea; border-color: #5cb85c; }
.step.diff { background: #fde8ec; border-color: #e63946; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ElGamal encryption demo (toy group: p=23, g=5, x=6, h=8).
// Security relies on the discrete log problem: given g and h=g^x mod p,
// find x. For real primes (2048-bit) this is computationally infeasible.
// Probabilistic: fresh random k each encryption => different (c1,c2) every time.
const P = 23, G = 5, X = 6; // prime, generator, private key
const H = modpow(G, X, P); // public key h = g^x mod p = 8
function modpow(base, exp, mod) {
let result = 1n;
base = BigInt(base) % BigInt(mod);
exp = BigInt(exp);
mod = BigInt(mod);
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod;
exp = exp >> 1n;
base = base * base % mod;
}
return Number(result);
}
function modinv(a, m) {
// Extended Euclidean algorithm for modular inverse
let [old_r, r] = [a, m], [old_s, s] = [1, 0];
while (r !== 0) {
const q = Math.floor(old_r / r);
[old_r, r] = [r, old_r - q * r];
[old_s, s] = [s, old_s - q * s];
}
return ((old_s % m) + m) % m;
}
function encrypt(m, k) {
const c1 = modpow(G, k, P); // c1 = g^k mod p
const s = modpow(H, k, P); // s = h^k mod p (shared secret)
const c2 = (m * s) % P; // c2 = m * s mod p
return { c1, c2, s, k };
}
function decrypt(c1, c2) {
const s = modpow(c1, X, P); // s = c1^x mod p = g^(kx) mod p
const si = modinv(s, P); // s^{-1} mod p
const m = (c2 * si) % P; // m = c2 * s^{-1} mod p
return { s, si, m };
}
let lastC1 = null, lastC2 = null;
function doEncrypt() {
const mVal = parseInt(document.getElementById('msg').value, 10);
if (isNaN(mVal) || mVal < 1 || mVal > 22) {
setStatus(`Choose m between 1 and 22.`, ''); return;
}
// random k in [1, p-2]
const k = 1 + Math.floor(Math.random() * (P - 2));
const { c1, c2, s } = encrypt(mVal, k);
const stepsEl = document.getElementById('steps');
const isRepeat = lastC1 !== null;
const prevC1 = lastC1, prevC2 = lastC2;
lastC1 = c1; lastC2 = c2;
const dec = decrypt(c1, c2);
stepsEl.innerHTML = '';
function addStep(html, cls) {
const d = document.createElement('div');
d.className = 'step' + (cls ? ' ' + cls : '');
d.innerHTML = html;
stepsEl.appendChild(d);
}
addStep(fmt(`<b>Random k</b> = %k% (chosen freshly at random each time)`, { k: k }));
addStep(`<b>c<sub>1</sub></b> = g<sup>k</sup> mod p = ${G}<sup>${k}</sup> mod ${P} = <b>${c1}</b>`);
addStep(fmt(`<b>Shared secret s</b> = h<sup>k</sup> mod p = %H%<sup>%k%</sup> mod %P% = <b>%s%</b>`, { H: H, k: k, P: P, s: s }));
addStep(`<b>c<sub>2</sub></b> = m · s mod p = ${mVal} · ${s} mod ${P} = <b>${c2}</b>`);
if (isRepeat) {
const changed = (c1 !== prevC1 || c2 !== prevC2);
addStep(
fmt(`Previous ciphertext: (<b>%prevC1%, %prevC2%</b>)<br>
New ciphertext: (<b>%c1%, %c2%</b>)`, { prevC1: prevC1, prevC2: prevC2, c1: c1, c2: c2 }),
changed ? 'diff' : 'hi'
);
if (changed) {
setStatus(`Different ciphertext for the same message!`, 'ok');
} else {
setStatus(`Same k happened by chance — try again!`, '');
}
}
addStep(
fmt(`<b>Decrypt:</b> s = c<sub>1</sub><sup>x</sup> mod p = %c1%<sup>%X%</sup> mod %P% = %s%,
s<sup>-1</sup> mod p = %si%,
m = c<sub>2</sub> · s<sup>-1</sup> mod p = %c2% · %si% mod %P% = <b>%m%</b>`, { c1: c1, X: X, P: P, s: dec.s, si: dec.si, c2: c2, m: dec.m }),
'ok'
);
if (dec.m !== mVal) {
setStatus(`Decryption mismatch — bug!`, 'bad');
} else if (!isRepeat) {
setStatus(fmt(`Encrypted! Ciphertext = (%c1%, %c2%). Decrypted back to %m%.`, { c1: c1, c2: c2, m: dec.m }), 'ok');
}
// Log to console for verification
console.log(`ElGamal | m=${mVal} k=${k} => (c1=${c1},c2=${c2}) dec=${dec.m} ok=${dec.m===mVal}`);
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + cls;
}
document.getElementById('btnEncrypt').addEventListener('click', () => {
lastC1 = null; lastC2 = null;
document.getElementById('steps').innerHTML = '';
setStatus('', '');
doEncrypt();
});
document.getElementById('btnAgain').addEventListener('click', doEncrypt);
<p class="hint">
ElGamal encryption uses small toy numbers here (p=23, g=5, private key x=6, public key h=g<sup>x</sup>=8). Pick a message (1–22), encrypt it, then encrypt again — you get a <b>different ciphertext</b> each time because a fresh random k is chosen. Decryption always recovers the original.
</p>
<div class="param-row">
<span class="param">p = 23</span>
<span class="param">g = 5</span>
<span class="param">x = 6 (private)</span>
<span class="param">h = g<sup>x</sup> mod p = 8 (public)</span>
</div>
<div class="controls">
<label for="msg">Message m (1–22):</label>
<input id="msg" type="number" min="1" max="22" value="7">
<button id="btnEncrypt" type="button">Encrypt</button>
<button id="btnAgain" type="button">Encrypt again</button>
</div>
<div id="steps" class="steps"></div>
<div id="status" class="status"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.param-row { display: flex; flex-wrap: wrap; gap: .4rem .9rem; margin: 0 0 .9rem; }
.param { font: 600 .82rem ui-monospace, monospace; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; padding: .15rem .45rem;
border-radius: 5px; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .9rem; }
label { font-size: .9rem; }
input[type=number] { width: 5rem; padding: .35rem .5rem; font-size: .9rem;
border: 1px solid #adb1b8; border-radius: 6px; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button:hover { background: #16294a; }
.steps { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .7rem; min-height: 3.5rem; }
.step { background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .45rem .7rem; font-size: .88rem; line-height: 1.55; }
.step b { color: #1d3557; }
.step.hi { background: #fff8e1; border-color: #f0c040; }
.step.ok { background: #e6f4ea; border-color: #5cb85c; }
.step.diff { background: #fde8ec; border-color: #e63946; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ElGamal encryption demo (toy group: p=23, g=5, x=6, h=8).
// Security relies on the discrete log problem: given g and h=g^x mod p,
// find x. For real primes (2048-bit) this is computationally infeasible.
// Probabilistic: fresh random k each encryption => different (c1,c2) every time.
const P = 23, G = 5, X = 6; // prime, generator, private key
const H = modpow(G, X, P); // public key h = g^x mod p = 8
function modpow(base, exp, mod) {
let result = 1n;
base = BigInt(base) % BigInt(mod);
exp = BigInt(exp);
mod = BigInt(mod);
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod;
exp = exp >> 1n;
base = base * base % mod;
}
return Number(result);
}
function modinv(a, m) {
// Extended Euclidean algorithm for modular inverse
let [old_r, r] = [a, m], [old_s, s] = [1, 0];
while (r !== 0) {
const q = Math.floor(old_r / r);
[old_r, r] = [r, old_r - q * r];
[old_s, s] = [s, old_s - q * s];
}
return ((old_s % m) + m) % m;
}
function encrypt(m, k) {
const c1 = modpow(G, k, P); // c1 = g^k mod p
const s = modpow(H, k, P); // s = h^k mod p (shared secret)
const c2 = (m * s) % P; // c2 = m * s mod p
return { c1, c2, s, k };
}
function decrypt(c1, c2) {
const s = modpow(c1, X, P); // s = c1^x mod p = g^(kx) mod p
const si = modinv(s, P); // s^{-1} mod p
const m = (c2 * si) % P; // m = c2 * s^{-1} mod p
return { s, si, m };
}
let lastC1 = null, lastC2 = null;
function doEncrypt() {
const mVal = parseInt(document.getElementById('msg').value, 10);
if (isNaN(mVal) || mVal < 1 || mVal > 22) {
setStatus(`Choose m between 1 and 22.`, ''); return;
}
// random k in [1, p-2]
const k = 1 + Math.floor(Math.random() * (P - 2));
const { c1, c2, s } = encrypt(mVal, k);
const stepsEl = document.getElementById('steps');
const isRepeat = lastC1 !== null;
const prevC1 = lastC1, prevC2 = lastC2;
lastC1 = c1; lastC2 = c2;
const dec = decrypt(c1, c2);
stepsEl.innerHTML = '';
function addStep(html, cls) {
const d = document.createElement('div');
d.className = 'step' + (cls ? ' ' + cls : '');
d.innerHTML = html;
stepsEl.appendChild(d);
}
addStep(fmt(`<b>Random k</b> = %k% (chosen freshly at random each time)`, { k: k }));
addStep(`<b>c<sub>1</sub></b> = g<sup>k</sup> mod p = ${G}<sup>${k}</sup> mod ${P} = <b>${c1}</b>`);
addStep(fmt(`<b>Shared secret s</b> = h<sup>k</sup> mod p = %H%<sup>%k%</sup> mod %P% = <b>%s%</b>`, { H: H, k: k, P: P, s: s }));
addStep(`<b>c<sub>2</sub></b> = m · s mod p = ${mVal} · ${s} mod ${P} = <b>${c2}</b>`);
if (isRepeat) {
const changed = (c1 !== prevC1 || c2 !== prevC2);
addStep(
fmt(`Previous ciphertext: (<b>%prevC1%, %prevC2%</b>)<br>
New ciphertext: (<b>%c1%, %c2%</b>)`, { prevC1: prevC1, prevC2: prevC2, c1: c1, c2: c2 }),
changed ? 'diff' : 'hi'
);
if (changed) {
setStatus(`Different ciphertext for the same message!`, 'ok');
} else {
setStatus(`Same k happened by chance — try again!`, '');
}
}
addStep(
fmt(`<b>Decrypt:</b> s = c<sub>1</sub><sup>x</sup> mod p = %c1%<sup>%X%</sup> mod %P% = %s%,
s<sup>-1</sup> mod p = %si%,
m = c<sub>2</sub> · s<sup>-1</sup> mod p = %c2% · %si% mod %P% = <b>%m%</b>`, { c1: c1, X: X, P: P, s: dec.s, si: dec.si, c2: c2, m: dec.m }),
'ok'
);
if (dec.m !== mVal) {
setStatus(`Decryption mismatch — bug!`, 'bad');
} else if (!isRepeat) {
setStatus(fmt(`Encrypted! Ciphertext = (%c1%, %c2%). Decrypted back to %m%.`, { c1: c1, c2: c2, m: dec.m }), 'ok');
}
// Log to console for verification
console.log(`ElGamal | m=${mVal} k=${k} => (c1=${c1},c2=${c2}) dec=${dec.m} ok=${dec.m===mVal}`);
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t;
s.className = 'status ' + cls;
}
document.getElementById('btnEncrypt').addEventListener('click', () => {
lastC1 = null; lastC2 = null;
document.getElementById('steps').innerHTML = '';
setStatus('', '');
doEncrypt();
});
document.getElementById('btnAgain').addEventListener('click', doEncrypt);
Comments
Loading comments...