Post Quantum Cryptography in a Browser

Prof Bill Buchanan OBE FRSE
4 min readNov 4, 2024

Like it or not, we need to move away from ECDH (Elliptic Curve Diffie Hellman) for key exchange and move towards using ML-KEM (aka Kyber) for a key encapsulation method (KEM).

Alice will generate a key pair (pkAlice and skAlice) and sends Bob her public key (pkAlice). Bob will then generate a new key (bobKey). He will then encrypt the new key (bobKey) with Alice’s public key (pkAlice), and sends the resultant ciphertext (cipherText) to Alice. Alice then decrypts the ciphertext (cipherText) with her private key (skAlice). She then gets aliceKey, and which will be the same as bobKey (Figure 1):

const [pkAlice, skAlice] = await recipient.generateKeyPair();
const sender = new MlKem512();
const [cipherText, bobKey] = await sender.encap(pkAlice);
const aliceKey = await recipient.decap(cipherText, skAlice);
Figure 1

The HTML is [here]:

<html>
<body>
<script type="module">
const toHexString = (bytes) => {
return Array.from(bytes, (byte) => {
return ('0' + (byte & 0xff).toString(16)).slice(-2);
}).join('');
};
import { MlKem512, MlKem768, MlKem1024 } from "https://esm.sh/mlkem";
globalThis.doKem1 = async () => {
try {
const recipient = new MlKem512();
const [pkAlice, skAlice] = await recipient.generateKeyPair();
const sender = new MlKem512();
const [cipherText, bobKey] = await sender.encap(pkAlice);
const aliceKey = await recipient.decap(cipherText, skAlice);

document.getElementById("Bob").innerHTML = "Bob key: " + toHexString(bobKey);
document.getElementById("Alice").innerHTML = "Alice key: " + toHexString(aliceKey) + "\n\n";
document.getElementById("Alice").innerHTML += "Alice public key length: " + pkAlice.length + "\n";
document.getElementById("Alice").innerHTML += "Alice public key: " + toHexString(pkAlice) + "\n\n";
document.getElementById("Alice").innerHTML += "Alice private key length: " + skAlice.length + "\n\n";
document.getElementById("Alice").innerHTML += "Alice private key: " + toHexString(skAlice) + "\n";
// bobKey === aliceKey
return;
} catch (err) {
alert("failed: ", err.message);
}
};
globalThis.doKem2 = async () => {
try {
const recipient = new MlKem768();
const [pkAlice, skAlice] = await recipient.generateKeyPair();
const sender = new MlKem768();
const [cipherText, bobKey] = await sender.encap(pkAlice);
const aliceKey = await recipient.decap(cipherText, skAlice);
document.getElementById("Bob").innerHTML = "Bob key: " + toHexString(bobKey);
document.getElementById("Alice").innerHTML = "Alice key: " + toHexString(aliceKey) + "\n\n";
document.getElementById("Alice").innerHTML += "Alice public key length: " + pkAlice.length + "\n";
document.getElementById("Alice").innerHTML += "Alice public key: " + toHexString(pkAlice) + "\n\n";
document.getElementById("Alice").innerHTML += "Alice private key length: " + skAlice.length + "\n\n";
document.getElementById("Alice").innerHTML += "Alice private key: " + toHexString(skAlice) + "\n";
// bobKey === aliceKey
return;
} catch (err) {
alert("failed: ", err.message);
}
};
globalThis.doKem3 = async () => {
try {
const recipient = new MlKem1024();
const [pkAlice, skAlice] = await recipient.generateKeyPair();
const sender = new MlKem1024();
const [cipherText, bobKey] = await sender.encap(pkAlice);
const aliceKey = await recipient.decap(cipherText, skAlice);
document.getElementById("Bob").innerHTML = "Bob key: " + toHexString(bobKey);
document.getElementById("Alice").innerHTML = "Alice key: " + toHexString(aliceKey) + "\n\n";
document.getElementById("Alice").innerHTML += "Alice public key length: " + pkAlice.length + "\n";
document.getElementById("Alice").innerHTML += "Alice public key: " + toHexString(pkAlice) + "\n\n";
document.getElementById("Alice").innerHTML += "Alice private key length: " + skAlice.length + "\n\n";
document.getElementById("Alice").innerHTML += "Alice private key: " + toHexString(skAlice) + "\n";

// bobKey === aliceKey
return;
} catch (err) {
alert("failed: ", err.message);
}
};
</script>

<div class="indented">
<table width="100%">
<tr>
<th>Method</th>
<td style="text-align:left">

