Argon2 is a password-hashing function that summarizes the state of the art in the design of memory-hard functions and can be used to hash passwords for credential storage, key derivation, or other applications.
12 |Here Argon2 library is compiled for browser runtime. Statistics, js library, source and docs on GitHub.
13 | 80 |Output should appear here. If not, please check DevTools in your browser.12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vanilla", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "run.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "private": true, 12 | "dependencies": { 13 | "argon2-browser": "^1.13.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/vanilla/run.js: -------------------------------------------------------------------------------- 1 | argon2 2 | .hash({ 3 | pass: 'p@ssw0rd', 4 | salt: 'somesalt' 5 | }) 6 | .then(hash => { 7 | document.querySelector('pre').innerText = 8 | `Encoded: ${hash.encoded}\n` + 9 | `Hex: ${hash.hashHex}\n`; 10 | 11 | argon2 12 | .verify({ 13 | pass: 'p@ssw0rd', 14 | encoded: hash.encoded 15 | }) 16 | .then(() => document.querySelector('pre').innerText += 'Verified OK') 17 | .catch(e => console.error('Error: ', e)); 18 | }) 19 | .catch(e => console.error('Error: ', e)); 20 | -------------------------------------------------------------------------------- /examples/webpack/argon2-demo-webpack.js: -------------------------------------------------------------------------------- 1 | const argon2 = require('argon2-browser'); 2 | 3 | argon2 4 | .hash({ 5 | pass: 'p@ssw0rd', 6 | salt: 'somesalt' 7 | }) 8 | .then(hash => { 9 | document.querySelector('pre').innerText = 10 | `Encoded: ${hash.encoded}\n` + 11 | `Hex: ${hash.hashHex}\n`; 12 | 13 | argon2 14 | .verify({ 15 | pass: 'p@ssw0rd', 16 | encoded: hash.encoded 17 | }) 18 | .then(() => document.querySelector('pre').innerText += 'Verified OK') 19 | .catch(e => console.error('Error: ', e)); 20 | }) 21 | .catch(e => console.error('Error: ', e)); 22 | -------------------------------------------------------------------------------- /examples/webpack/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
Output should appear here. If not, please check DevTools in your browser.11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "argon2-webpack", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "argon2-demo-webpack.js", 7 | "scripts": { 8 | "start": "webpack", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "argon2-browser": "^1.15.3", 15 | "base64-loader": "^1.0.0", 16 | "webpack": "^5.10.1" 17 | }, 18 | "devDependencies": { 19 | "webpack-cli": "^4.2.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // WebPack has native support for WebAssembly modules, however it's not ideal 4 | // and in our case it's introducing more issues than it solves. 5 | // 6 | // When building your own config, please make sure important bits 7 | // of this configuration are included there, otherwise you're likely to 8 | // see the same errors. 9 | // 10 | // If don't include these hacks and it still compiles successfully, 11 | // it means that you're using a more modern version of WebPack. 12 | // I'd be happy if you let me know that it's no longer needed! 13 | module.exports = { 14 | mode: 'production', 15 | entry: './argon2-demo-webpack.js', 16 | output: { 17 | path: path.resolve(__dirname, 'dist'), 18 | publicPath: 'dist/', 19 | filename: 'bundle.js', 20 | }, 21 | module: { 22 | // Makes WebPack think that we don't need to parse this module, 23 | // otherwise it tries to recompile it, but fails 24 | // 25 | // Error: Module not found: Error: Can't resolve 'env' 26 | noParse: /\.wasm$/, 27 | rules: [ 28 | { 29 | test: /\.wasm$/, 30 | // Tells WebPack that this module should be included as 31 | // base64-encoded binary file and not as code 32 | loader: 'base64-loader', 33 | // Disables WebPack's opinion where WebAssembly should be, 34 | // makes it think that it's not WebAssembly 35 | // 36 | // Error: WebAssembly module is included in initial chunk. 37 | type: 'javascript/auto', 38 | }, 39 | ], 40 | }, 41 | resolve: { 42 | // We're using different node.js modules in our code, 43 | // this prevents WebPack from failing on them or embedding 44 | // polyfills for them into the bundle. 45 | // 46 | // Error: Module not found: Error: Can't resolve 'fs' 47 | fallback: { 48 | path: false, 49 | fs: false, 50 | Buffer: false, 51 | process: false, 52 | }, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /lib/argon2.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define([], factory); 4 | } else if (typeof module === 'object' && module.exports) { 5 | module.exports = factory(); 6 | } else { 7 | root.argon2 = factory(); 8 | } 9 | })(typeof self !== 'undefined' ? self : this, function () { 10 | const global = typeof self !== 'undefined' ? self : this; 11 | 12 | /** 13 | * @enum 14 | */ 15 | const ArgonType = { 16 | Argon2d: 0, 17 | Argon2i: 1, 18 | Argon2id: 2, 19 | }; 20 | 21 | function loadModule(mem) { 22 | if (loadModule._promise) { 23 | return loadModule._promise; 24 | } 25 | if (loadModule._module) { 26 | return Promise.resolve(loadModule._module); 27 | } 28 | let promise; 29 | if ( 30 | global.process && 31 | global.process.versions && 32 | global.process.versions.node 33 | ) { 34 | promise = loadWasmModule().then( 35 | (Module) => 36 | new Promise((resolve) => { 37 | Module.postRun = () => resolve(Module); 38 | }) 39 | ); 40 | } else { 41 | promise = loadWasmBinary().then((wasmBinary) => { 42 | const wasmMemory = mem ? createWasmMemory(mem) : undefined; 43 | return initWasm(wasmBinary, wasmMemory); 44 | }); 45 | } 46 | loadModule._promise = promise; 47 | return promise.then((Module) => { 48 | loadModule._module = Module; 49 | delete loadModule._promise; 50 | return Module; 51 | }); 52 | } 53 | 54 | function initWasm(wasmBinary, wasmMemory) { 55 | return new Promise((resolve) => { 56 | global.Module = { 57 | wasmBinary, 58 | wasmMemory, 59 | postRun() { 60 | resolve(Module); 61 | }, 62 | }; 63 | return loadWasmModule(); 64 | }); 65 | } 66 | 67 | function loadWasmModule() { 68 | if (global.loadArgon2WasmModule) { 69 | return global.loadArgon2WasmModule(); 70 | } 71 | if (typeof require === 'function') { 72 | return Promise.resolve(require('../dist/argon2.js')); 73 | } 74 | return import('../dist/argon2.js'); 75 | } 76 | 77 | function loadWasmBinary() { 78 | if (global.loadArgon2WasmBinary) { 79 | return global.loadArgon2WasmBinary(); 80 | } 81 | if (typeof require === 'function') { 82 | return Promise.resolve(require('../dist/argon2.wasm')).then( 83 | (wasmModule) => { 84 | return decodeWasmBinary(wasmModule); 85 | } 86 | ); 87 | } 88 | const wasmPath = 89 | global.argon2WasmPath || 90 | 'node_modules/argon2-browser/dist/argon2.wasm'; 91 | return fetch(wasmPath) 92 | .then((response) => response.arrayBuffer()) 93 | .then((ab) => new Uint8Array(ab)); 94 | } 95 | 96 | function decodeWasmBinary(base64) { 97 | if (typeof Buffer === 'function') { 98 | return new Uint8Array(Buffer.from(base64, 'base64')); 99 | } 100 | const text = atob(base64); 101 | const binary = new Uint8Array(new ArrayBuffer(text.length)); 102 | for (let i = 0; i < text.length; i++) { 103 | binary[i] = text.charCodeAt(i); 104 | } 105 | return binary; 106 | } 107 | 108 | function createWasmMemory(mem) { 109 | const KB = 1024; 110 | const MB = 1024 * KB; 111 | const GB = 1024 * MB; 112 | const WASM_PAGE_SIZE = 64 * KB; 113 | 114 | const totalMemory = (2 * GB - 64 * KB) / WASM_PAGE_SIZE; 115 | const initialMemory = Math.min( 116 | Math.max(Math.ceil((mem * KB) / WASM_PAGE_SIZE), 256) + 256, 117 | totalMemory 118 | ); 119 | 120 | return new WebAssembly.Memory({ 121 | initial: initialMemory, 122 | maximum: totalMemory, 123 | }); 124 | } 125 | 126 | function allocateArray(Module, arr) { 127 | return Module.allocate(arr, 'i8', Module.ALLOC_NORMAL); 128 | } 129 | 130 | function allocateArrayStr(Module, arr) { 131 | const nullTerminatedArray = new Uint8Array([...arr, 0]); 132 | return allocateArray(Module, nullTerminatedArray); 133 | } 134 | 135 | function encodeUtf8(str) { 136 | if (typeof str !== 'string') { 137 | return str; 138 | } 139 | if (typeof TextEncoder === 'function') { 140 | return new TextEncoder().encode(str); 141 | } else if (typeof Buffer === 'function') { 142 | return Buffer.from(str); 143 | } else { 144 | throw new Error("Don't know how to encode UTF8"); 145 | } 146 | } 147 | 148 | /** 149 | * Argon2 hash 150 | * @param {string|Uint8Array} params.pass - password string 151 | * @param {string|Uint8Array} params.salt - salt string 152 | * @param {number} [params.time=1] - the number of iterations 153 | * @param {number} [params.mem=1024] - used memory, in KiB 154 | * @param {number} [params.hashLen=24] - desired hash length 155 | * @param {number} [params.parallelism=1] - desired parallelism 156 | * @param {number} [params.type=argon2.ArgonType.Argon2d] - hash type: 157 | * argon2.ArgonType.Argon2d 158 | * argon2.ArgonType.Argon2i 159 | * argon2.ArgonType.Argon2id 160 | * 161 | * @return Promise 162 | * 163 | * @example 164 | * argon2.hash({ pass: 'password', salt: 'somesalt' }) 165 | * .then(h => console.log(h.hash, h.hashHex, h.encoded)) 166 | * .catch(e => console.error(e.message, e.code)) 167 | */ 168 | function argon2Hash(params) { 169 | const mCost = params.mem || 1024; 170 | return loadModule(mCost).then((Module) => { 171 | const tCost = params.time || 1; 172 | const parallelism = params.parallelism || 1; 173 | const pwdEncoded = encodeUtf8(params.pass); 174 | const pwd = allocateArrayStr(Module, pwdEncoded); 175 | const pwdlen = pwdEncoded.length; 176 | const saltEncoded = encodeUtf8(params.salt); 177 | const salt = allocateArrayStr(Module, saltEncoded); 178 | const saltlen = saltEncoded.length; 179 | const argon2Type = params.type || ArgonType.Argon2d; 180 | const hash = Module.allocate( 181 | new Array(params.hashLen || 24), 182 | 'i8', 183 | Module.ALLOC_NORMAL 184 | ); 185 | const secret = params.secret 186 | ? allocateArray(Module, params.secret) 187 | : 0; 188 | const secretlen = params.secret ? params.secret.byteLength : 0; 189 | const ad = params.ad ? allocateArray(Module, params.ad) : 0; 190 | const adlen = params.ad ? params.ad.byteLength : 0; 191 | const hashlen = params.hashLen || 24; 192 | const encodedlen = Module._argon2_encodedlen( 193 | tCost, 194 | mCost, 195 | parallelism, 196 | saltlen, 197 | hashlen, 198 | argon2Type 199 | ); 200 | const encoded = Module.allocate( 201 | new Array(encodedlen + 1), 202 | 'i8', 203 | Module.ALLOC_NORMAL 204 | ); 205 | const version = 0x13; 206 | let err; 207 | let res; 208 | try { 209 | res = Module._argon2_hash_ext( 210 | tCost, 211 | mCost, 212 | parallelism, 213 | pwd, 214 | pwdlen, 215 | salt, 216 | saltlen, 217 | hash, 218 | hashlen, 219 | encoded, 220 | encodedlen, 221 | argon2Type, 222 | secret, 223 | secretlen, 224 | ad, 225 | adlen, 226 | version 227 | ); 228 | } catch (e) { 229 | err = e; 230 | } 231 | let result; 232 | if (res === 0 && !err) { 233 | let hashStr = ''; 234 | const hashArr = new Uint8Array(hashlen); 235 | for (let i = 0; i < hashlen; i++) { 236 | const byte = Module.HEAP8[hash + i]; 237 | hashArr[i] = byte; 238 | hashStr += ('0' + (0xff & byte).toString(16)).slice(-2); 239 | } 240 | const encodedStr = Module.UTF8ToString(encoded); 241 | result = { 242 | hash: hashArr, 243 | hashHex: hashStr, 244 | encoded: encodedStr, 245 | }; 246 | } else { 247 | try { 248 | if (!err) { 249 | err = Module.UTF8ToString( 250 | Module._argon2_error_message(res) 251 | ); 252 | } 253 | } catch (e) {} 254 | result = { message: err, code: res }; 255 | } 256 | try { 257 | Module._free(pwd); 258 | Module._free(salt); 259 | Module._free(hash); 260 | Module._free(encoded); 261 | if (ad) { 262 | Module._free(ad); 263 | } 264 | if (secret) { 265 | Module._free(secret); 266 | } 267 | } catch (e) {} 268 | if (err) { 269 | throw result; 270 | } else { 271 | return result; 272 | } 273 | }); 274 | } 275 | 276 | /** 277 | * Argon2 verify function 278 | * @param {string} params.pass - password string 279 | * @param {string|Uint8Array} params.encoded - encoded hash 280 | * @param {number} [params.type=argon2.ArgonType.Argon2d] - hash type: 281 | * argon2.ArgonType.Argon2d 282 | * argon2.ArgonType.Argon2i 283 | * argon2.ArgonType.Argon2id 284 | * 285 | * @returns Promise 286 | * 287 | * @example 288 | * argon2.verify({ pass: 'password', encoded: 'encoded-hash' }) 289 | * .then(() => console.log('OK')) 290 | * .catch(e => console.error(e.message, e.code)) 291 | */ 292 | function argon2Verify(params) { 293 | return loadModule().then((Module) => { 294 | const pwdEncoded = encodeUtf8(params.pass); 295 | const pwd = allocateArrayStr(Module, pwdEncoded); 296 | const pwdlen = pwdEncoded.length; 297 | const secret = params.secret 298 | ? allocateArray(Module, params.secret) 299 | : 0; 300 | const secretlen = params.secret ? params.secret.byteLength : 0; 301 | const ad = params.ad ? allocateArray(Module, params.ad) : 0; 302 | const adlen = params.ad ? params.ad.byteLength : 0; 303 | const encEncoded = encodeUtf8(params.encoded); 304 | const enc = allocateArrayStr(Module, encEncoded); 305 | let argon2Type = params.type; 306 | if (argon2Type === undefined) { 307 | let typeStr = params.encoded.split('$')[1]; 308 | if (typeStr) { 309 | typeStr = typeStr.replace('a', 'A'); 310 | argon2Type = ArgonType[typeStr] || ArgonType.Argon2d; 311 | } 312 | } 313 | let err; 314 | let res; 315 | try { 316 | res = Module._argon2_verify_ext( 317 | enc, 318 | pwd, 319 | pwdlen, 320 | secret, 321 | secretlen, 322 | ad, 323 | adlen, 324 | argon2Type 325 | ); 326 | } catch (e) { 327 | err = e; 328 | } 329 | let result; 330 | if (res || err) { 331 | try { 332 | if (!err) { 333 | err = Module.UTF8ToString( 334 | Module._argon2_error_message(res) 335 | ); 336 | } 337 | } catch (e) {} 338 | result = { message: err, code: res }; 339 | } 340 | try { 341 | Module._free(pwd); 342 | Module._free(enc); 343 | } catch (e) {} 344 | if (err) { 345 | throw result; 346 | } else { 347 | return result; 348 | } 349 | }); 350 | } 351 | 352 | function unloadRuntime() { 353 | if (loadModule._module) { 354 | loadModule._module.unloadRuntime(); 355 | delete loadModule._promise; 356 | delete loadModule._module; 357 | } 358 | } 359 | 360 | return { 361 | ArgonType, 362 | hash: argon2Hash, 363 | verify: argon2Verify, 364 | unloadRuntime, 365 | }; 366 | }); 367 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "argon2-browser", 3 | "version": "1.18.0", 4 | "description": "Argon2 library compiled for browser runtime", 5 | "main": "lib/argon2.js", 6 | "directories": { 7 | "doc": "docs", 8 | "lib": "lib" 9 | }, 10 | "scripts": { 11 | "test": "mocha", 12 | "build-bundle": "webpack" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/antelle/argon2-browser.git" 17 | }, 18 | "author": "Antelle", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/antelle/argon2-browser/issues" 22 | }, 23 | "homepage": "https://github.com/antelle/argon2-browser#readme", 24 | "devDependencies": { 25 | "base64-loader": "^1.0.0", 26 | "chai": "^4.3.4", 27 | "http-server": "^0.12.3", 28 | "mocha": "^8.4.0", 29 | "prettier": "^2.3.0", 30 | "puppeteer": "^9.1.1", 31 | "webpack": "^5.37.1", 32 | "webpack-cli": "^4.7.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /perf-test.c: -------------------------------------------------------------------------------- 1 | #include