From 8fa586731a67eb658be5b0f2ff5edeca523b7e62 Mon Sep 17 00:00:00 2001 From: ale Date: Mon, 8 Dec 2025 21:06:35 +0100 Subject: [PATCH 1/5] out bcrypt Signed-off-by: ale --- API.md | 1 - CHANGELOG.md | 3 +-- PROJECT_SUMMARY.md | 4 +--- QUICK_REFERENCE.md | 1 - README.md | 3 +-- app/api/search/route.ts | 8 ++------ app/layout.tsx | 8 ++++---- app/page.tsx | 8 ++------ lib/elasticsearch.ts | 3 --- lib/hash.ts | 23 +---------------------- package.json | 2 -- public/manifest.json | 2 +- scripts/index-file.ts | 17 +++++------------ 13 files changed, 18 insertions(+), 65 deletions(-) diff --git a/API.md b/API.md index 1f85a01..90c650e 100644 --- a/API.md +++ b/API.md @@ -179,7 +179,6 @@ The API automatically detects hash types based on length and format: | SHA1 | 40 | `^[a-f0-9]{40}$` | | SHA256 | 64 | `^[a-f0-9]{64}$` | | SHA512 | 128 | `^[a-f0-9]{128}$` | -| Bcrypt | 60 | `^\$2[abxy]\$` | Hashes are case-insensitive. diff --git a/CHANGELOG.md b/CHANGELOG.md index 865cb0c..7b2b868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added #### Core Features -- Hash search functionality for MD5, SHA1, SHA256, SHA512, and Bcrypt +- Hash search functionality for MD5, SHA1, SHA256, and SHA512 - Hash generation from plaintext input - Automatic detection of hash types based on length and pattern - Real-time hash generation with instant results - Copy to clipboard functionality for all hash values -- Bcrypt verification support #### Backend - Elasticsearch integration with configurable endpoint diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md index 670e903..35025be 100644 --- a/PROJECT_SUMMARY.md +++ b/PROJECT_SUMMARY.md @@ -13,7 +13,7 @@ ## ✨ Key Features ### 🔍 Hash Search -- Search for MD5, SHA1, SHA256, SHA512, and Bcrypt hashes +- Search for MD5, SHA1, SHA256, and SHA512 hashes - Automatic hash type detection - Case-insensitive matching - Real-time results @@ -174,7 +174,6 @@ export ELASTICSEARCH_NODE=http://localhost:9200 | SHA1 | 40 | `^[a-f0-9]{40}$` | | SHA256 | 64 | `^[a-f0-9]{64}$` | | SHA512 | 128 | `^[a-f0-9]{128}$` | -| Bcrypt | 60 | `^\$2[abxy]\$` | --- @@ -245,7 +244,6 @@ export ELASTICSEARCH_NODE=http://localhost:9200 ## 📈 Future Enhancements ### Planned Features -- Bcrypt hash validation - Argon2 hash support - Search history - Batch lookup diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index 6b83e89..5be492e 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -25,7 +25,6 @@ npm run index-file -- --help # Show help | SHA1 | 40 | `5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8` | | SHA256 | 64 | `5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8` | | SHA512 | 128 | `b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb9...` | -| Bcrypt | 60 | `$2b$10$N9qo8uLOickgx2ZMRZoMye...` | ## 🔌 API Quick Reference diff --git a/README.md b/README.md index 6b20ffc..7d399db 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A modern, high-performance hash search and generation tool powered by Elasticsea ## ✨ Features -- 🔍 **Hash Lookup**: Search for MD5, SHA1, SHA256, SHA512, and Bcrypt hashes +- 🔍 **Hash Lookup**: Search for MD5, SHA1, SHA256, and SHA512 hashes - 🔑 **Hash Generation**: Generate multiple hash types from plaintext - 💾 **Auto-Indexing**: Automatically stores searched plaintext and hashes - 📊 **Elasticsearch Backend**: Scalable storage with 10 shards for performance @@ -274,7 +274,6 @@ npm run lint | SHA1 | 40 | `^[a-f0-9]{40}$` | | SHA256 | 64 | `^[a-f0-9]{64}$` | | SHA512 | 128 | `^[a-f0-9]{128}$` | -| Bcrypt | 60 | `^\$2[abxy]\$` | ## 🚀 Performance diff --git a/app/api/search/route.ts b/app/api/search/route.ts index f7e6a4d..e53d805 100644 --- a/app/api/search/route.ts +++ b/app/api/search/route.ts @@ -8,7 +8,6 @@ interface HashDocument { sha1: string; sha256: string; sha512: string; - bcrypt: string; created_at?: string; } @@ -44,7 +43,7 @@ export async function POST(request: NextRequest) { index: INDEX_NAME, query: { term: { - [hashType]: hashType === 'bcrypt' ? cleanQuery : cleanQueryLower + [hashType]: cleanQueryLower } } }); @@ -66,7 +65,6 @@ export async function POST(request: NextRequest) { sha1: source.sha1, sha256: source.sha256, sha512: source.sha512, - bcrypt: source.bcrypt, } }; }) @@ -101,11 +99,10 @@ export async function POST(request: NextRequest) { sha1: existingDoc.sha1, sha256: existingDoc.sha256, sha512: existingDoc.sha512, - bcrypt: existingDoc.bcrypt, }; } else { // Plaintext not found, generate hashes and check if any hash already exists - hashes = await generateHashes(cleanQuery); + hashes = generateHashes(cleanQuery); const hashExistsResponse = await esClient.search({ index: INDEX_NAME, @@ -147,7 +144,6 @@ export async function POST(request: NextRequest) { sha1: hashes.sha1, sha256: hashes.sha256, sha512: hashes.sha512, - bcrypt: hashes.bcrypt, } }); } diff --git a/app/layout.tsx b/app/layout.tsx index 9b07bc1..d686628 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -14,8 +14,8 @@ const geistMono = Geist_Mono({ export const metadata: Metadata = { title: "Hasher - Hash Search & Generator", - description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, SHA512, and Bcrypt. Powered by Elasticsearch.", - keywords: ["hash", "md5", "sha1", "sha256", "sha512", "bcrypt", "hash generator", "hash search", "elasticsearch"], + description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, and SHA512. Powered by Elasticsearch.", + keywords: ["hash", "md5", "sha1", "sha256", "sha512", "hash generator", "hash search", "elasticsearch"], authors: [{ name: "Hasher" }], creator: "Hasher", publisher: "Hasher", @@ -28,7 +28,7 @@ export const metadata: Metadata = { openGraph: { type: "website", title: "Hasher - Hash Search & Generator", - description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, SHA512, and Bcrypt.", + description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, and SHA512.", siteName: "Hasher", images: [ { @@ -42,7 +42,7 @@ export const metadata: Metadata = { twitter: { card: "summary", title: "Hasher - Hash Search & Generator", - description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, SHA512, and Bcrypt.", + description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, and SHA512.", images: ["/logo.png"], }, viewport: { diff --git a/app/page.tsx b/app/page.tsx index cf7cbaa..c8c7704 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -15,7 +15,6 @@ interface SearchResult { sha1: string; sha256: string; sha512: string; - bcrypt: string; }; results?: Array<{ plaintext: string; @@ -24,7 +23,6 @@ interface SearchResult { sha1: string; sha256: string; sha512: string; - bcrypt: string; }; }>; message?: string; @@ -144,7 +142,7 @@ export default function Home() { Search for hashes or generate them from plaintext

