@noble/hashes
Advanced tools
Comparing version 1.3.2 to 1.3.3
@@ -14,4 +14,9 @@ "use strict"; | ||
exports.bool = bool; | ||
// copied from utils | ||
function isBytes(a) { | ||
return (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); | ||
} | ||
function bytes(b, ...lengths) { | ||
if (!(b instanceof Uint8Array)) | ||
if (!isBytes(b)) | ||
throw new Error('Expected Uint8Array'); | ||
@@ -18,0 +23,0 @@ if (lengths.length > 0 && !lengths.includes(b.length)) |
@@ -9,4 +9,9 @@ function number(n) { | ||
} | ||
// copied from utils | ||
function isBytes(a) { | ||
return (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); | ||
} | ||
function bytes(b, ...lengths) { | ||
if (!(b instanceof Uint8Array)) | ||
if (!isBytes(b)) | ||
throw new Error('Expected Uint8Array'); | ||
@@ -13,0 +18,0 @@ if (lengths.length > 0 && !lengths.includes(b.length)) |
import { number as assertNumber } from './_assert.js'; | ||
import { toBytes, wrapConstructorWithOpts, u32 } from './utils.js'; | ||
import { toBytes, wrapConstructorWithOpts, u32, wrapXOFConstructorWithOpts, } from './utils.js'; | ||
import { Keccak } from './sha3.js'; | ||
@@ -202,2 +202,11 @@ // cSHAKE && KMAC (NIST SP800-185) | ||
export const parallelhash256xof = /* @__PURE__ */ (() => genPrl(136, 256 / 8, cshake256, true))(); | ||
const genTurboshake = (blockLen, outputLen) => wrapXOFConstructorWithOpts((opts = {}) => { | ||
const D = opts.D === undefined ? 0x1f : opts.D; | ||
// Section 2.1 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/ | ||
if (!Number.isSafeInteger(D) || D < 0x01 || D > 0x7f) | ||
throw new Error(`turboshake: wrong domain separation byte: ${D}, should be 0x01..0x7f`); | ||
return new Keccak(blockLen, D, opts.dkLen === undefined ? outputLen : opts.dkLen, true, 12); | ||
}); | ||
export const turboshake128 = /* @__PURE__ */ genTurboshake(168, 256 / 8); | ||
export const turboshake256 = /* @__PURE__ */ genTurboshake(136, 512 / 8); | ||
// Kangaroo | ||
@@ -204,0 +213,0 @@ // Same as NIST rightEncode, but returns [0] for zero string |
@@ -7,8 +7,11 @@ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// Makes the utils un-importable in browsers without a bundler. | ||
// Once node.js 18 is deprecated, we can just drop the import. | ||
// Once node.js 18 is deprecated (2025-04-30), we can just drop the import. | ||
import { crypto } from '@noble/hashes/crypto'; | ||
const u8a = (a) => a instanceof Uint8Array; | ||
// Cast array to different type | ||
export const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); | ||
export const u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4)); | ||
function isBytes(a) { | ||
return (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); | ||
} | ||
// Cast array to view | ||
@@ -20,5 +23,8 @@ export const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); | ||
// early-throw an error because we don't support BE yet. | ||
// Other libraries would silently corrupt the data instead of throwing an error, | ||
// when they don't support it. | ||
export const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44; | ||
if (!isLE) | ||
throw new Error('Non little-endian hardware is not supported'); | ||
// Array where index 0xf0 (240) is mapped to string 'f0' | ||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0')); | ||
@@ -29,3 +35,3 @@ /** | ||
export function bytesToHex(bytes) { | ||
if (!u8a(bytes)) | ||
if (!isBytes(bytes)) | ||
throw new Error('Uint8Array expected'); | ||
@@ -39,2 +45,13 @@ // pre-caching improves the speed 6x | ||
} | ||
// We use optimized technique to convert hex string to byte array | ||
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 }; | ||
function asciiToBase16(char) { | ||
if (char >= asciis._0 && char <= asciis._9) | ||
return char - asciis._0; | ||
if (char >= asciis._A && char <= asciis._F) | ||
return char - (asciis._A - 10); | ||
if (char >= asciis._a && char <= asciis._f) | ||
return char - (asciis._a - 10); | ||
return; | ||
} | ||
/** | ||
@@ -46,13 +63,15 @@ * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23]) | ||
throw new Error('hex string expected, got ' + typeof hex); | ||
const len = hex.length; | ||
if (len % 2) | ||
throw new Error('padded hex string expected, got unpadded hex of length ' + len); | ||
const array = new Uint8Array(len / 2); | ||
for (let i = 0; i < array.length; i++) { | ||
const j = i * 2; | ||
const hexByte = hex.slice(j, j + 2); | ||
const byte = Number.parseInt(hexByte, 16); | ||
if (Number.isNaN(byte) || byte < 0) | ||
throw new Error('Invalid byte sequence'); | ||
array[i] = byte; | ||
const hl = hex.length; | ||
const al = hl / 2; | ||
if (hl % 2) | ||
throw new Error('padded hex string expected, got unpadded hex of length ' + hl); | ||
const array = new Uint8Array(al); | ||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { | ||
const n1 = asciiToBase16(hex.charCodeAt(hi)); | ||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); | ||
if (n1 === undefined || n2 === undefined) { | ||
const char = hex[hi] + hex[hi + 1]; | ||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi); | ||
} | ||
array[ai] = n1 * 16 + n2; | ||
} | ||
@@ -94,3 +113,3 @@ return array; | ||
data = utf8ToBytes(data); | ||
if (!u8a(data)) | ||
if (!isBytes(data)) | ||
throw new Error(`expected Uint8Array, got ${typeof data}`); | ||
@@ -103,11 +122,16 @@ return data; | ||
export function concatBytes(...arrays) { | ||
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); | ||
let pad = 0; // walk through each item, ensure they have proper type | ||
arrays.forEach((a) => { | ||
if (!u8a(a)) | ||
let sum = 0; | ||
for (let i = 0; i < arrays.length; i++) { | ||
const a = arrays[i]; | ||
if (!isBytes(a)) | ||
throw new Error('Uint8Array expected'); | ||
r.set(a, pad); | ||
sum += a.length; | ||
} | ||
const res = new Uint8Array(sum); | ||
for (let i = 0, pad = 0; i < arrays.length; i++) { | ||
const a = arrays[i]; | ||
res.set(a, pad); | ||
pad += a.length; | ||
}); | ||
return r; | ||
} | ||
return res; | ||
} | ||
@@ -114,0 +138,0 @@ // For runtime check if class implements interface |
{ | ||
"name": "@noble/hashes", | ||
"version": "1.3.2", | ||
"description": "Audited & minimal 0-dependency JS implementation of SHA2, SHA3, RIPEMD, BLAKE2/3, HMAC, HKDF, PBKDF2, Scrypt", | ||
"version": "1.3.3", | ||
"description": "Audited & minimal 0-dependency JS implementation of SHA, RIPEMD, BLAKE, HMAC, HKDF, PBKDF & Scrypt", | ||
"files": [ | ||
@@ -40,4 +40,4 @@ "/*.js", | ||
"micro-should": "0.4.0", | ||
"prettier": "2.8.4", | ||
"typescript": "5.0.2" | ||
"prettier": "3.1.1", | ||
"typescript": "5.3.2" | ||
}, | ||
@@ -127,2 +127,7 @@ "engines": { | ||
}, | ||
"./sha2": { | ||
"types": "./sha2.d.ts", | ||
"import": "./esm/sha2.js", | ||
"default": "./sha2.js" | ||
}, | ||
"./sha3-addons": { | ||
@@ -129,0 +134,0 @@ "types": "./sha3-addons.d.ts", |
313
README.md
# noble-hashes | ||
Audited & minimal JS implementation of SHA2, SHA3, RIPEMD, BLAKE2/3, HMAC, HKDF, PBKDF2 & Scrypt. | ||
Audited & minimal JS implementation of SHA, RIPEMD, BLAKE, HMAC, HKDF, PBKDF, Scrypt & Argon2. | ||
@@ -11,3 +11,3 @@ - 🔒 [**Audited**](#security) by an independent security firm | ||
- 🐢 Scrypt supports `N: 2**22`, while other implementations are limited to `2**20` | ||
- 🦘 SHA3 supports Keccak, TupleHash, KangarooTwelve and MarsupilamiFourteen | ||
- 🦘 SHA3 supports Keccak, cSHAKE, KangarooTwelve, MarsupilamiFourteen and TurboSHAKE | ||
- 🪶 Just 3.4k lines / 17KB gzipped. SHA256-only is 240 lines / 3KB gzipped | ||
@@ -21,12 +21,11 @@ | ||
- No dependencies, protection against supply chain attacks | ||
- Auditable TypeScript / JS code | ||
- Supported on all major platforms | ||
- Releases are signed with PGP keys and built transparently with NPM provenance | ||
- Check out [homepage](https://paulmillr.com/noble/) & all libraries: | ||
- Zero or minimal dependencies | ||
- Highly readable TypeScript / JS code | ||
- PGP-signed releases and transparent NPM builds | ||
- All libraries: | ||
[ciphers](https://github.com/paulmillr/noble-ciphers), | ||
[curves](https://github.com/paulmillr/noble-curves), | ||
[hashes](https://github.com/paulmillr/noble-hashes), | ||
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) / | ||
[ed25519](https://github.com/paulmillr/noble-ed25519) | ||
[hashes](https://github.com/paulmillr/noble-hashes) | ||
- [Check out homepage](https://paulmillr.com/noble/) | ||
for reading resources, documentation and apps built with noble | ||
@@ -40,3 +39,3 @@ ## Usage | ||
For React Native, you may need a [polyfill for getRandomValues](https://github.com/LinusU/react-native-get-random-values). | ||
If you don't like NPM, a standalone [noble-hashes.js](https://github.com/paulmillr/noble-hashes/releases) is also available. | ||
A standalone file [noble-hashes.js](https://github.com/paulmillr/noble-hashes/releases) is also available. | ||
@@ -50,35 +49,30 @@ ```js | ||
console.log(sha256('abc')); // == sha256(new TextEncoder().encode('abc')) | ||
``` | ||
// sha384 is here, because it uses same internals as sha512 | ||
import { sha512, sha512_256, sha384 } from '@noble/hashes/sha512'; | ||
// prettier-ignore | ||
import { | ||
sha3_224, sha3_256, sha3_384, sha3_512, | ||
keccak_224, keccak_256, keccak_384, keccak_512, | ||
shake128, shake256 | ||
} from '@noble/hashes/sha3'; | ||
// prettier-ignore | ||
import { | ||
cshake128, cshake256, kmac128, kmac256, | ||
k12, m14, | ||
tuplehash256, parallelhash256, keccakprg | ||
} from '@noble/hashes/sha3-addons'; | ||
import { ripemd160 } from '@noble/hashes/ripemd160'; | ||
import { blake3 } from '@noble/hashes/blake3'; | ||
import { blake2b } from '@noble/hashes/blake2b'; | ||
import { blake2s } from '@noble/hashes/blake2s'; | ||
import { hmac } from '@noble/hashes/hmac'; | ||
import { hkdf } from '@noble/hashes/hkdf'; | ||
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2'; | ||
import { scrypt, scryptAsync } from '@noble/hashes/scrypt'; | ||
- [Implementations](#implementations) | ||
- [sha2: sha256, sha384, sha512, sha512_256](#sha2-sha256-sha384-sha512-sha512_256) | ||
- [sha3: FIPS, SHAKE, Keccak](#sha3-fips-shake-keccak) | ||
- [sha3-addons: cSHAKE, KMAC, K12, M14, TurboSHAKE](#sha3-addons-cshake-kmac-k12-m14-turboshake) | ||
- [ripemd160](#ripemd160) | ||
- [blake2b, blake2s, blake3](#blake2b-blake2s-blake3) | ||
- [sha1: legacy hash](#sha1-legacy-hash) | ||
- [hmac](#hmac) | ||
- [hkdf](#hkdf) | ||
- [pbkdf2](#pbkdf2) | ||
- [scrypt](#scrypt) | ||
- [argon2](#argon2) | ||
- [utils](#utils) | ||
- [All available imports](#all-available-imports) | ||
- [Security](#security) | ||
- [Constant-timeness](#constant-timeness) | ||
- [Memory dumping](#memory-dumping) | ||
- [Supply chain security](#supply-chain-security) | ||
- [Randomness](#randomness) | ||
- [Speed](#speed) | ||
- [Contributing & testing](#contributing--testing) | ||
- [Resources](#resources) | ||
- [License](#license) | ||
import { sha1 } from '@noble/hashes/sha1'; // legacy | ||
### Implementations | ||
// small utility method that converts bytes to hex | ||
import { bytesToHex as toHex } from '@noble/hashes/utils'; | ||
console.log(toHex(sha256('abc'))); // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | ||
``` | ||
## API | ||
All hash functions: | ||
@@ -115,20 +109,4 @@ | ||
## Modules | ||
##### sha2: sha256, sha384, sha512, sha512_256 | ||
- [SHA2 (sha256, sha384, sha512, sha512_256)](#sha2-sha256-sha384-sha512-sha512_256) | ||
- [SHA3 (FIPS, SHAKE, Keccak)](#sha3-fips-shake-keccak) | ||
- [SHA3 Addons (cSHAKE, KMAC, KangarooTwelve, MarsupilamiFourteen)](#sha3-addons-cshake-kmac-tuplehash-parallelhash-kangarootwelve-marsupilamifourteen) | ||
- [RIPEMD-160](#ripemd-160) | ||
- [BLAKE2b, BLAKE2s](#blake2b-blake2s) | ||
- [BLAKE3](#blake3) | ||
- [SHA1 (legacy)](#sha1-legacy) | ||
- [HMAC](#hmac) | ||
- [HKDF](#hkdf) | ||
- [PBKDF2](#pbkdf2) | ||
- [Scrypt](#scrypt) | ||
- [ESKDF](#eskdf) | ||
- [utils](#utils) | ||
##### SHA2 (sha256, sha384, sha512, sha512_256) | ||
```typescript | ||
@@ -171,3 +149,3 @@ import { sha256 } from '@noble/hashes/sha256'; | ||
##### SHA3 (FIPS, SHAKE, Keccak) | ||
##### sha3: FIPS, SHAKE, Keccak | ||
@@ -202,3 +180,3 @@ ```typescript | ||
##### SHA3 Addons (cSHAKE, KMAC, TupleHash, ParallelHash, KangarooTwelve, MarsupilamiFourteen) | ||
##### sha3-addons: cSHAKE, KMAC, K12, M14, TurboSHAKE | ||
@@ -213,2 +191,4 @@ ```typescript | ||
m14, | ||
turboshake128, | ||
turboshake256, | ||
tuplehash128, | ||
@@ -226,2 +206,4 @@ tuplehash256, | ||
const h7g = m14('abc'); | ||
const h7t1 = turboshake128('abc'); | ||
const h7t2 = turboshake256('def', { D: 0x05 }); | ||
const h7i = tuplehash128(['ab', 'c']); // tuplehash(['ab', 'c']) !== tuplehash(['a', 'bc']) !== tuplehash(['abc']) | ||
@@ -240,9 +222,9 @@ // Same as k12/blake3, but without reduced number of rounds. Doesn't speedup anything due lack of SIMD and threading, | ||
cSHAKE, KMAC, TupleHash, ParallelHash + XOF variants | ||
- 🦘 K12 ([KangarooTwelve Paper](https://keccak.team/files/KangarooTwelve.pdf), | ||
[RFC Draft](https://www.ietf.org/archive/id/draft-irtf-cfrg-kangarootwelve-06.txt)) | ||
and M14 aka MarsupilamiFourteen are basically parallel versions of Keccak with | ||
reduced number of rounds (same as Blake3 and ParallelHash). | ||
- [Reduced-round Keccak](https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/): | ||
- 🦘 K12 aka KangarooTwelve | ||
- M14 aka MarsupilamiFourteen | ||
- TurboSHAKE | ||
- [KeccakPRG](https://keccak.team/files/CSF-0.1.pdf): Pseudo-random generator based on Keccak | ||
##### RIPEMD-160 | ||
##### ripemd160 | ||
@@ -253,3 +235,3 @@ ```typescript | ||
const hash8 = ripemd160('abc'); | ||
const hash9 = ripemd160() | ||
const hash9 = ripemd160 | ||
.create() | ||
@@ -263,3 +245,3 @@ .update(Uint8Array.from([1, 2, 3])) | ||
##### BLAKE2b, BLAKE2s | ||
##### blake2b, blake2s, blake3 | ||
@@ -276,9 +258,3 @@ ```typescript | ||
.digest(); | ||
``` | ||
See [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693), [Website](https://www.blake2.net). | ||
##### BLAKE3 | ||
```typescript | ||
import { blake3 } from '@noble/hashes/blake3'; | ||
@@ -289,4 +265,6 @@ // All params are optional | ||
##### SHA1 (legacy) | ||
See [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693), [Website](https://www.blake2.net). | ||
##### sha1: legacy hash | ||
SHA1 was cryptographically broken, however, it was not broken for cases like HMAC. | ||
@@ -303,3 +281,3 @@ | ||
##### HMAC | ||
##### hmac | ||
@@ -310,3 +288,6 @@ ```typescript | ||
const mac1 = hmac(sha256, 'key', 'message'); | ||
const mac2 = hmac.create(sha256, Uint8Array.from([1, 2, 3])).update(Uint8Array.from([4, 5, 6])).digest(); | ||
const mac2 = hmac | ||
.create(sha256, Uint8Array.from([1, 2, 3])) | ||
.update(Uint8Array.from([4, 5, 6])) | ||
.digest(); | ||
``` | ||
@@ -316,3 +297,3 @@ | ||
##### HKDF | ||
##### hkdf | ||
@@ -338,3 +319,3 @@ ```typescript | ||
##### PBKDF2 | ||
##### pbkdf2 | ||
@@ -354,3 +335,3 @@ ```typescript | ||
##### Scrypt | ||
##### scrypt | ||
@@ -392,3 +373,3 @@ ```typescript | ||
##### Argon2 | ||
##### argon2 | ||
@@ -402,32 +383,2 @@ Experimental Argon2 RFC 9106 implementation. It may be removed at any time. | ||
##### ESKDF | ||
A tiny stretched KDF for various applications like AES key-gen. Takes >= 2 seconds to execute. | ||
Takes following params: | ||
- `username` - username, email, or identifier, min: 8 characters, should have enough entropy | ||
- `password` - min: 8 characters, should have enough entropy | ||
Produces ESKDF instance that has `deriveChildKey(protocol, accountId[, options])` function. | ||
- `protocol` - 3-15 character protocol name | ||
- `accountId` - numeric identifier of account | ||
- `options` - `keyLength: 32` with specified key length (default is 32), | ||
or `modulus: 2n ** 221n - 17n` with specified modulus. It will fetch modulus + 64 bits of | ||
data, execute modular division. The result will have negligible bias as per FIPS 186 B.4.1. | ||
Can be used to generate, for example, elliptic curve keys. | ||
Takes username and password, then takes protocol name and account id. | ||
```typescript | ||
import { eskdf } from '@noble/hashes/eskdf'; | ||
const kdf = await eskdf('example@university', 'beginning-new-example'); | ||
console.log(kdf.fingerprint); | ||
const key1 = kdf.deriveChildKey('aes', 0); | ||
const key2 = kdf.deriveChildKey('aes', 0, { keyLength: 16 }); | ||
const ecc1 = kdf.deriveChildKey('ecc', 0, { modulus: 2n ** 252n - 27742317777372353535851937790883648493n }) | ||
kdf.expire(); | ||
``` | ||
##### utils | ||
@@ -443,45 +394,105 @@ | ||
##### All available imports | ||
```js | ||
// sha384 is here, because it uses same internals as sha512 | ||
import { sha512, sha512_256, sha384 } from '@noble/hashes/sha512'; | ||
// prettier-ignore | ||
import { | ||
sha3_224, sha3_256, sha3_384, sha3_512, | ||
keccak_224, keccak_256, keccak_384, keccak_512, | ||
shake128, shake256 | ||
} from '@noble/hashes/sha3'; | ||
// prettier-ignore | ||
import { | ||
cshake128, cshake256, | ||
k12, m14, | ||
turboshake128, turboshake256, | ||
kmac128, kmac256, | ||
tuplehash256, parallelhash256, keccakprg | ||
} from '@noble/hashes/sha3-addons'; | ||
import { ripemd160 } from '@noble/hashes/ripemd160'; | ||
import { blake3 } from '@noble/hashes/blake3'; | ||
import { blake2b } from '@noble/hashes/blake2b'; | ||
import { blake2s } from '@noble/hashes/blake2s'; | ||
import { hmac } from '@noble/hashes/hmac'; | ||
import { hkdf } from '@noble/hashes/hkdf'; | ||
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2'; | ||
import { scrypt, scryptAsync } from '@noble/hashes/scrypt'; | ||
import { sha1 } from '@noble/hashes/sha1'; // legacy | ||
// small utility method that converts bytes to hex | ||
import { bytesToHex as toHex } from '@noble/hashes/utils'; | ||
console.log(toHex(sha256('abc'))); // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | ||
``` | ||
## Security | ||
Noble is production-ready. | ||
The library has been independently audited: | ||
1. The library has been audited in Jan 2022 by an independent security firm | ||
cure53: [PDF](https://cure53.de/pentest-report_hashing-libs.pdf). | ||
No vulnerabilities have been found. The audit has been funded by | ||
[Ethereum Foundation](https://ethereum.org/en/) with help of [Nomic Labs](https://nomiclabs.io). | ||
Modules `blake3`, `sha3-addons`, `sha1` and `argon2` have not been audited. | ||
See [changes since audit](https://github.com/paulmillr/noble-hashes/compare/1.0.0..main). | ||
2. The library has been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). | ||
You can run the fuzzer by yourself to check it. | ||
3. [Timing attack](https://en.wikipedia.org/wiki/Timing_attack) considerations: | ||
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to | ||
achieve in a scripting language. Which means _any other JS library can't have constant-timeness_. | ||
Even statically typed Rust, a language without GC, | ||
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) | ||
for some cases. If your goal is absolute security, don't use any JS lib — including | ||
bindings to native ones. Use low-level libraries & languages. Nonetheless we're | ||
targetting algorithmic constant time. | ||
4. Memory dump considerations: the library shares state buffers between hash | ||
function calls. The buffers are zeroed-out after each call. However, if an attacker | ||
can read application memory, you are doomed in any case: | ||
- At some point, input will be a string and strings are immutable in JS: | ||
there is no way to overwrite them with zeros. For example: deriving | ||
key from `scrypt(password, salt)` where password and salt are strings | ||
- Input from a file will stay in file buffers | ||
- Input / output will be re-used multiple times in application which means | ||
it could stay in memory | ||
- `await anything()` will always write all internal variables (including numbers) | ||
to memory. With async functions / Promises there are no guarantees when the code | ||
chunk would be executed. Which means attacker can have plenty of time to read data from memory | ||
- There is no way to guarantee anything about zeroing sensitive data without | ||
complex tests-suite which will dump process memory and verify that there is | ||
no sensitive data left. For JS it means testing all browsers (incl. mobile), | ||
which is complex. And of course it will be useless without using the same | ||
test-suite in the actual application that consumes the library | ||
- at version 1.0.0, in Jan 2022, by [cure53](https://cure53.de) | ||
- PDFs: [online](https://cure53.de/pentest-report_hashing-libs.pdf), [offline, in-repo](./audit/2022-01-05-cure53-audit-nbl2.pdf) | ||
- [Changes since audit](https://github.com/paulmillr/noble-hashes/compare/1.0.0..main). | ||
- Scope: everything, besides `blake3`, `sha3-addons`, `sha1` and `argon2`, which have not been audited | ||
- The audit has been funded by [Ethereum Foundation](https://ethereum.org/en/) with help of [Nomic Labs](https://nomiclabs.io) | ||
We consider infrastructure attacks like rogue NPM modules very important; that's | ||
why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. | ||
If your app uses 500 dependencies, any dep could get hacked and you'll be downloading | ||
malware with every `npm install`. Our goal is to minimize this attack vector. | ||
It is tested against property-based, cross-library and Wycheproof vectors, | ||
and has fuzzing by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). | ||
If you see anything unusual: investigate and report. | ||
### Constant-timeness | ||
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to | ||
achieve [timing attack](https://en.wikipedia.org/wiki/Timing_attack) resistance | ||
in a scripting language. Which means _any other JS library can't have | ||
constant-timeness_. Even statically typed Rust, a language without GC, | ||
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security) | ||
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. | ||
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time. | ||
### Memory dumping | ||
The library shares state buffers between hash | ||
function calls. The buffers are zeroed-out after each call. However, if an attacker | ||
can read application memory, you are doomed in any case: | ||
- At some point, input will be a string and strings are immutable in JS: | ||
there is no way to overwrite them with zeros. For example: deriving | ||
key from `scrypt(password, salt)` where password and salt are strings | ||
- Input from a file will stay in file buffers | ||
- Input / output will be re-used multiple times in application which means it could stay in memory | ||
- `await anything()` will always write all internal variables (including numbers) | ||
to memory. With async functions / Promises there are no guarantees when the code | ||
chunk would be executed. Which means attacker can have plenty of time to read data from memory | ||
- There is no way to guarantee anything about zeroing sensitive data without | ||
complex tests-suite which will dump process memory and verify that there is | ||
no sensitive data left. For JS it means testing all browsers (incl. mobile), | ||
which is complex. And of course it will be useless without using the same | ||
test-suite in the actual application that consumes the library | ||
### Supply chain security | ||
* **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures. | ||
* **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs | ||
* **Rare releasing** is followed to ensure less re-audit need for end-users | ||
* **Dependencies** are minimized and locked-down: | ||
- If your app has 500 dependencies, any dep could get hacked and you'll be downloading | ||
malware with every install. We make sure to use as few dependencies as possible | ||
- We prevent automatic dependency updates by locking-down version ranges. Every update is checked with `npm-diff` | ||
* **Dev Dependencies** are only used if you want to contribute to the repo. They are disabled for end-users: | ||
- scure-base, scure-bip32, scure-bip39, micro-bmark and micro-should are developed by the same author and follow identical security practices | ||
- prettier (linter), fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. The packages are big, which makes it hard to audit their source code thoroughly and fully | ||
### Randomness | ||
We're deferring to built-in | ||
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) | ||
which is considered cryptographically secure (CSPRNG). | ||
In the past, browsers had bugs that made it weak: it may happen again. | ||
Implementing a userspace CSPRNG to get resilient to the weakness | ||
is even worse: there is no reliable userspace source of quality entropy. | ||
## Speed | ||
@@ -550,2 +561,8 @@ | ||
## Resources | ||
Check out [paulmillr.com/noble](https://paulmillr.com/noble/) | ||
for useful resources, articles, documentation and demos | ||
related to the library. | ||
## License | ||
@@ -552,0 +569,0 @@ |
@@ -94,2 +94,17 @@ import { Input, Hash, HashXOF } from './utils.js'; | ||
}; | ||
export type TurboshakeOpts = ShakeOpts & { | ||
D?: number; | ||
}; | ||
export declare const turboshake128: { | ||
(msg: Input, opts?: TurboshakeOpts | undefined): Uint8Array; | ||
outputLen: number; | ||
blockLen: number; | ||
create(opts: TurboshakeOpts): HashXOF<HashXOF<Keccak>>; | ||
}; | ||
export declare const turboshake256: { | ||
(msg: Input, opts?: TurboshakeOpts | undefined): Uint8Array; | ||
outputLen: number; | ||
blockLen: number; | ||
create(opts: TurboshakeOpts): HashXOF<HashXOF<Keccak>>; | ||
}; | ||
export type KangarooOpts = { | ||
@@ -96,0 +111,0 @@ dkLen?: number; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.keccakprg = exports.m14 = exports.k12 = exports.parallelhash256xof = exports.parallelhash128xof = exports.parallelhash256 = exports.parallelhash128 = exports.tuplehash256xof = exports.tuplehash128xof = exports.tuplehash256 = exports.tuplehash128 = exports.kmac256xof = exports.kmac128xof = exports.kmac256 = exports.kmac128 = exports.cshake256 = exports.cshake128 = void 0; | ||
exports.keccakprg = exports.m14 = exports.k12 = exports.turboshake256 = exports.turboshake128 = exports.parallelhash256xof = exports.parallelhash128xof = exports.parallelhash256 = exports.parallelhash128 = exports.tuplehash256xof = exports.tuplehash128xof = exports.tuplehash256 = exports.tuplehash128 = exports.kmac256xof = exports.kmac128xof = exports.kmac256 = exports.kmac128 = exports.cshake256 = exports.cshake128 = void 0; | ||
const _assert_js_1 = require("./_assert.js"); | ||
@@ -205,2 +205,11 @@ const utils_js_1 = require("./utils.js"); | ||
exports.parallelhash256xof = (() => genPrl(136, 256 / 8, exports.cshake256, true))(); | ||
const genTurboshake = (blockLen, outputLen) => (0, utils_js_1.wrapXOFConstructorWithOpts)((opts = {}) => { | ||
const D = opts.D === undefined ? 0x1f : opts.D; | ||
// Section 2.1 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/ | ||
if (!Number.isSafeInteger(D) || D < 0x01 || D > 0x7f) | ||
throw new Error(`turboshake: wrong domain separation byte: ${D}, should be 0x01..0x7f`); | ||
return new sha3_js_1.Keccak(blockLen, D, opts.dkLen === undefined ? outputLen : opts.dkLen, true, 12); | ||
}); | ||
exports.turboshake128 = genTurboshake(168, 256 / 8); | ||
exports.turboshake256 = genTurboshake(136, 512 / 8); | ||
// Kangaroo | ||
@@ -207,0 +216,0 @@ // Same as NIST rightEncode, but returns [0] for zero string |
@@ -9,4 +9,12 @@ function number(n: number) { | ||
// copied from utils | ||
function isBytes(a: unknown): a is Uint8Array { | ||
return ( | ||
a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array') | ||
); | ||
} | ||
function bytes(b: Uint8Array | undefined, ...lengths: number[]) { | ||
if (!(b instanceof Uint8Array)) throw new Error('Expected Uint8Array'); | ||
if (!isBytes(b)) throw new Error('Expected Uint8Array'); | ||
if (lengths.length > 0 && !lengths.includes(b.length)) | ||
@@ -13,0 +21,0 @@ throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`); |
import { number as assertNumber } from './_assert.js'; | ||
import { Input, toBytes, wrapConstructorWithOpts, u32, Hash, HashXOF } from './utils.js'; | ||
import { | ||
Input, | ||
toBytes, | ||
wrapConstructorWithOpts, | ||
u32, | ||
Hash, | ||
HashXOF, | ||
wrapXOFConstructorWithOpts, | ||
} from './utils.js'; | ||
import { Keccak, ShakeOpts } from './sha3.js'; | ||
@@ -244,2 +252,19 @@ // cSHAKE && KMAC (NIST SP800-185) | ||
// Should be simple 'shake with 12 rounds', but no, we got whole new spec about Turbo SHAKE Pro MAX. | ||
export type TurboshakeOpts = ShakeOpts & { | ||
D?: number; // Domain separation byte | ||
}; | ||
const genTurboshake = (blockLen: number, outputLen: number) => | ||
wrapXOFConstructorWithOpts<HashXOF<Keccak>, TurboshakeOpts>((opts: TurboshakeOpts = {}) => { | ||
const D = opts.D === undefined ? 0x1f : opts.D; | ||
// Section 2.1 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/ | ||
if (!Number.isSafeInteger(D) || D < 0x01 || D > 0x7f) | ||
throw new Error(`turboshake: wrong domain separation byte: ${D}, should be 0x01..0x7f`); | ||
return new Keccak(blockLen, D, opts.dkLen === undefined ? outputLen : opts.dkLen, true, 12); | ||
}); | ||
export const turboshake128 = /* @__PURE__ */ genTurboshake(168, 256 / 8); | ||
export const turboshake256 = /* @__PURE__ */ genTurboshake(136, 512 / 8); | ||
// Kangaroo | ||
@@ -246,0 +271,0 @@ // Same as NIST rightEncode, but returns [0] for zero string |
@@ -8,3 +8,3 @@ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
// Makes the utils un-importable in browsers without a bundler. | ||
// Once node.js 18 is deprecated, we can just drop the import. | ||
// Once node.js 18 is deprecated (2025-04-30), we can just drop the import. | ||
import { crypto } from '@noble/hashes/crypto'; | ||
@@ -16,3 +16,2 @@ | ||
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array; | ||
// Cast array to different type | ||
@@ -23,2 +22,9 @@ export const u8 = (arr: TypedArray) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); | ||
function isBytes(a: unknown): a is Uint8Array { | ||
return ( | ||
a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array') | ||
); | ||
} | ||
// Cast array to view | ||
@@ -33,5 +39,8 @@ export const createView = (arr: TypedArray) => | ||
// early-throw an error because we don't support BE yet. | ||
// Other libraries would silently corrupt the data instead of throwing an error, | ||
// when they don't support it. | ||
export const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44; | ||
if (!isLE) throw new Error('Non little-endian hardware is not supported'); | ||
// Array where index 0xf0 (240) is mapped to string 'f0' | ||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => | ||
@@ -44,3 +53,3 @@ i.toString(16).padStart(2, '0') | ||
export function bytesToHex(bytes: Uint8Array): string { | ||
if (!u8a(bytes)) throw new Error('Uint8Array expected'); | ||
if (!isBytes(bytes)) throw new Error('Uint8Array expected'); | ||
// pre-caching improves the speed 6x | ||
@@ -54,2 +63,11 @@ let hex = ''; | ||
// We use optimized technique to convert hex string to byte array | ||
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const; | ||
function asciiToBase16(char: number): number | undefined { | ||
if (char >= asciis._0 && char <= asciis._9) return char - asciis._0; | ||
if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10); | ||
if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10); | ||
return; | ||
} | ||
/** | ||
@@ -60,11 +78,14 @@ * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23]) | ||
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex); | ||
const len = hex.length; | ||
if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len); | ||
const array = new Uint8Array(len / 2); | ||
for (let i = 0; i < array.length; i++) { | ||
const j = i * 2; | ||
const hexByte = hex.slice(j, j + 2); | ||
const byte = Number.parseInt(hexByte, 16); | ||
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence'); | ||
array[i] = byte; | ||
const hl = hex.length; | ||
const al = hl / 2; | ||
if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl); | ||
const array = new Uint8Array(al); | ||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { | ||
const n1 = asciiToBase16(hex.charCodeAt(hi)); | ||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); | ||
if (n1 === undefined || n2 === undefined) { | ||
const char = hex[hi] + hex[hi + 1]; | ||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi); | ||
} | ||
array[ai] = n1 * 16 + n2; | ||
} | ||
@@ -112,3 +133,3 @@ return array; | ||
if (typeof data === 'string') data = utf8ToBytes(data); | ||
if (!u8a(data)) throw new Error(`expected Uint8Array, got ${typeof data}`); | ||
if (!isBytes(data)) throw new Error(`expected Uint8Array, got ${typeof data}`); | ||
return data; | ||
@@ -121,10 +142,15 @@ } | ||
export function concatBytes(...arrays: Uint8Array[]): Uint8Array { | ||
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); | ||
let pad = 0; // walk through each item, ensure they have proper type | ||
arrays.forEach((a) => { | ||
if (!u8a(a)) throw new Error('Uint8Array expected'); | ||
r.set(a, pad); | ||
let sum = 0; | ||
for (let i = 0; i < arrays.length; i++) { | ||
const a = arrays[i]; | ||
if (!isBytes(a)) throw new Error('Uint8Array expected'); | ||
sum += a.length; | ||
} | ||
const res = new Uint8Array(sum); | ||
for (let i = 0, pad = 0; i < arrays.length; i++) { | ||
const a = arrays[i]; | ||
res.set(a, pad); | ||
pad += a.length; | ||
}); | ||
return r; | ||
} | ||
return res; | ||
} | ||
@@ -131,0 +157,0 @@ |
68
utils.js
@@ -10,5 +10,4 @@ "use strict"; | ||
// Makes the utils un-importable in browsers without a bundler. | ||
// Once node.js 18 is deprecated, we can just drop the import. | ||
// Once node.js 18 is deprecated (2025-04-30), we can just drop the import. | ||
const crypto_1 = require("@noble/hashes/crypto"); | ||
const u8a = (a) => a instanceof Uint8Array; | ||
// Cast array to different type | ||
@@ -19,2 +18,6 @@ const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); | ||
exports.u32 = u32; | ||
function isBytes(a) { | ||
return (a instanceof Uint8Array || | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); | ||
} | ||
// Cast array to view | ||
@@ -28,5 +31,8 @@ const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); | ||
// early-throw an error because we don't support BE yet. | ||
// Other libraries would silently corrupt the data instead of throwing an error, | ||
// when they don't support it. | ||
exports.isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44; | ||
if (!exports.isLE) | ||
throw new Error('Non little-endian hardware is not supported'); | ||
// Array where index 0xf0 (240) is mapped to string 'f0' | ||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0')); | ||
@@ -37,3 +43,3 @@ /** | ||
function bytesToHex(bytes) { | ||
if (!u8a(bytes)) | ||
if (!isBytes(bytes)) | ||
throw new Error('Uint8Array expected'); | ||
@@ -48,2 +54,13 @@ // pre-caching improves the speed 6x | ||
exports.bytesToHex = bytesToHex; | ||
// We use optimized technique to convert hex string to byte array | ||
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 }; | ||
function asciiToBase16(char) { | ||
if (char >= asciis._0 && char <= asciis._9) | ||
return char - asciis._0; | ||
if (char >= asciis._A && char <= asciis._F) | ||
return char - (asciis._A - 10); | ||
if (char >= asciis._a && char <= asciis._f) | ||
return char - (asciis._a - 10); | ||
return; | ||
} | ||
/** | ||
@@ -55,13 +72,15 @@ * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23]) | ||
throw new Error('hex string expected, got ' + typeof hex); | ||
const len = hex.length; | ||
if (len % 2) | ||
throw new Error('padded hex string expected, got unpadded hex of length ' + len); | ||
const array = new Uint8Array(len / 2); | ||
for (let i = 0; i < array.length; i++) { | ||
const j = i * 2; | ||
const hexByte = hex.slice(j, j + 2); | ||
const byte = Number.parseInt(hexByte, 16); | ||
if (Number.isNaN(byte) || byte < 0) | ||
throw new Error('Invalid byte sequence'); | ||
array[i] = byte; | ||
const hl = hex.length; | ||
const al = hl / 2; | ||
if (hl % 2) | ||
throw new Error('padded hex string expected, got unpadded hex of length ' + hl); | ||
const array = new Uint8Array(al); | ||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { | ||
const n1 = asciiToBase16(hex.charCodeAt(hi)); | ||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); | ||
if (n1 === undefined || n2 === undefined) { | ||
const char = hex[hi] + hex[hi + 1]; | ||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi); | ||
} | ||
array[ai] = n1 * 16 + n2; | ||
} | ||
@@ -107,3 +126,3 @@ return array; | ||
data = utf8ToBytes(data); | ||
if (!u8a(data)) | ||
if (!isBytes(data)) | ||
throw new Error(`expected Uint8Array, got ${typeof data}`); | ||
@@ -117,11 +136,16 @@ return data; | ||
function concatBytes(...arrays) { | ||
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); | ||
let pad = 0; // walk through each item, ensure they have proper type | ||
arrays.forEach((a) => { | ||
if (!u8a(a)) | ||
let sum = 0; | ||
for (let i = 0; i < arrays.length; i++) { | ||
const a = arrays[i]; | ||
if (!isBytes(a)) | ||
throw new Error('Uint8Array expected'); | ||
r.set(a, pad); | ||
sum += a.length; | ||
} | ||
const res = new Uint8Array(sum); | ||
for (let i = 0, pad = 0; i < arrays.length; i++) { | ||
const a = arrays[i]; | ||
res.set(a, pad); | ||
pad += a.length; | ||
}); | ||
return r; | ||
} | ||
return res; | ||
} | ||
@@ -128,0 +152,0 @@ exports.concatBytes = concatBytes; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
761453
148
10463
552