Skip to content

Commit 615cdd0

Browse files
committed
Merge branch 'feat/file-hasher' into chore/all-my-stuffs
# Conflicts: # components.d.ts # package.json # pnpm-lock.yaml # src/tools/index.ts
2 parents 2691579 + 9b22ee0 commit 615cdd0

File tree

5 files changed

+277
-1
lines changed

5 files changed

+277
-1
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"figlet": "^1.7.0",
8989
"figue": "^1.2.0",
9090
"fuse.js": "^6.6.2",
91+
"hash-wasm": "^4.11.0",
9192
"highlight.js": "^11.7.0",
9293
"iarna-toml-esm": "^3.0.5",
9394
"ibantools": "^4.3.3",
@@ -96,6 +97,7 @@
9697
"jsbarcode": "^3.11.6",
9798
"iconv-lite": "^0.6.3",
9899
"js-base64": "^3.7.6",
100+
"js-base64": "^3.7.7",
99101
"json5": "^2.2.3",
100102
"jwt-decode": "^3.1.2",
101103
"libphonenumber-js": "^1.10.28",

pnpm-lock.yaml

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/file-hasher/file-hasher.vue

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<script setup lang="ts">
2+
import {
3+
createAdler32, // (): Promise<IHasher>
4+
createBLAKE2b, // (bits?: number, key?: IDataType): Promise<IHasher> // default is 512 bits
5+
createBLAKE2s, // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
6+
createBLAKE3, // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
7+
createCRC32, // (): Promise<IHasher>
8+
createCRC32C, // (): Promise<IHasher>
9+
createKeccak, // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
10+
createMD4, // (): Promise<IHasher>
11+
createMD5, // (): Promise<IHasher>
12+
createRIPEMD160, // (): Promise<IHasher>
13+
createSHA1, // (): Promise<IHasher>
14+
createSHA224, // (): Promise<IHasher>
15+
createSHA256, // (): Promise<IHasher>
16+
createSHA3, // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
17+
createSHA384, // (): Promise<IHasher>
18+
createSHA512, // (): Promise<IHasher>
19+
createSM3, // (): Promise<IHasher>
20+
createWhirlpool, // (): Promise<IHasher>
21+
// createXXHash32, //(seed: number): Promise<IHasher>
22+
// createXXHash64, //(seedLow: number, seedHigh: number): Promise<IHasher>
23+
// createXXHash3, //(seedLow: number, seedHigh: number): Promise<IHasher>
24+
// createXXHash128, //(seedLow: number, seedHigh: number): Promise<IHasher>
25+
} from 'hash-wasm';
26+
import type { lib } from 'crypto-js';
27+
import { enc } from 'crypto-js';
28+
29+
import type { IHasher } from 'hash-wasm/dist/lib/WASMInterface';
30+
import InputCopyable from '../../components/InputCopyable.vue';
31+
import { convertHexToBin } from '../hash-text/hash-text.service';
32+
import { useQueryParamOrStorage } from '@/composable/queryParams';
33+
import { withDefaultOnError } from '@/utils/defaults';
34+
35+
const status = ref<'idle' | 'done' | 'error' | 'processing'>('idle');
36+
const file = ref<File | null>(null);
37+
38+
async function getHashersAsync() {
39+
return {
40+
adler32: await createAdler32(), // (): Promise<IHasher>
41+
BLAKE2b: await createBLAKE2b(), // (bits?: number, key?: IDataType): Promise<IHasher> // default is 512 bits
42+
BLAKE2s: await createBLAKE2s(), // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
43+
BLAKE3: await createBLAKE3(), // (bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
44+
CRC32: await createCRC32(), // (): Promise<IHasher>
45+
CRC32C: await createCRC32C(), // (): Promise<IHasher>
46+
Keccak_224: await createKeccak(224), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
47+
Keccak_256: await createKeccak(256), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
48+
Keccak_384: await createKeccak(384), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
49+
Keccak_512: await createKeccak(512), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
50+
MD4: await createMD4(), // (): Promise<IHasher>
51+
MD5: await createMD5(), // (): Promise<IHasher>
52+
RIPEMD160: await createRIPEMD160(), // (): Promise<IHasher>
53+
SHA1: await createSHA1(), // (): Promise<IHasher>
54+
SHA224: await createSHA224(), // (): Promise<IHasher>
55+
SHA256: await createSHA256(), // (): Promise<IHasher>
56+
SHA3_224: await createSHA3(224), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
57+
SHA3_256: await createSHA3(256), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
58+
SHA3_384: await createSHA3(384), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
59+
SHA3_512: await createSHA3(512), // (bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
60+
SHA384: await createSHA384(), // (): Promise<IHasher>
61+
SHA512: await createSHA512(), // (): Promise<IHasher>
62+
SM3: await createSM3(), // (): Promise<IHasher>
63+
Whirlpool: await createWhirlpool(), // (): Promise<IHasher>
64+
};
65+
}
66+
67+
const defaultHashWasmValues = {
68+
adler32: '',
69+
BLAKE2b: '',
70+
BLAKE2s: '',
71+
BLAKE3: '',
72+
CRC32: '',
73+
CRC32C: '',
74+
Keccak_224: '',
75+
Keccak_256: '',
76+
Keccak_384: '',
77+
Keccak_512: '',
78+
MD4: '',
79+
MD5: '',
80+
RIPEMD160: '',
81+
SHA1: '',
82+
SHA224: '',
83+
SHA256: '',
84+
SHA3_224: '',
85+
SHA3_256: '',
86+
SHA3_384: '',
87+
SHA3_512: '',
88+
SHA384: '',
89+
SHA512: '',
90+
SM3: '',
91+
Whirlpool: '',
92+
};
93+
const chunkSize = 64 * 1024 * 1024;
94+
const fileReader = new FileReader();
95+
96+
async function hashChunkAsync(chunk: Blob, hashers: IHasher[]) {
97+
return new Promise<void>((resolve, _reject) => {
98+
fileReader.onload = async (e) => {
99+
const view = new Uint8Array((e.target?.result as ArrayBuffer)!);
100+
for (const hasher of hashers) {
101+
hasher.update(view);
102+
}
103+
resolve();
104+
};
105+
106+
fileReader.readAsArrayBuffer(chunk);
107+
});
108+
}
109+
110+
type AlgoWasmNames = keyof typeof defaultHashWasmValues;
111+
const algoWasmNames = Object.keys(defaultHashWasmValues) as AlgoWasmNames[];
112+
113+
async function hashFileAsync(file: File) {
114+
const chunkNumber = Math.floor(file.size / chunkSize);
115+
116+
const hashers = await getHashersAsync();
117+
for (let i = 0; i <= chunkNumber; i++) {
118+
const chunk = file.slice(
119+
chunkSize * i,
120+
Math.min(chunkSize * (i + 1), file.size),
121+
);
122+
await hashChunkAsync(chunk, Object.values(hashers));
123+
}
124+
125+
const ret = { ...defaultHashWasmValues };
126+
for (const algo of algoWasmNames) {
127+
ret[algo as AlgoWasmNames] = hashers[algo as AlgoWasmNames].digest();
128+
}
129+
return Promise.resolve(ret);
130+
}
131+
132+
const hashes = ref(defaultHashWasmValues);
133+
async function onUpload(uploadedFile: File) {
134+
file.value = uploadedFile;
135+
136+
status.value = 'processing';
137+
try {
138+
hashes.value = await hashFileAsync(uploadedFile);
139+
status.value = 'done';
140+
}
141+
catch (e) {
142+
status.value = 'error';
143+
}
144+
}
145+
146+
type Encoding = keyof typeof enc | 'Bin';
147+
148+
const encoding = useQueryParamOrStorage<Encoding>({ defaultValue: 'Hex', storageName: 'hash-text:encoding', name: 'encoding' });
149+
150+
function formatWithEncoding(words: lib.WordArray, encoding: Encoding) {
151+
if (encoding === 'Bin') {
152+
return convertHexToBin(words.toString(enc.Hex));
153+
}
154+
155+
return words.toString(enc[encoding]);
156+
}
157+
158+
const hashWasmValues = computed(() => withDefaultOnError(() => {
159+
const encodingValue = encoding.value;
160+
const hashesValue = hashes.value;
161+
162+
const ret = defaultHashWasmValues;
163+
for (const algo of algoWasmNames) {
164+
ret[algo] = formatWithEncoding(enc.Hex.parse(hashesValue[algo]), encodingValue);
165+
}
166+
return ret;
167+
}, defaultHashWasmValues));
168+
</script>
169+
170+
<template>
171+
<div>
172+
<c-card>
173+
<c-file-upload
174+
title="Drag and drop a file here, or click to select a file"
175+
@file-upload="onUpload"
176+
/>
177+
178+
<n-divider />
179+
180+
<c-select
181+
v-model:value="encoding"
182+
mb-4
183+
label="Digest encoding"
184+
:options="[
185+
{
186+
label: 'Binary (base 2)',
187+
value: 'Bin',
188+
},
189+
{
190+
label: 'Hexadecimal (base 16)',
191+
value: 'Hex',
192+
},
193+
{
194+
label: 'Base64 (base 64)',
195+
value: 'Base64',
196+
},
197+
{
198+
label: 'Base64url (base 64 with url safe chars)',
199+
value: 'Base64url',
200+
},
201+
]"
202+
/>
203+
</c-card>
204+
205+
<div mt-3 flex justify-center>
206+
<c-alert v-if="status === 'error'" type="error">
207+
An error occured hashing file '{{ file?.name }}'.
208+
</c-alert>
209+
<n-spin
210+
v-if="status === 'processing'"
211+
size="small"
212+
/>
213+
</div>
214+
215+
<c-card v-if="status === 'done'" :title="`Hashes of ${file?.name}`">
216+
<div v-for="algo in algoWasmNames" :key="algo" style="margin: 5px 0">
217+
<n-input-group>
218+
<n-input-group-label style="flex: 0 0 120px">
219+
{{ algo.toUpperCase() }}
220+
</n-input-group-label>
221+
<InputCopyable :value="hashWasmValues[algo]" readonly />
222+
</n-input-group>
223+
</div>
224+
</c-card>
225+
</div>
226+
</template>

