(Javascript) Cryptography is Hard
With JavaScript, the thieves are trying to get out, not in

(Javascript) Cryptography is Hard

On the surface, something like login with Cognito doesn’t seem like it should require much. But when you look at something like aws-amplify-js, it’s surprisingly heavy. Even when you import only what you need, it can add around 150KB to your bundle. For smaller apps, that’s basically doubling your size just to support authentication.

I recently decided to rewrite a Cognito SRP helper library and remove the dependency weight by leaning entirely on native ECMAScript APIs instead of NodeJS libraries as shown below.

  • Crypographic logic: npm:crypto-js → crypto.subtle
  • Big Integer support: npm:jsbn → BigInt
  • Binary buffer support: npm:buffer → Uint8Array

Once everything was refactored, the entire implementation came out to under 10KB minified for EVERYTHING. The size of my WHOLE library is 15x SMALLER than only using PART of aws-amplify-js. That's insane!

Article content


And that difference isn’t just theoretical... smaller bundles mean faster page loads and noticeably better Lambda cold start performance.

What caught me off guard wasn’t the SRP math itself. It was the data encoding details. Converting between binary, Base64, and hex is easy to get wrong. Hex encoding represents each byte as two characters. If you accidentally treat each hex digit as its own byte, your signatures break.

Below are some example conversions.

const uint8ArrayToHex = (uint8: Uint8Array): string =>
  Array.from(uint8)
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");

export default uint8ArrayToHex;        
function uint8ArrayFromBase64(value: string): Uint8Array {
  const binaryString = atob(value);
  const bytes = new Uint8Array(binaryString.length);
  for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }

  return bytes;
}

export default uint8ArrayFromBase64;        
const uint8ArrayFromString = (str: string): Uint8Array => {
  return new TextEncoder().encode(str);
};

export default fromString;        
const hexFromBigInt = (value: bigint) : string => {
  return value.toString(16);
};        

While JavaScript has come a long way, regarding cryptography you still tend to notice more of what's missing and not what's there.

Article content
Even when baseline, it may not work in nodejs.

Would I recommend everyone dive into cryptography? Probably not. It’s easy to make subtle mistakes, and those mistakes matter. But working through it definitely changes how you think about data, performance, and abstractions. For me, that alone made it worth the effort.

If you’re curious, here’s the related discussion: https://github.com/simonmcallister0210/cognito-srp-helper/issues/63#issuecomment-3924253852

Article content
Code snippet


To view or add a comment, sign in

Explore content categories