<input type="button" class="btn btn-medium btn-success" value="ML-KEM 512" onclick="doKem1()">
<input type="button" class="btn btn-medium btn-success" value="ML-KEM 768" onclick="doKem2()">
<input type="button" class="btn btn-medium btn-success" value="ML-KEM 1024" onclick="doKem3()">

</td>
</tr>
<tr>
<th width="15%">Bob</th>
<td>
<textarea cols="20" id="Bob" name="input" rows="2" style="width:100%">
</textarea>
</td>
</tr>
<tr>
<th>Alice</th>
<td>
<textarea cols="20" id="Alice" name="alice" rows="30" style="width:100%">
</textarea>
</td>
</tr>

</table>

</div>

For ML-KEM-512, we get [here]:

Bob key: 00230839f575f3ac93c80df22aeffc6f6c0ce5954995a78ec3f24b0df9c9cc5d

Alice key: 00230839f575f3ac93c80df22aeffc6f6c0ce5954995a78ec3f24b0df9c9cc5d
Alice public key length: 800
Alice public key: f29b6d107363e17a1cc70655abeba0e17c3e5a6490c71320e53cb05dc75c38179fc6928f3f25285337c825f47ea3e7af4eb6c12d65692d7a91799b5af0266a92f73f3d577efaab8ac1e43fc8d88b573c9a23822c4ff77b74d6c281b0aff6d6b2e5c7658bdb740067129b208a134378b42517e3225aa01575815041b3685e22753b0bdc827bd344b1943f0b84249a17b0dd7066ee350f7bfa487d0bab4a02738a79c35e3338d4442bad946b89326d92325f2f780394476199b87cafe2abea515f774baac8260d15361d8e386f870acfb05310a9c4847363c722725c703312a2f568628a39932a9a0553c5e1333b6b06435916470ccb233a4caeabe414f661293d201789d870f721a85c3447f57385996a76e5c63b98dc43c2b93c98d15d4ab0009268401de4160cab2b4569a19129b5e7a3600f0081b31550461a2d80db30fd35162f95bf87e48c1041616ba3ab4aa7b048ccb336e8a9d042c4a7d45fccb34255709bef32c312dc5f89f068ea4ab05a490ada942aed1bbb256b96da077f131090460c4d8e0a478d5a3aaa95141e1781fa86b7a1e0bc1dfb8961c7a75b86656a3b3ec1c80cf3954ea980603a696aaa95757e022f23fcb4fc93547d794e7b9a8637c72a1c36af3f99cbbb80a82d5701b5a425d26858f06a1a92c61f89f88a8981afc2ca7a9b2c0b08dac1b37c409ce0c7ba6a5c40f832a2d22820685eb9c726c2198d8bd790aaca7013f11c7407952c3195e5970af68bae446b60b3825d69a379e5b04a590259558c704e298b2aeaa2f9a806ab43763ef21a7da8b8d04439f92906992864ec6415eebbbae859599188b409b7004ec0aa0c843e3d8c4525e3551bdb0bbb2797bbe7ca33a18b9afb3770aa97a491830c982d3a835184286ba47a95f0c14afd075d365b8cf1f367116a2debe21262407ccb6c2fa3f231819bb20b045631f9b9ba0a8ef0819faac3c694f886c466868847b61a0800f17510f0d06271da6eb5f7a3320c6dcdab50ff96517b7a9fe13c435067cb3a21ae9b8825f3b721339195afe11b739391de411c8d866e0fb4835d18980986360f024c4cd75ee6349aa6db31186f0835d69266abcc317d6bfa58692c4c007b6eb515a7767768800385
Alice private key length: 1632
Alice private key: f4d27ff736745846a9f65374bc31a328a7367d03864f13254087... 092ce946dbd8

For ML-KEM-768 [here]:

