]> git.ardis.lu - static-encrypt-passkey/commitdiff
Pad plaintext bytes so the encoded ciphertext has no base64 padding
authorArdis Lu <ardisglu@gmail.com>
Wed, 23 Jul 2025 04:18:51 +0000 (21:18 -0700)
committerArdis Lu <ardisglu@gmail.com>
Wed, 23 Jul 2025 04:18:51 +0000 (21:18 -0700)
Since base64 padding is ignored on decode, the padding can be deleted or replaced with junk data. As a result, it is possible to decode different strings into the same bytes. By padding the bytes so the bytes are a multiple of 3, there is never base64 padding and exactly one input yields the desired output.

script.js

index 7b8fcc1566eb0412313c86b9eb4ef01b32bbdccf..684840bd7523635ad19899749e7e871aeb3b8200 100644 (file)
--- a/script.js
+++ b/script.js
@@ -80,6 +80,14 @@ export async function encrypt(plaintext) {
   // Prepare non-crypto inputs
   const encoder = new TextEncoder();
   const encodedPlaintext = encoder.encode(plaintext);
+
+  // Pad ciphertext so the final buffer length is a multiple of 3, to remove base64 padding. Not cryptographically
+  // significant, just for aesthetics. +16 assumes ciphertext includes a 128 bit AES-GCM authentication tag.
+  const p = 3 - ((salt.byteLength + iv.byteLength + encodedPlaintext.byteLength + 16) % 3);
+  const paddedPlaintext = new Uint8Array(encodedPlaintext.byteLength + p);
+  paddedPlaintext.set([p]); // Offset to discard on decode
+  paddedPlaintext.set(encodedPlaintext, p); // Gap filled with zero bytes, e.g. [3, 0, 0, ...encodedPlaintext]
+
   const info = encoder.encode('https://github.com/ardislu/static-encrypt-passkey');
 
   const prf = await createPrf();
@@ -87,7 +95,7 @@ export async function encrypt(plaintext) {
   const ciphertext = new Uint8Array(await crypto.subtle.encrypt(
     { name: 'AES-GCM', iv },
     key,
-    encodedPlaintext
+    paddedPlaintext
   ));
 
   const buffer = Uint8Array.from([...salt, ...iv, ...ciphertext]);
@@ -120,8 +128,9 @@ export async function decrypt(content) {
     key,
     ciphertext
   ));
+  const unpaddedPlaintext = encodedPlaintext.slice(encodedPlaintext[0]);
 
-  const plaintext = new TextDecoder().decode(encodedPlaintext);
+  const plaintext = new TextDecoder().decode(unpaddedPlaintext);
 
   return plaintext;
 }
\ No newline at end of file
This page took 0.03878 seconds and 4 git commands to generate.