first commit
Some checks failed
Some checks failed
This commit is contained in:
337
app/hash/page.tsx
Normal file
337
app/hash/page.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Lock, FileDigit, Copy, Eraser, Zap } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { toast } from "sonner";
|
||||
|
||||
|
||||
interface HashResult {
|
||||
md5: string;
|
||||
sha1: string;
|
||||
sha256: string;
|
||||
sha512: string;
|
||||
}
|
||||
|
||||
export default function HashPage() {
|
||||
const [input, setInput] = useState("");
|
||||
const [results, setResults] = useState<HashResult | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// MD5 Implementation preserved from original
|
||||
const calculateMD5 = async (str: string): Promise<string> => {
|
||||
const md5 = (string: string) => {
|
||||
const rotateLeft = (x: number, n: number) => (x << n) | (x >>> (32 - n));
|
||||
const addUnsigned = (x: number, y: number) => {
|
||||
const x4 = x & 0x80000000;
|
||||
const y4 = y & 0x80000000;
|
||||
const x8 = x & 0x40000000;
|
||||
const y8 = y & 0x40000000;
|
||||
const result = (x & 0x3fffffff) + (y & 0x3fffffff);
|
||||
if (x8 & y8) return result ^ 0x80000000 ^ x4 ^ y4;
|
||||
if (x8 | y8) {
|
||||
if (result & 0x40000000) return result ^ 0xc0000000 ^ x4 ^ y4;
|
||||
else return result ^ 0x40000000 ^ x4 ^ y4;
|
||||
} else {
|
||||
return result ^ x4 ^ y4;
|
||||
}
|
||||
};
|
||||
|
||||
const F = (x: number, y: number, z: number) => (x & y) | (~x & z);
|
||||
const G = (x: number, y: number, z: number) => (x & z) | (y & ~z);
|
||||
const H = (x: number, y: number, z: number) => x ^ y ^ z;
|
||||
const I = (x: number, y: number, z: number) => y ^ (x | ~z);
|
||||
|
||||
const FF = (a: number, b: number, c: number, d: number, x: number, s: number, ac: number) =>
|
||||
addUnsigned(rotateLeft(addUnsigned(addUnsigned(a, F(b, c, d)), addUnsigned(x, ac)), s), b);
|
||||
const GG = (a: number, b: number, c: number, d: number, x: number, s: number, ac: number) =>
|
||||
addUnsigned(rotateLeft(addUnsigned(addUnsigned(a, G(b, c, d)), addUnsigned(x, ac)), s), b);
|
||||
const HH = (a: number, b: number, c: number, d: number, x: number, s: number, ac: number) =>
|
||||
addUnsigned(rotateLeft(addUnsigned(addUnsigned(a, H(b, c, d)), addUnsigned(x, ac)), s), b);
|
||||
const II = (a: number, b: number, c: number, d: number, x: number, s: number, ac: number) =>
|
||||
addUnsigned(rotateLeft(addUnsigned(addUnsigned(a, I(b, c, d)), addUnsigned(x, ac)), s), b);
|
||||
|
||||
const convertToWordArray = (str: string) => {
|
||||
let lWordCount;
|
||||
const lMessageLength = str.length;
|
||||
const lNumberOfWords = (((lMessageLength + 8) - ((lMessageLength + 8) % 64)) / 64 + 1) * 16;
|
||||
const lWordArray = Array(lNumberOfWords - 1).fill(0);
|
||||
let lBytePosition = 0;
|
||||
let lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition);
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
};
|
||||
|
||||
const wordToHex = (lValue: number) => {
|
||||
let result = "";
|
||||
for (let lCount = 0; lCount <= 3; lCount++) {
|
||||
const lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
result += ("0" + lByte.toString(16)).slice(-2);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const utf8Encode = (str: string) => {
|
||||
return unescape(encodeURIComponent(str));
|
||||
};
|
||||
|
||||
const x = convertToWordArray(utf8Encode(string));
|
||||
let a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476;
|
||||
|
||||
for (let k = 0; k < x.length; k += 16) {
|
||||
const AA = a, BB = b, CC = c, DD = d;
|
||||
a = FF(a, b, c, d, x[k], 7, 0xd76aa478);
|
||||
d = FF(d, a, b, c, x[k + 1], 12, 0xe8c7b756);
|
||||
c = FF(c, d, a, b, x[k + 2], 17, 0x242070db);
|
||||
b = FF(b, c, d, a, x[k + 3], 22, 0xc1bdceee);
|
||||
a = FF(a, b, c, d, x[k + 4], 7, 0xf57c0faf);
|
||||
d = FF(d, a, b, c, x[k + 5], 12, 0x4787c62a);
|
||||
c = FF(c, d, a, b, x[k + 6], 17, 0xa8304613);
|
||||
b = FF(b, c, d, a, x[k + 7], 22, 0xfd469501);
|
||||
a = FF(a, b, c, d, x[k + 8], 7, 0x698098d8);
|
||||
d = FF(d, a, b, c, x[k + 9], 12, 0x8b44f7af);
|
||||
c = FF(c, d, a, b, x[k + 10], 17, 0xffff5bb1);
|
||||
b = FF(b, c, d, a, x[k + 11], 22, 0x895cd7be);
|
||||
a = FF(a, b, c, d, x[k + 12], 7, 0x6b901122);
|
||||
d = FF(d, a, b, c, x[k + 13], 12, 0xfd987193);
|
||||
c = FF(c, d, a, b, x[k + 14], 17, 0xa679438e);
|
||||
b = FF(b, c, d, a, x[k + 15], 22, 0x49b40821);
|
||||
a = GG(a, b, c, d, x[k + 1], 5, 0xf61e2562);
|
||||
d = GG(d, a, b, c, x[k + 6], 9, 0xc040b340);
|
||||
c = GG(c, d, a, b, x[k + 11], 14, 0x265e5a51);
|
||||
b = GG(b, c, d, a, x[k], 20, 0xe9b6c7aa);
|
||||
a = GG(a, b, c, d, x[k + 5], 5, 0xd62f105d);
|
||||
d = GG(d, a, b, c, x[k + 10], 9, 0x2441453);
|
||||
c = GG(c, d, a, b, x[k + 15], 14, 0xd8a1e681);
|
||||
b = GG(b, c, d, a, x[k + 4], 20, 0xe7d3fbc8);
|
||||
a = GG(a, b, c, d, x[k + 9], 5, 0x21e1cde6);
|
||||
d = GG(d, a, b, c, x[k + 14], 9, 0xc33707d6);
|
||||
c = GG(c, d, a, b, x[k + 3], 14, 0xf4d50d87);
|
||||
b = GG(b, c, d, a, x[k + 8], 20, 0x455a14ed);
|
||||
a = GG(a, b, c, d, x[k + 13], 5, 0xa9e3e905);
|
||||
d = GG(d, a, b, c, x[k + 2], 9, 0xfcefa3f8);
|
||||
c = GG(c, d, a, b, x[k + 7], 14, 0x676f02d9);
|
||||
b = GG(b, c, d, a, x[k + 12], 20, 0x8d2a4c8a);
|
||||
a = HH(a, b, c, d, x[k + 5], 4, 0xfffa3942);
|
||||
d = HH(d, a, b, c, x[k + 8], 11, 0x8771f681);
|
||||
c = HH(c, d, a, b, x[k + 11], 16, 0x6d9d6122);
|
||||
b = HH(b, c, d, a, x[k + 14], 23, 0xfde5380c);
|
||||
a = HH(a, b, c, d, x[k + 1], 4, 0xa4beea44);
|
||||
d = HH(d, a, b, c, x[k + 4], 11, 0x4bdecfa9);
|
||||
c = HH(c, d, a, b, x[k + 7], 16, 0xf6bb4b60);
|
||||
b = HH(b, c, d, a, x[k + 10], 23, 0xbebfbc70);
|
||||
a = HH(a, b, c, d, x[k + 13], 4, 0x289b7ec6);
|
||||
d = HH(d, a, b, c, x[k], 11, 0xeaa127fa);
|
||||
c = HH(c, d, a, b, x[k + 3], 16, 0xd4ef3085);
|
||||
b = HH(b, c, d, a, x[k + 6], 23, 0x4881d05);
|
||||
a = HH(a, b, c, d, x[k + 9], 4, 0xd9d4d039);
|
||||
d = HH(d, a, b, c, x[k + 12], 11, 0xe6db99e5);
|
||||
c = HH(c, d, a, b, x[k + 15], 16, 0x1fa27cf8);
|
||||
b = HH(b, c, d, a, x[k + 2], 23, 0xc4ac5665);
|
||||
a = II(a, b, c, d, x[k], 6, 0xf4292244);
|
||||
d = II(d, a, b, c, x[k + 7], 10, 0x432aff97);
|
||||
c = II(c, d, a, b, x[k + 14], 15, 0xab9423a7);
|
||||
b = II(b, c, d, a, x[k + 5], 21, 0xfc93a039);
|
||||
a = II(a, b, c, d, x[k + 12], 6, 0x655b59c3);
|
||||
d = II(d, a, b, c, x[k + 3], 10, 0x8f0ccc92);
|
||||
c = II(c, d, a, b, x[k + 10], 15, 0xffeff47d);
|
||||
b = II(b, c, d, a, x[k + 1], 21, 0x85845dd1);
|
||||
a = II(a, b, c, d, x[k + 8], 6, 0x6fa87e4f);
|
||||
d = II(d, a, b, c, x[k + 15], 10, 0xfe2ce6e0);
|
||||
c = II(c, d, a, b, x[k + 6], 15, 0xa3014314);
|
||||
b = II(b, c, d, a, x[k + 13], 21, 0x4e0811a1);
|
||||
a = II(a, b, c, d, x[k + 4], 6, 0xf7537e82);
|
||||
d = II(d, a, b, c, x[k + 11], 10, 0xbd3af235);
|
||||
c = II(c, d, a, b, x[k + 2], 15, 0x2ad7d2bb);
|
||||
b = II(b, c, d, a, x[k + 9], 21, 0xeb86d391);
|
||||
a = addUnsigned(a, AA);
|
||||
b = addUnsigned(b, BB);
|
||||
c = addUnsigned(c, CC);
|
||||
d = addUnsigned(d, DD);
|
||||
}
|
||||
return wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
|
||||
};
|
||||
return md5(str);
|
||||
};
|
||||
|
||||
const calculateHash = async () => {
|
||||
if (!input) {
|
||||
toast.warning("请输入要计算哈希的内容");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(input);
|
||||
|
||||
const [sha1, sha256, sha512] = await Promise.all([
|
||||
crypto.subtle.digest("SHA-1", data),
|
||||
crypto.subtle.digest("SHA-256", data),
|
||||
crypto.subtle.digest("SHA-512", data),
|
||||
]);
|
||||
|
||||
const md5Hash = await calculateMD5(input);
|
||||
|
||||
const arrayBufferToHex = (buffer: ArrayBuffer): string => {
|
||||
return Array.from(new Uint8Array(buffer))
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
};
|
||||
|
||||
setResults({
|
||||
md5: md5Hash,
|
||||
sha1: arrayBufferToHex(sha1),
|
||||
sha256: arrayBufferToHex(sha256),
|
||||
sha512: arrayBufferToHex(sha512),
|
||||
});
|
||||
toast.success("计算完成");
|
||||
} catch {
|
||||
toast.error("计算失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = async (text: string, name: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast.success(`${name} 已复制`);
|
||||
} catch {
|
||||
toast.error("复制失败");
|
||||
}
|
||||
};
|
||||
|
||||
const clearAll = () => {
|
||||
setInput("");
|
||||
setResults(null);
|
||||
};
|
||||
|
||||
const HashResultItem = ({ label, value }: { label: string; value: string }) => (
|
||||
<div className="rounded-lg bg-muted p-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium text-sm">{label}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 gap-1"
|
||||
onClick={() => copyToClipboard(value, label)}
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
<span className="text-xs">复制</span>
|
||||
</Button>
|
||||
</div>
|
||||
<code className="block break-all font-mono text-sm leading-relaxed bg-background/50 p-2 rounded border">
|
||||
{value}
|
||||
</code>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
{/* Page Header */}
|
||||
<div className="flex items-center space-x-4 border-b pb-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-linear-to-br from-red-500 to-rose-600 shadow-lg">
|
||||
<FileDigit className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Hash 计算</h1>
|
||||
<p className="text-muted-foreground">
|
||||
计算文本的 MD5、SHA1、SHA256、SHA512 哈希值
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<div className="space-y-6">
|
||||
<Card className="h-full flex flex-col">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
|
||||
<CardTitle className="text-base font-medium">输入文本</CardTitle>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={calculateHash} disabled={loading || !input} className="gap-2" size="sm">
|
||||
<Zap className="h-4 w-4" />
|
||||
{loading ? "计算中..." : "计算哈希"}
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" onClick={clearAll} className="h-8 w-8 text-destructive">
|
||||
<Eraser className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 min-h-100">
|
||||
<Textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="请输入要计算哈希值的文本..."
|
||||
className="h-full min-h-75 resize-none font-mono"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card className="h-full flex flex-col">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base font-medium flex items-center gap-2">
|
||||
<Lock className="h-4 w-4" />
|
||||
哈希结果
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1">
|
||||
{results ? (
|
||||
<div className="space-y-3">
|
||||
<HashResultItem label="MD5 (32位)" value={results.md5} />
|
||||
<HashResultItem label="SHA1 (40位)" value={results.sha1} />
|
||||
<HashResultItem label="SHA256 (64位)" value={results.sha256} />
|
||||
<HashResultItem label="SHA512 (128位)" value={results.sha512} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full min-h-75 text-muted-foreground">
|
||||
<FileDigit className="h-16 w-16 mb-4 opacity-20" />
|
||||
<p>输入文本后点击计算哈希</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<span className="text-xl">💡</span> 使用说明
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<ul className="list-disc pl-4 text-sm text-muted-foreground space-y-1">
|
||||
<li>MD5 产生 128 位(16 字节)哈希值,已不推荐用于安全场景</li>
|
||||
<li>SHA1 产生 160 位(20 字节)哈希值</li>
|
||||
<li>SHA256 产生 256 位(32 字节)哈希值,安全性较高</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<ul className="list-disc pl-4 text-sm text-muted-foreground space-y-1">
|
||||
<li>SHA512 产生 512 位(64 字节)哈希值,安全性最高</li>
|
||||
<li>哈希函数是单向的,无法从哈希值反推原文</li>
|
||||
<li>相同输入总是产生相同输出</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user