Bob key: 5d12534c35b5ad782c6a3af13253ca305a174d477ef4b1e465c15d321bc2e37b
Alice key: 5d12534c35b5ad782c6a3af13253ca305a174d477ef4b1e465c15d321bc2e37b
Alice public key length: 1184
Alice public key: 462a667bf11b2196c6f8524cb4ab8d79c67ec18c21b9875ecb6b0e065a1c56157364ac0d43421407e6b69708147d59ce4c9ccaf2a6289caa0bbc9665cf6a513dd3c4ae50b02f902933f66b3a81bbcec994fe4b2c9b631dd0926b4b6a10558c53133353820c8929080015342ec45a15624980267878c7e19c4253a53910aa78a321a9421ec16a7885399a05184ad80b01c684bc8ef77bfa593371007ce3051791e69d8af7b0fdc459035b1529797c368274c9c4922fc30bebc6b7e9b50c13b4880f503de6a8254ac54d576660ea7b0c7321a3f8044c87a807e95289ff56849ef654c1183760bbbed80cbc66e69e4e2b478be53ebbc5150b9c796a1609a8c226ec31424fcb23f2c4930b2bc5cf81110b95c04f46952fe306e042043c5799d1a5cf17fc8758d4095ff2637c94980bc056f5f629f9ab75fae20226b9326fd252cea300db799de1688df41c6b25a3162f50a52722b2a4215d841ab6b004c51c944b29eb6cf6703719d4cc02f6c3e69758c3011113d125bd171dbaf6afedf81ba1c40d2af7b2f7849e9c439dc0bc021e68399b0009189c16b886b80c135e618c74f5b296bb10a12c1762cc8c32b6e086e657b76f9bbc3340385b650291766a2779241ce90fbca54a14760ac6459bdf7803d484cab8b9255a15c4a56a432dc954a335bcf79a3617999080fb9d67c31ce6314d447092c540bed5749303433892f00b1c4a86b380b364071c8c5106ce7c3c865660f0db968b859c4595aea0036538a7aa9e42ae8d188338cb7f8aa63e5dcc3f31c6c958b14adf980e9c487a725067785610d0f92d7bfa245595bcd73c840d99a58040824a86561b12314fb74c2b108356f467eba5909d531992c4b17a9b54757a1c38b73e71e06707e84645293b0e192a9874b6573a5b41c79befc1a78a1a6d2b675f3ce85d938bbcddc52f8012122e63497b289a3d3ba12d3c71c664ac49b40415c3abb684c4e967ab811451bd275f14175a90d66e974b351ce0b6003c35cdc548e6021dd0649c797a580b509656d98ad5614099f24e2aea71139295b006626638222f3853641772f4837577493bad7b3af725733c1829d69949fceaa5e0c4b143f4842583175067896c281628742e06ab30335b0477606e18e578e0e21f4b5c2da632121bccc384a39590474d351319e5f871e795bf1fc9b825035e3f799aad99c4ed3623c65426844158e48a8c1f3a937c761e2f734756dc3971093d44c380e15c89253c415e31ac1012156818938613a29516003ab97b3a56ce99e578c8b12f2582a75e0a6ae5222f27a8b627260e62c948092767ebe7aa631a91ee4a2011d78ee0602e66422c9940cc65d042014a32609423d8739b72a819d262535c06bfefe542fc08d03f354d76d77fe7b77ae33ba931f1a353abb159f99a720caea9fb4f90583a11767aaaab52b1c1a11b67683de4c167d97e6d9b4cbdf8a7ed8a367059a80ecb4472d1cc4dfc5e7b3163f865be06123fa56277d9092299963413e50320e2b0584c5243a43769b04689333ebef360cf507c5db0ab7c5158bc77cd040a6fb0b8bcd3ca90652094d5a058165b7183f34d3fe0985adc3417907dfa3544628588c36943eef2b0a9e6a48d40058b094ef110fdd59ea7dc9e499c2f4193d8fe75538eb86c49356b0b380d2cc78a5dd605
Alice private key length: 2400
Alice private key: e4359b4815af37371d91cb3675401edc0aa97356c...72d35788f7b75ae4e210f53f60f468396f864dbb66e84fba0557b077128ec168ccb795843207e4cdd3780e4998d43a

And for ML-KEM-1024 [here]:

