jsSHA: the whole SHA family in pure TypeScript, with an ES3 fallback for the script tag
A JavaScript/TypeScript implementation of the complete Secure Hash Standard (SHA) family (SHA-1, SHA-224/256/384/512, SHA3-224/256/384/512, SHAKE128/256, cSHAKE128/256, and KMAC128/256) with HMAC.
At a glance
- What is it?
- jsSHA is a streaming implementation of eleven hash variants plus HMAC, cSHAKE and KMAC, written in TypeScript with no native dependency, distributed as five per-family builds alongside the full one. The interesting engineering is in the packaging: one package that loads as an ES3 script tag, as CommonJS with or without subpath exports, as an ES module, and through Deno's registry specifier.
- Who is it for?
- jsSHA is the right choice when you need a hash in an environment that has no built-in one, and the specific case that justifies it is a browser without the platform's crypto interface, an old runtime, or a build step you do not control. It is also the right choice for streaming, because the update method takes a chunk and never requires the whole input, which is the property that a one-shot digest of a buffer in memory does not give you.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 35 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Four ways to load one package, and the residue of every era of packaging
The installation section is the most revealing page in this readme, because the same library is documented four ways and each way targets a different runtime. For a browser you include a script file, and there are five of them to choose from, one covering every variant and one per family. For Node you install from the package registry and require the module, with the note that the limited variant files are also exported, and the syntax for that depends on your Node version: the subpath form needs a recent release, an older one needs a flag, and versions without subpath support need a different path that points into the distribution directory instead. For an ES module you import the same entry point, again with a version note. And for Deno you import through the registry specifier, with types resolved from the package automatically. Underneath all four is a build described as a minified, universal module definition version compatible with a language generation from the previous decade, shipped with a source map. A single artefact that loads as a script tag, as CommonJS, as a bundle and as a Deno import is a genuinely wide compatibility range, and the fact that the output targets such an old language level is a deliberate choice rather than neglect. The repository shows what it has cost in residue. There is a manifest for a package manager that was deprecated years ago, a test configuration for a browser runner that has largely been superseded, a committed distribution directory, an ignore file for that directory when packaging, a Rollup configuration, and a modern exports map. Four generations of JavaScript packaging in one repository, each left in place.
The per-family builds, and an exports map that gets the details right
Five files is not many, and the reason there are five rather than one is the third entry in the features list of every library like this: how much of it ends up in your bundle. The readme names the full build and four limited ones, and you import the limited one when that is all you need. The cost of that decision is visible in the manifest, and the manifest gets the hard parts right. There are two entry points per family: a clean subpath and a second path that reaches into the distribution directory, and the second exists precisely for the runtimes that cannot use subpath exports, so a consumer on an old runtime is not forced to deep-import a file path that a future release might rename. Each entry then declares both module systems, and each of those declares its own type declaration file, with separate files for the import and the require conditions. That is the detail most packages get wrong. When a package exposes both module systems, the type declarations cannot be assumed to be interchangeable, because the two systems can resolve types differently, and shipping one declaration for both is a source of type errors that appear only under one of them. Shipping two is the correct answer and it is visible here in every entry. So the packaging says three things about how seriously this is taken: the per-family builds are for bundle size, the dual path is for old runtimes, and the per-condition type files are for correctness.
Three constructor arguments, and an input format list that is longer than the output list
The API is one class with a constructor, an update method and a get method, and getting it right is mostly a matter of reading the format lists carefully. The constructor takes three things: which variant, what format the input is in, and an optional options map. Eleven variants are listed, from the original algorithm through the two truncation families, the permutation-based family, and the two extendable output functions. Six input formats are listed, and five output formats. The difference is the thing to notice, because the one format you can hash into is the one format you cannot hash out, and it is not an oversight. Text is an input format because a string has to be encoded before it is hashed, and the encoding is not implied by the format, so it is a separate option with three allowed values covering the two byte orders of wide character text and the default. Text is not an output format, because the output of a hash is bytes and there is no sensible way to turn bytes into text. The two options on the way in also have documented defaults, and one of them is significant: a count of rounds, defaulting to one, which controls how many times the hashing is iterated. The options on the way out are narrower, one controlling whether hexadecimal output is upper or lower case, which defaults to lower, and one controlling the padding character on base sixty-four output.
The whole call, from the readme's own example:
const shaObj = new jsSHA("SHA-512", "TEXT", { encoding: "UTF8" });
shaObj.update("This is").update(" a ");
shaObj.update("test");
const hash = shaObj.getHash("HEX");The readme is explicit that each of those two applies to only one output format, which saves you from wondering why your base sixty-four padding did not change.
Iterated hashing is available for digests and refused for every keyed construction
This is the detail in the readme that most rewards reading it end to end, because it is stated three times in three slightly different ways and the repetition is the point. The constructor's option map includes a count of rounds with a default of one, so a plain digest can be iterated as many times as you ask, which is the primitive you need for key stretching. Then each of the three keyed or tagged constructions carries the same sentence: you cannot specify the count of rounds with it. First for the keyed construction, then for the first of the two extendable-output constructions, then for the second. Three repetitions of one prohibition, which means it is a specification property rather than an oversight. It is also the correct behaviour. A keyed construction is already defined in terms of nested hashing, and iterating the outer loop is a different, nonstandard thing; the same is true of the tagged constructions, which are themselves built from an extendable output function. A library that quietly accepted the option would produce a digest that matches no specification and matches no other implementation, and a library that refuses it produces a type error or an exception at the point where you would otherwise have shipped a wrong answer. The same three constructions share the other repeated requirement as well, and that one is stated more forcefully, as important, in all three cases: the requested output length is mandatory for them and it is expressed in multiples of eight bits. So for those three, the length is not a default you can omit, it is a parameter you must supply.
Three variable-output functions, and one documentation defect where it matters most
The three functions that produce output of a length you choose share a shape, and comparing them is the fastest way to understand the API. The first is the extendable output function itself, which takes an output length and nothing else. The second adds two optional inputs, a customisation string and a function name, and the readme says both are optional in the specification, which means calling it with neither is defined and reduces to the plain version. The third is the keyed one, and it takes the same pair of optional inputs plus a key, where the readme states the opposite asymmetry: the customisation is optional and the key is required. That difference is the specification's, not the library's, and getting it backwards would be a real interoperability bug. Here is where the documentation has one. In the type signature given for the third function, the property name for the key is written with a missing closing quote and with the same optional marker as the property beside it, so a reader copying the shape from the code block would reasonably conclude the key was optional, while the prose in the same section says it is required. It is a small typographical error in the one place where it could mislead, and it is the sort of thing you would catch in review if the sections were read against each other. The working examples in the readme are correct, and both of the working examples for the other two functions include the output length, so the intent is unambiguous even where the signature is not.
Streaming is what justifies a crypto library in a scripting language
The description calls this a streaming implementation, and that word is the reason to consider the library at all rather than as a curiosity. The update method takes a chunk, returns the object so calls can be chained, and can be called any number of times, which means the input never has to exist in memory at once. Hash a file, hash a socket, hash a stream of events, hash a hundred megabytes of log data as it arrives, and the peak memory is one chunk rather than the whole input. That is a real capability and it is the same capability a platform interface may or may not give you, depending on the runtime. The second reason to consider it is purity. The library is written in the language and ships no native binding and no WebAssembly module, so there is nothing to compile, nothing to mismatch with your architecture, and nothing to load in a worker or a sandboxed context. That is worth a great deal in the environments where a native module will not run, and it is also the cost. Everything is implemented in the same language that is interpreting your application, so for a large input in a hot loop the platform's own implementation, which is written in lower-level code and is hardware accelerated on modern hardware, will be faster. The readme publishes no performance figures at all, which is honest and also means the claim is untested in the way that matters. The topic list includes a general cryptography tag alongside the algorithm names, and the topic list also includes the oldest algorithm in the family, which is worth noting as a reminder that a hash library's job is to compute what you ask for and the judgement about which one to ask for is yours.
A security policy, a permissive licence, and a wiki as the real documentation
For a cryptographic library the governance facts matter as much as the code, and this repository has the right ones. There is a security policy file, which means there is somewhere to report a finding that is not the public issue tracker, and that distinction is the one that matters when a vulnerability in a digest implementation is worth keeping quiet for a while. The licence is the three clause BSD, which is the right choice for this category for a reason beyond permissiveness: a permissive licence means anyone who finds a defect can publish a fixed version, so a security response does not depend on the maintainer having time. The human detail worth naming is the same one that appeared in the package list and the browser runner, and it is the pattern: this library has been in continuous use long enough that it accumulated the packaging of every era, and the maintainers kept the old entries rather than breaking the consumers who relied on them. Documentation is a wiki rather than a set of pages in the repository, with the readme covering the common cases and pointing at the wiki for completeness, and a documentation site published from the repository's own pages. The trade-off is the one the readme states: the wiki describes the current version, so if you are on an older release the guidance you read may describe behaviour that release does not have. The changelog and the contributing guide are both in the repository, which is where you want them for a project with a version history this long.
The version story, and the platform implementation you should probably use first
The release record is worth reading because the gaps are informative. There is a release from 2022, one from 2024, and one from 2026, which is a project that ships when there is something to ship rather than on a schedule, and the manifest version matches the newest release, so the package and the tag are not out of step. The last push was in late August 2026, so the work is current. Now the comparison that should come first for anyone reading this. Both modern browsers and current server runtimes ship a cryptographic interface of their own, covering the hash families in this library, implemented in lower-level code, subject to the platform's own review, and accelerated in hardware where the hardware exists. Choosing this library over that is choosing portability over speed, and the honest situations in which that trade is right are a browser old enough or restricted enough not to offer the platform interface, a runtime on a platform where the library's per-family build is all you can load, or a need for the streaming shape that the platform interface does not give you in the same form. Choosing it over the platform when the platform has what you need is a choice you should be able to state the reason for. And if you do choose it, the per-family build is the reason your bundle stays small, the round count is the reason you can derive keys, and the three repeated restrictions on output length and iteration are the reason your output will match another implementation.
Editorial conclusion
jsSHA is the right choice when you need a hash in an environment that has no built-in one, and the specific case that justifies it is a browser without the platform's crypto interface, an old runtime, or a build step you do not control. It is also the right choice for streaming, because the update method takes a chunk and never requires the whole input, which is the property that a one-shot digest of a buffer in memory does not give you. Do not reach for it by default in a modern browser or on a current server runtime, where the platform implementation is faster, is hardware accelerated where it can be, and is audited as part of the browser. Three things to check. Which of the eleven variants you actually need, because the per-family build exists so your bundle does not carry the other ten. Whether the version you pin matches the runtime you are on, since the documented subpath syntax needs a recent Node and the readme gives a fallback path for older ones. And the three explicit restrictions the readme repeats, because iterated hashing is available for plain digests and refused for every keyed construction, which is a specification detail rather than a bug.
Frequently asked questions
Which hash algorithms does jsSHA implement?
The full family as the readme lists it: the original algorithm, the two truncation families of the second standard, the permutation-based family, the two extendable output functions, the two tagged variants, and the two keyed variants, plus a keyed construction. The constructor takes the variant name as its first argument.
How do I import only the family I need?
There is a limited build per family alongside the full one, exported as its own subpath, and the readme also gives a second subpath that reaches into the distribution directory for runtimes that do not support subpath exports. In a browser you include the corresponding script file instead of the combined one.
What does the options map on the constructor do?
Two things by default: the encoding used to convert text input into bytes, with three allowed values covering UTF-8 and the two byte orders of UTF-16, and the number of hashing rounds, which defaults to one and controls iteration. A separate optional map on the hash call controls whether hexadecimal output is upper case and what character pads base sixty-four output.
Is iterated hashing available?
For plain digests, yes, through a rounds option that defaults to one. The readme states three times, once per keyed or tagged construction, that you cannot specify it there, which is a specification property rather than an oversight, since those constructions are already defined in terms of nested hashing.
How do I use it in a browser with a script tag?
Include one of the script files in your header, either the one covering all variants or the one for the single family you need. The readme describes the shipped file as a minified universal module definition build compatible with a very old language version, with its source map alongside it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/caligatio-jssha)