diff --git a/package.json b/package.json index fd6c02e6..a4c236af 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "figlet": "^1.7.0", "figue": "^1.2.0", "fuse.js": "^6.6.2", + "hash-wasm": "^4.11.0", "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd6c38c9..c9329a67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ dependencies: fuse.js: specifier: ^6.6.2 version: 6.6.2 + hash-wasm: + specifier: ^4.11.0 + version: 4.11.0 highlight.js: specifier: ^11.7.0 version: 11.7.0 @@ -3351,7 +3354,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.7.2(vue@3.3.4) + '@vueuse/shared': 10.9.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3993,10 +3996,10 @@ packages: - vue dev: false - /@vueuse/shared@10.7.2(vue@3.3.4): - resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} + /@vueuse/shared@10.9.0(vue@3.3.4): + resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} dependencies: - vue-demi: 0.14.6(vue@3.3.4) + vue-demi: 0.14.7(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -5969,6 +5972,10 @@ packages: function-bind: 1.1.2 dev: true + /hash-wasm@4.11.0: + resolution: {integrity: sha512-HVusNXlVqHe0fzIzdQOGolnFN6mX/fqcrSAOcTBXdvzrXVHwTz11vXeKRmkR5gTuwVpvHZEIyKoePDvuAR+XwQ==} + dev: false + /hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} @@ -9151,8 +9158,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.6(vue@3.3.4): - resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} + /vue-demi@0.14.7(vue@3.3.4): + resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} hasBin: true requiresBuild: true diff --git a/src/composable/queryParams.ts b/src/composable/queryParams.ts index 9699abbc..9e184610 100644 --- a/src/composable/queryParams.ts +++ b/src/composable/queryParams.ts @@ -1,7 +1,8 @@ import { useRouteQuery } from '@vueuse/router'; import { computed } from 'vue'; +import { useStorage } from '@vueuse/core'; -export { useQueryParam }; +export { useQueryParam, useQueryParamOrStorage }; const transformers = { number: { @@ -33,3 +34,31 @@ function useQueryParam({ name, defaultValue }: { name: string; defaultValue: }, }); } + +function useQueryParamOrStorage({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue?: T }) { + const type = typeof defaultValue; + const transformer = transformers[type as keyof typeof transformers] ?? transformers.string; + + const storageRef = useStorage(storageName, defaultValue); + const storageDefaultValue = storageRef.value ?? defaultValue; + + const proxy = useRouteQuery(name, transformer.toQuery(storageDefaultValue as never)); + + const ref = computed({ + get() { + return transformer.fromQuery(proxy.value) as unknown as T; + }, + set(value) { + proxy.value = transformer.toQuery(value as never); + }, + }); + + watch( + ref, + (newValue) => { + storageRef.value = newValue; + }, + ); + + return ref; +} diff --git a/src/tools/hash-text/hash-text.vue b/src/tools/hash-text/hash-text.vue index 6eae9818..c6466f20 100644 --- a/src/tools/hash-text/hash-text.vue +++ b/src/tools/hash-text/hash-text.vue @@ -2,9 +2,30 @@ import type { lib } from 'crypto-js'; import { MD5, RIPEMD160, SHA1, SHA224, SHA256, SHA3, SHA384, SHA512, enc } from 'crypto-js'; +import { + adler32, + argon2d, + argon2i, + argon2id, + blake2b, + blake2s, + blake3, + crc32, + crc32c, + createSHA256, + keccak, + pbkdf2, + sm3, + whirlpool, + xxhash128, + xxhash3, + xxhash32, + xxhash64, +} from 'hash-wasm'; + import InputCopyable from '../../components/InputCopyable.vue'; import { convertHexToBin } from './hash-text.service'; -import { useQueryParam } from '@/composable/queryParams'; +import { useQueryParamOrStorage } from '@/composable/queryParams'; const algos = { MD5, @@ -20,7 +41,27 @@ const algos = { type AlgoNames = keyof typeof algos; type Encoding = keyof typeof enc | 'Bin'; const algoNames = Object.keys(algos) as AlgoNames[]; -const encoding = useQueryParam({ defaultValue: 'Hex', name: 'encoding' }); + +const algosWasm = { + adler32, + crc32, + crc32c, + blake2b, + blake2s, + blake3, + keccak, + xxhash32, + xxhash64, + xxhash3, + xxhash128, + sm3, + whirlpool, +} as const; + +type AlgoWasmNames = keyof typeof algosWasm; +const algoWasmNames = Object.keys(algosWasm) as AlgoWasmNames[]; + +const encoding = useQueryParamOrStorage({ defaultValue: 'Hex', storageName: 'hash-text:encoding', name: 'encoding' }); const clearText = ref(''); function formatWithEncoding(words: lib.WordArray, encoding: Encoding) { @@ -32,12 +73,91 @@ function formatWithEncoding(words: lib.WordArray, encoding: Encoding) { } const hashText = (algo: AlgoNames, value: string) => formatWithEncoding(algos[algo](value), encoding.value); + +const defaultHashWasmValues = { + adler32: '', + crc32: '', + crc32c: '', + blake2b: '', + blake2s: '', + blake3: '', + keccak: '', + xxhash32: '', + xxhash64: '', + xxhash3: '', + xxhash128: '', + sm3: '', + whirlpool: '', +}; +const hashWasmValues = computedAsync(async () => { + const encodingValue = encoding.value; + const clearTextValue = clearText.value; + const ret = defaultHashWasmValues; + for (const algo of algoWasmNames) { + ret[algo] = formatWithEncoding(enc.Hex.parse(await algosWasm[algo](clearTextValue)), encodingValue); + } + return ret; +}, defaultHashWasmValues); + +const salt = new Uint8Array(16); +window.crypto.getRandomValues(salt); +const hashWasmArgon2i = computedAsync(async () => { + const clearTextValue = clearText.value; + return await argon2i({ + password: clearTextValue, + salt, + parallelism: 1, + memorySize: 128, + iterations: 4, + hashLength: 16, + outputType: 'encoded', + }); +}); +const hashWasmArgon2d = computedAsync(async () => { + const clearTextValue = clearText.value; + return await argon2d({ + password: clearTextValue, + salt, + parallelism: 1, + memorySize: 128, + iterations: 4, + hashLength: 16, + outputType: 'encoded', + }); +}); +const hashWasmArgon2id = computedAsync(async () => { + const clearTextValue = clearText.value; + return await argon2id({ + password: clearTextValue, + salt, + parallelism: 1, + memorySize: 128, + iterations: 4, + hashLength: 16, + outputType: 'encoded', + }); +}); +const hashWasmPBKDF2 = computedAsync(async () => { + const clearTextValue = clearText.value; + return await pbkdf2({ + password: clearTextValue, + salt, + iterations: 16, + hashLength: 32, + hashFunction: createSHA256(), + }); +}); diff --git a/src/tools/hash-text/index.ts b/src/tools/hash-text/index.ts index 2070e41d..d214cf97 100644 --- a/src/tools/hash-text/index.ts +++ b/src/tools/hash-text/index.ts @@ -5,7 +5,8 @@ import { translate } from '@/plugins/i18n.plugin'; export const tool = defineTool({ name: translate('tools.hash-text.title'), path: '/hash-text', - description: translate('tools.hash-text.description'), + description: + 'Hash a text string using the function you need : MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3, RIPEMD160, ADLER32, CRC32, CRC32C, BLAKE, KECCAK, XXHASH, SM3, WHIRLPOOL or Argon2(i/d/id)', keywords: [ 'hash', 'digest', @@ -20,6 +21,24 @@ export const tool = defineTool({ 'SHA384', 'SHA3', 'RIPEMD160', + 'ADLER32', + 'BLAKE2B', + 'BLAKE2S', + 'BLAKE3', + 'CRC32', + 'CRC32C', + 'KECCAK', + 'PBKDF2', + 'SM3', + 'WHIRLPOOL', + 'XXHASH128', + 'XXHASH3', + 'XXHASH32', + 'XXHASH64', + 'Argon2', + 'Argon2i', + 'Argon2d', + 'Argon2id', ], component: () => import('./hash-text.vue'), icon: EyeOff,