Bob key: e7606da7f7f6882f92de15662559c676fbfc0bd945f75678f6f9ffc36d2cf93d
Alice key: e7606da7f7f6882f92de15662559c676fbfc0bd945f75678f6f9ffc36d2cf93d
Alice public key length: 1568
Alice public key: 7ebcbcdfe7073bd825b94726fc2b4298666b3da4208484a62df0985ffc9812a0aae4441d0e8418b82c90bce08d59f9252efb39780641380746ace1a5b57b1bbec90fab5cb360acc0c1f016a9b99b3d647fc68633bd60bd01204f502a5fa11c06e5a934a0cc938cdac9e43c14553b6bff36614fac48c090382dea8af5f17fc05bbb2138b113688ab3a1adb754482ad60139e267c35acedd7a18fb9c9c6ea83e486a6342b3b4c8397341aa82d61a9dbca7582840536b604e0bd28a1a7414e04c6f241b151c535484178930a3920c3a9ecf2b7681918781c3bace72cbc6f386a5278778727d956444349cc4e7959733a01c74fc9e5fa88505a2609a915d3088535d28791a8cb4d94bc3f0b0582e8cae373695a30bc7e97c4098d04aa3e49ce3b36fa72725b1d7468a8394cbc1ccb4a67c1e9229e6da9bd22470bb84980e0c6a0fa5878cfa742358c1651703b0771113983b0e6a9de6498c69a4c8a0e21c53104923d7ce3db62dfbe840196723091b290683c457c93ca305c5e0c4815fda3135643065bc2966d1209dd6a4992a2bf4081736116f70a4af5808962dfa678675a97127cd941191ebc53435ec0b8e0503dca6610696b322434ca4578d51d202742c256338b5a8acc3d9d0b55eb879d6b0ba2346746b82102d28b586b29b46801f7a636725a149797cc425a520c4c54b97e458366075049781ad933fd01645acf32efe91928720711d700b61310e76e4bab46b2486c22eb2943a79c281caa066082bc0e075ca2933660e4169ccb6ab1636382d7c15f432328f18653760a00191cd99f5717720901784a36401464e10b8e9b06749501b6b305152f423f7561d6078c55349946d7bac013b9c37a6c569aa1e4ddb8376b4a7232a334882a4d5748d801430992b4817da706c6038e6062620b1876e1c6df14207eab5a405a46872c0cc6dc8040b92066510b48c7786c48527ead889950147ede7b0abb85d37970178093a1795b22d799a0d7466e1a5c25539a3594c054cf94effc19c4409124fa80d8a11b421243fc1a3bf2bc50674a329d7f54dfb4813581a336a58549cf54baac1bc489473bdc57d4002c43b45400f5b58787327f62940019c09c1cb594fd977727b3340cb7af020987706ca84d334acdba1e7eaaf14420991802c620479ef8a0792c39439fab0c0a91cef910ca5a9262ce6c672d39d8f0b2f34c939dd794061d68703092b06122017784443c24ef3a033b1a81a611830f95432daea5e26bb4d762776b16a83cffacc18fc1d96d969da1456390a6cf582197e1a5f7a536c878c3577722163232e74e0358c694e7618cd6165855ea245d3d8c9e89804a1137e9938277fe136c8fb50e21207b9580ace7c886141119b09cffd82861ed1a90bc1640c2925cd08934fa6a5edba05f248b496cca280e4443ef8a774f50d5e864bea992c1df688b8d1982c865b61107b4144a85eb790f8100b91a56ab225375e88c25fdc22cf84b7774cb84fdc6e494a05cbd803cb86407b110f64513410a833a4d47b3b2320acd5a16c652d40582fcc1b8e8b53a76b9501f9f08fba0b05eb97519b46278d79acb5630a1d1cb502e178c5b9ce38483733e1148586b32963c22693533a09810f846e3091a4976087b14a8d0608cb8c035f4b2453a5b7625feb3d6ba44d86b06492bca6271cba3924bb4d331fa1bcb0ab93112e5735f0748e516434c4d083bad66f5be47cc505c317f54a48814071973f49e17dfef8833a15833adaaba4b716ba8c7b9232a7ed05ab3ee2bc2430753421076c2c30be520f915a1c69ba78c2161743b78acd8369d03714847848fdb72706695f829b0d162383a8d3c55ed4bb767899c32c394cc08025b521a91622ccda15fbb3070069649251900dda35b3d99f0cdc6de27863af8c6b92155c5fc80ff3453375b18f33689e94d0862106738ef0c771fc3d79d1c957f0b7c4d6b227485f13db9ce0521f3013c479d3046e0610af931334b2c573b25b5c5cc247808f5d431ab76b71e13b6950e1266547452933ab8ee225bb483cdf007fa1fb0d724b92de34b33706199b2851a03c642991ccb880b221142c5bca9dd5b90608bbaa427aa4ff52510ba291e292185ee11672d55bdeb5adb0fa44f589b85f5890562c0eb2f81d9bcb5272ba0b74aa26629c645d42a9c796c598c3f5df21f402fe3cc1e37881151f6cb1148b3f84b541a1cd6139156ef78315
Alice private key length: 3168
Alice private key: 3c39292432862b168e7f2b6135dc3640d71cd... 0f6210db8

Conclusions

There is little to stop organisations from migrating to ML-KEM. We have Golang code to implement it:

and with JavaScript:

--

--

Prof Bill Buchanan OBE FRSE
Prof Bill Buchanan OBE FRSE

Written by Prof Bill Buchanan OBE FRSE

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.

Responses (1)