Skip to content

Commit 04bd4bf

Browse files
committed
feat: HMAC generator addition
1 parent 56e9f2d commit 04bd4bf

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { CopyButton } from "@/Components/CopyButton";
2+
import {
3+
Button,
4+
Grid,
5+
Group,
6+
NativeSelect,
7+
PasswordInput,
8+
Stack,
9+
Text,
10+
Textarea,
11+
Title,
12+
} from "@mantine/core";
13+
import { notifications } from "@mantine/notifications";
14+
import { useCallback, useMemo, useState } from "react";
15+
16+
type Algo = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
17+
18+
async function computeHmac(
19+
algorithm: Algo,
20+
key: string,
21+
message: string,
22+
output: "hex" | "base64"
23+
) {
24+
const enc = new TextEncoder();
25+
const keyData = enc.encode(key);
26+
const msgData = enc.encode(message);
27+
const cryptoKey = await crypto.subtle.importKey(
28+
"raw",
29+
keyData,
30+
{ name: "HMAC", hash: { name: algorithm } },
31+
false,
32+
["sign"]
33+
);
34+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, msgData);
35+
const bytes = new Uint8Array(signature);
36+
if (output === "base64") {
37+
const bin = Array.from(bytes)
38+
.map(b => String.fromCharCode(b))
39+
.join("");
40+
return btoa(bin);
41+
}
42+
return Array.from(bytes)
43+
.map(b => b.toString(16).padStart(2, "0"))
44+
.join("");
45+
}
46+
47+
export default function HmacGenerator() {
48+
const [algorithm, setAlgorithm] = useState<Algo>("SHA-256");
49+
const [key, setKey] = useState<string>("");
50+
const [message, setMessage] = useState<string>("");
51+
const [output, setOutput] = useState<"hex" | "base64">("hex");
52+
const [result, setResult] = useState<string>("");
53+
const [loading, setLoading] = useState(false);
54+
55+
const run = useCallback(async () => {
56+
if (!key) {
57+
notifications.show({ title: "Key required", message: "Enter a secret key", color: "red" });
58+
return;
59+
}
60+
setLoading(true);
61+
try {
62+
const sig = await computeHmac(algorithm, key, message, output);
63+
setResult(sig);
64+
} catch (e: any) {
65+
notifications.show({ title: "Error", message: e?.message || String(e), color: "red" });
66+
} finally {
67+
setLoading(false);
68+
}
69+
}, [algorithm, key, message, output]);
70+
71+
const algoOptions = useMemo(
72+
() => [
73+
{ value: "SHA-1", label: "HMAC-SHA1" },
74+
{ value: "SHA-256", label: "HMAC-SHA256" },
75+
{ value: "SHA-384", label: "HMAC-SHA384" },
76+
{ value: "SHA-512", label: "HMAC-SHA512" },
77+
],
78+
[]
79+
);
80+
81+
return (
82+
<Stack className="overflow-padding" h="100%" gap="md" pt="xl">
83+
<Title order={4}>HMAC Generator</Title>
84+
<Grid align="end">
85+
<Grid.Col span={3}>
86+
<NativeSelect
87+
label="Algorithm"
88+
data={algoOptions}
89+
value={algorithm}
90+
onChange={v => setAlgorithm((v.currentTarget.value as Algo) || "SHA-256")}
91+
/>
92+
</Grid.Col>
93+
<Grid.Col span={7}>
94+
<PasswordInput
95+
label="Secret key"
96+
placeholder="Enter key"
97+
value={key}
98+
onChange={e => setKey(e.currentTarget.value)}
99+
/>
100+
</Grid.Col>
101+
<Grid.Col span={2}>
102+
<NativeSelect
103+
label="Output"
104+
data={[
105+
{ value: "hex", label: "Hex" },
106+
{ value: "base64", label: "Base64" },
107+
]}
108+
value={output}
109+
onChange={v => setOutput((v.currentTarget.value as any) || "hex")}
110+
/>
111+
</Grid.Col>
112+
</Grid>
113+
114+
<Grid>
115+
<Grid.Col span={6}>
116+
<Stack>
117+
<Textarea
118+
autosize
119+
minRows={10}
120+
value={message}
121+
onChange={e => setMessage(e.currentTarget.value)}
122+
placeholder="Type message to sign..."
123+
/>
124+
</Stack>
125+
</Grid.Col>
126+
<Grid.Col span={6}>
127+
<Stack gap={8}>
128+
<Group justify="space-between">
129+
<Text size="sm" c="dimmed">
130+
Signature
131+
</Text>
132+
<CopyButton
133+
value={result}
134+
size="xs"
135+
variant="subtle"
136+
label="Copy"
137+
fullWidth={false}
138+
/>
139+
</Group>
140+
<Textarea autosize minRows={8} readOnly value={result} />
141+
</Stack>
142+
</Grid.Col>
143+
</Grid>
144+
<Button loading={loading} onClick={run} variant="light">
145+
Generate
146+
</Button>
147+
</Stack>
148+
);
149+
}

0 commit comments

Comments
 (0)