src/tools/file-hasher/index.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { EyeOff } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'File Hasher',
6+
path: '/file-hasher',
7+
description: 'Compute Hash of files',
8+
keywords: ['file', 'hash',
9+
'digest',
10+
'crypto',
11+
'security',
12+
'text',
13+
'MD5',
14+
'SHA1',
15+
'SHA256',
16+
'SHA224',
17+
'SHA512',
18+
'SHA384',
19+
'SHA3',
20+
'RIPEMD160',
21+
'ADLER32',
22+
'BLAKE2B',
23+
'BLAKE2S',
24+
'BLAKE3',
25+
'CRC32',
26+
'CRC32C',
27+
'KECCAK',
28+
'PBKDF2',
29+
'SM3',
30+
'WHIRLPOOL',
31+
'XXHASH128',
32+
'XXHASH3',
33+
'XXHASH32',
34+
'XXHASH64'],
35+
component: () => import('./file-hasher.vue'),
36+
icon: EyeOff,
37+
createdAt: new Date('2024-05-11'),
38+
});

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { tool as colorWheel } from './color-wheel';
99
import { tool as currencyConverter } from './currency-converter';
1010
import { tool as emailParser } from './email-parser';
1111
import { tool as outlookParser } from './outlook-parser';
12+
import { tool as fileHasher } from './file-hasher';
1213

1314
import { tool as cssXpathConverter } from './css-xpath-converter';
1415
import { tool as cssSelectorsMemo } from './css-selectors-memo';
@@ -119,6 +120,7 @@ export const toolsByCategory: ToolCategory[] = [
119120
tokenGenerator,
120121
hashText,
121122
crcCalculator,
123+
fileHasher,
122124
bcrypt,
123125
uuidGenerator,
124126
ulidGenerator,

0 commit comments

Comments
 (0)