- Supports MD5, SHA1, SHA256, SHA512, and Bcrypt + Supports MD5, SHA1, SHA256, and SHA512

{stats && (
@@ -214,7 +212,6 @@ export default function Home() { -
{result.wasGenerated && (
@@ -260,7 +257,6 @@ export default function Home() { -
))} @@ -304,7 +300,7 @@ export default function Home() {

Generate Hashes

- Enter any plaintext to instantly generate MD5, SHA1, SHA256, SHA512, and Bcrypt hashes. Results are saved automatically. + Enter any plaintext to instantly generate MD5, SHA1, SHA256, and SHA512 hashes. Results are saved automatically.

diff --git a/lib/elasticsearch.ts b/lib/elasticsearch.ts index 3c0ad0c..940d69c 100644 --- a/lib/elasticsearch.ts +++ b/lib/elasticsearch.ts @@ -46,9 +46,6 @@ export const INDEX_MAPPING = { sha512: { type: 'keyword' as const }, - bcrypt: { - type: 'keyword' as const - }, created_at: { type: 'date' as const } diff --git a/lib/hash.ts b/lib/hash.ts index 5e1d9c8..149a418 100644 --- a/lib/hash.ts +++ b/lib/hash.ts @@ -1,5 +1,4 @@ import crypto from 'crypto'; -import bcrypt from 'bcrypt'; export interface HashResult { plaintext: string; @@ -7,22 +6,18 @@ export interface HashResult { sha1: string; sha256: string; sha512: string; - bcrypt: string; } /** * Generate all common hashes for a given plaintext */ -export async function generateHashes(plaintext: string): Promise { - const bcryptHash = await bcrypt.hash(plaintext, 10); - +export function generateHashes(plaintext: string): HashResult { return { plaintext, md5: crypto.createHash('md5').update(plaintext).digest('hex'), sha1: crypto.createHash('sha1').update(plaintext).digest('hex'), sha256: crypto.createHash('sha256').update(plaintext).digest('hex'), sha512: crypto.createHash('sha512').update(plaintext).digest('hex'), - bcrypt: bcryptHash, }; } @@ -52,11 +47,6 @@ export function detectHashType(hash: string): string | null { return 'sha512'; } - // BCrypt: starts with $2a$, $2b$, $2x$, or $2y$ - if (/^\$2[abxy]\$/.test(cleanHash)) { - return 'bcrypt'; - } - return null; } @@ -66,14 +56,3 @@ export function detectHashType(hash: string): string | null { export function isHash(input: string): boolean { return detectHashType(input) !== null; } - -/** - * Verify a plaintext against a bcrypt hash - */ -export async function verifyBcrypt(plaintext: string, hash: string): Promise { - try { - return await bcrypt.compare(plaintext, hash); - } catch (_error) { - return false; - } -} diff --git a/package.json b/package.json index 6e5d52b..a8387ed 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,6 @@ }, "dependencies": { "@elastic/elasticsearch": "^9.2.0", - "@types/bcrypt": "^6.0.0", - "bcrypt": "^6.0.0", "lucide-react": "^0.555.0", "next": "15.4.8", "react": "19.1.2", diff --git a/public/manifest.json b/public/manifest.json index fb0548f..0d66f80 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "Hasher - Hash Search & Generator", "short_name": "Hasher", - "description": "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, SHA512, and Bcrypt.", + "description": "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, and SHA512.", "start_url": "/", "display": "standalone", "background_color": "#ffffff", diff --git a/scripts/index-file.ts b/scripts/index-file.ts index a2b2cde..f491162 100644 --- a/scripts/index-file.ts +++ b/scripts/index-file.ts @@ -35,7 +35,6 @@ interface HashDocument { sha1: string; sha256: string; sha512: string; - bcrypt: string; created_at: string; } @@ -157,17 +156,13 @@ function deleteState(stateFile: string): void { } } -async function generateHashes(plaintext: string): Promise { - const bcrypt = await import('bcrypt'); - const bcryptHash = await bcrypt.default.hash(plaintext, 10); - +function generateHashes(plaintext: string): HashDocument { return { plaintext, md5: crypto.createHash('md5').update(plaintext).digest('hex'), sha1: crypto.createHash('sha1').update(plaintext).digest('hex'), sha256: crypto.createHash('sha256').update(plaintext).digest('hex'), sha512: crypto.createHash('sha512').update(plaintext).digest('hex'), - bcrypt: bcryptHash, created_at: new Date().toISOString() }; } @@ -313,12 +308,10 @@ async function indexFile(filePath: string, batchSize: number, shouldResume: bool const bulkOperations: any[] = []; // Generate hashes for all items in batch first - const batchWithHashes = await Promise.all( - batch.map(async (plaintext: string) => ({ - plaintext, - hashes: await generateHashes(plaintext) - })) - ); + const batchWithHashes = batch.map((plaintext: string) => ({ + plaintext, + hashes: generateHashes(plaintext) + })); if (checkDuplicates) { // Check which items already exist (by plaintext or any hash) From 2de78b7461badf4aa1f7238424e678b9f51964d6 Mon Sep 17 00:00:00 2001 From: ale Date: Mon, 8 Dec 2025 23:08:24 +0100 Subject: [PATCH 2/5] share link Signed-off-by: ale --- app/page.tsx | 120 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index c8c7704..622afad 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,8 @@ 'use client'; -import { useState, useEffect } from 'react'; -import { Search, Copy, Check, Hash, Key, AlertCircle, Loader2, Database } from 'lucide-react'; +import { useState, useEffect, useCallback } from 'react'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { Search, Copy, Check, Hash, Key, AlertCircle, Loader2, Database, Link } from 'lucide-react'; interface SearchResult { found: boolean; @@ -46,12 +47,56 @@ function formatNumber(num: number): string { } export default function Home() { + const searchParams = useSearchParams(); + const router = useRouter(); const [query, setQuery] = useState(''); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [copiedField, setCopiedField] = useState(null); const [stats, setStats] = useState(null); + const [copiedLink, setCopiedLink] = useState(false); + + const performSearch = useCallback(async (searchQuery: string) => { + if (!searchQuery.trim()) return; + + setLoading(true); + setError(''); + setResult(null); + + try { + const response = await fetch('/api/search', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: searchQuery.trim() }) + }); + + if (!response.ok) { + throw new Error('Search failed'); + } + + const data = await response.json(); + setResult(data); + + // Update URL with search query + const newUrl = new URL(window.location.href); + newUrl.searchParams.set('q', searchQuery.trim()); + router.replace(newUrl.pathname + newUrl.search, { scroll: false }); + } catch (_err) { + setError('Failed to perform search. Please check your connection.'); + } finally { + setLoading(false); + } + }, [router]); + + // Load query from URL on mount + useEffect(() => { + const urlQuery = searchParams.get('q'); + if (urlQuery) { + setQuery(urlQuery); + performSearch(urlQuery); + } + }, [searchParams, performSearch]); useEffect(() => { const fetchStats = async () => { @@ -73,30 +118,7 @@ export default function Home() { const handleSearch = async (e: React.FormEvent) => { e.preventDefault(); - if (!query.trim()) return; - - setLoading(true); - setError(''); - setResult(null); - - try { - const response = await fetch('/api/search', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query: query.trim() }) - }); - - if (!response.ok) { - throw new Error('Search failed'); - } - - const data = await response.json(); - setResult(data); - } catch (_err) { - setError('Failed to perform search. Please check your connection.'); - } finally { - setLoading(false); - } + performSearch(query); }; const copyToClipboard = (text: string, field: string) => { @@ -105,6 +127,14 @@ export default function Home() { setTimeout(() => setCopiedField(null), 2000); }; + const copyShareLink = () => { + const url = new URL(window.location.href); + url.searchParams.set('q', query.trim()); + navigator.clipboard.writeText(url.toString()); + setCopiedLink(true); + setTimeout(() => setCopiedLink(false), 2000); + }; + const HashDisplay = ({ label, value, field }: { label: string; value: string; field: string }) => (
@@ -166,19 +196,35 @@ export default function Home() { value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Enter a hash or plaintext..." - className="w-full px-6 py-4 pr-14 text-lg rounded-2xl border-2 border-gray-200 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 outline-none transition-all shadow-sm" + className="w-full px-6 py-4 pr-28 text-lg rounded-2xl border-2 border-gray-200 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 outline-none transition-all shadow-sm" /> - )} - + +
From 42bc5a15d08a969e0d5fe40669d891ae3b8d3c49 Mon Sep 17 00:00:00 2001 From: ale Date: Mon, 8 Dec 2025 23:08:38 +0100 Subject: [PATCH 3/5] sanitize nosql Signed-off-by: ale --- app/api/search/route.ts | 101 +++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/app/api/search/route.ts b/app/api/search/route.ts index e53d805..18a986c 100644 --- a/app/api/search/route.ts +++ b/app/api/search/route.ts @@ -11,13 +11,101 @@ interface HashDocument { created_at?: string; } +// Maximum allowed query length +const MAX_QUERY_LENGTH = 1000; + +// Characters that could be used in NoSQL/Elasticsearch injection attacks +const DANGEROUS_PATTERNS = [ + /[{}\[\]]/g, // JSON structure characters + /\$[a-zA-Z]/g, // MongoDB-style operators + /\\u[0-9a-fA-F]{4}/g, // Unicode escapes + /