1
API.md
1
API.md
@@ -179,7 +179,6 @@ The API automatically detects hash types based on length and format:
|
|||||||
| SHA1 | 40 | `^[a-f0-9]{40}$` |
|
| SHA1 | 40 | `^[a-f0-9]{40}$` |
|
||||||
| SHA256 | 64 | `^[a-f0-9]{64}$` |
|
| SHA256 | 64 | `^[a-f0-9]{64}$` |
|
||||||
| SHA512 | 128 | `^[a-f0-9]{128}$` |
|
| SHA512 | 128 | `^[a-f0-9]{128}$` |
|
||||||
| Bcrypt | 60 | `^\$2[abxy]\$` |
|
|
||||||
|
|
||||||
Hashes are case-insensitive.
|
Hashes are case-insensitive.
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
#### Core Features
|
#### 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
|
- Hash generation from plaintext input
|
||||||
- Automatic detection of hash types based on length and pattern
|
- Automatic detection of hash types based on length and pattern
|
||||||
- Real-time hash generation with instant results
|
- Real-time hash generation with instant results
|
||||||
- Copy to clipboard functionality for all hash values
|
- Copy to clipboard functionality for all hash values
|
||||||
- Bcrypt verification support
|
|
||||||
|
|
||||||
#### Backend
|
#### Backend
|
||||||
- Elasticsearch integration with configurable endpoint
|
- Elasticsearch integration with configurable endpoint
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
## ✨ Key Features
|
## ✨ Key Features
|
||||||
|
|
||||||
### 🔍 Hash Search
|
### 🔍 Hash Search
|
||||||
- Search for MD5, SHA1, SHA256, SHA512, and Bcrypt hashes
|
- Search for MD5, SHA1, SHA256, and SHA512 hashes
|
||||||
- Automatic hash type detection
|
- Automatic hash type detection
|
||||||
- Case-insensitive matching
|
- Case-insensitive matching
|
||||||
- Real-time results
|
- Real-time results
|
||||||
@@ -174,7 +174,6 @@ export ELASTICSEARCH_NODE=http://localhost:9200
|
|||||||
| SHA1 | 40 | `^[a-f0-9]{40}$` |
|
| SHA1 | 40 | `^[a-f0-9]{40}$` |
|
||||||
| SHA256 | 64 | `^[a-f0-9]{64}$` |
|
| SHA256 | 64 | `^[a-f0-9]{64}$` |
|
||||||
| SHA512 | 128 | `^[a-f0-9]{128}$` |
|
| SHA512 | 128 | `^[a-f0-9]{128}$` |
|
||||||
| Bcrypt | 60 | `^\$2[abxy]\$` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -245,7 +244,6 @@ export ELASTICSEARCH_NODE=http://localhost:9200
|
|||||||
## 📈 Future Enhancements
|
## 📈 Future Enhancements
|
||||||
|
|
||||||
### Planned Features
|
### Planned Features
|
||||||
- Bcrypt hash validation
|
|
||||||
- Argon2 hash support
|
- Argon2 hash support
|
||||||
- Search history
|
- Search history
|
||||||
- Batch lookup
|
- Batch lookup
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ npm run index-file -- --help # Show help
|
|||||||
| SHA1 | 40 | `5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8` |
|
| SHA1 | 40 | `5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8` |
|
||||||
| SHA256 | 64 | `5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8` |
|
| SHA256 | 64 | `5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8` |
|
||||||
| SHA512 | 128 | `b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb9...` |
|
| SHA512 | 128 | `b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb9...` |
|
||||||
| Bcrypt | 60 | `$2b$10$N9qo8uLOickgx2ZMRZoMye...` |
|
|
||||||
|
|
||||||
## 🔌 API Quick Reference
|
## 🔌 API Quick Reference
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ A modern, high-performance hash search and generation tool powered by Elasticsea
|
|||||||
|
|
||||||
## ✨ Features
|
## ✨ 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
|
- 🔑 **Hash Generation**: Generate multiple hash types from plaintext
|
||||||
- 💾 **Auto-Indexing**: Automatically stores searched plaintext and hashes
|
- 💾 **Auto-Indexing**: Automatically stores searched plaintext and hashes
|
||||||
- 📊 **Elasticsearch Backend**: Scalable storage with 10 shards for performance
|
- 📊 **Elasticsearch Backend**: Scalable storage with 10 shards for performance
|
||||||
@@ -274,7 +274,6 @@ npm run lint
|
|||||||
| SHA1 | 40 | `^[a-f0-9]{40}$` |
|
| SHA1 | 40 | `^[a-f0-9]{40}$` |
|
||||||
| SHA256 | 64 | `^[a-f0-9]{64}$` |
|
| SHA256 | 64 | `^[a-f0-9]{64}$` |
|
||||||
| SHA512 | 128 | `^[a-f0-9]{128}$` |
|
| SHA512 | 128 | `^[a-f0-9]{128}$` |
|
||||||
| Bcrypt | 60 | `^\$2[abxy]\$` |
|
|
||||||
|
|
||||||
## 🚀 Performance
|
## 🚀 Performance
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ interface HashDocument {
|
|||||||
sha1: string;
|
sha1: string;
|
||||||
sha256: string;
|
sha256: string;
|
||||||
sha512: string;
|
sha512: string;
|
||||||
bcrypt: string;
|
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ export async function POST(request: NextRequest) {
|
|||||||
index: INDEX_NAME,
|
index: INDEX_NAME,
|
||||||
query: {
|
query: {
|
||||||
term: {
|
term: {
|
||||||
[hashType]: hashType === 'bcrypt' ? cleanQuery : cleanQueryLower
|
[hashType]: cleanQueryLower
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -66,7 +65,6 @@ export async function POST(request: NextRequest) {
|
|||||||
sha1: source.sha1,
|
sha1: source.sha1,
|
||||||
sha256: source.sha256,
|
sha256: source.sha256,
|
||||||
sha512: source.sha512,
|
sha512: source.sha512,
|
||||||
bcrypt: source.bcrypt,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -101,11 +99,10 @@ export async function POST(request: NextRequest) {
|
|||||||
sha1: existingDoc.sha1,
|
sha1: existingDoc.sha1,
|
||||||
sha256: existingDoc.sha256,
|
sha256: existingDoc.sha256,
|
||||||
sha512: existingDoc.sha512,
|
sha512: existingDoc.sha512,
|
||||||
bcrypt: existingDoc.bcrypt,
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Plaintext not found, generate hashes and check if any hash already exists
|
// Plaintext not found, generate hashes and check if any hash already exists
|
||||||
hashes = await generateHashes(cleanQuery);
|
hashes = generateHashes(cleanQuery);
|
||||||
|
|
||||||
const hashExistsResponse = await esClient.search<HashDocument>({
|
const hashExistsResponse = await esClient.search<HashDocument>({
|
||||||
index: INDEX_NAME,
|
index: INDEX_NAME,
|
||||||
@@ -147,7 +144,6 @@ export async function POST(request: NextRequest) {
|
|||||||
sha1: hashes.sha1,
|
sha1: hashes.sha1,
|
||||||
sha256: hashes.sha256,
|
sha256: hashes.sha256,
|
||||||
sha512: hashes.sha512,
|
sha512: hashes.sha512,
|
||||||
bcrypt: hashes.bcrypt,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ const geistMono = Geist_Mono({
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Hasher - Hash Search & Generator",
|
title: "Hasher - Hash Search & Generator",
|
||||||
description: "Search for hashes or generate them from plaintext. Supports MD5, SHA1, SHA256, SHA512, and Bcrypt. Powered by 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", "bcrypt", "hash generator", "hash search", "elasticsearch"],
|
keywords: ["hash", "md5", "sha1", "sha256", "sha512", "hash generator", "hash search", "elasticsearch"],
|
||||||
authors: [{ name: "Hasher" }],
|
authors: [{ name: "Hasher" }],
|
||||||
creator: "Hasher",
|
creator: "Hasher",
|
||||||
publisher: "Hasher",
|
publisher: "Hasher",
|
||||||
@@ -28,7 +28,7 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
type: "website",
|
type: "website",
|
||||||
title: "Hasher - Hash Search & Generator",
|
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",
|
siteName: "Hasher",
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
@@ -42,7 +42,7 @@ export const metadata: Metadata = {
|
|||||||
twitter: {
|
twitter: {
|
||||||
card: "summary",
|
card: "summary",
|
||||||
title: "Hasher - Hash Search & Generator",
|
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"],
|
images: ["/logo.png"],
|
||||||
},
|
},
|
||||||
viewport: {
|
viewport: {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ interface SearchResult {
|
|||||||
sha1: string;
|
sha1: string;
|
||||||
sha256: string;
|
sha256: string;
|
||||||
sha512: string;
|
sha512: string;
|
||||||
bcrypt: string;
|
|
||||||
};
|
};
|
||||||
results?: Array<{
|
results?: Array<{
|
||||||
plaintext: string;
|
plaintext: string;
|
||||||
@@ -24,7 +23,6 @@ interface SearchResult {
|
|||||||
sha1: string;
|
sha1: string;
|
||||||
sha256: string;
|
sha256: string;
|
||||||
sha512: string;
|
sha512: string;
|
||||||
bcrypt: string;
|
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
message?: string;
|
message?: string;
|
||||||
@@ -144,7 +142,7 @@ export default function Home() {
|
|||||||
Search for hashes or generate them from plaintext
|
Search for hashes or generate them from plaintext
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500 mt-2">
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
Supports MD5, SHA1, SHA256, SHA512, and Bcrypt
|
Supports MD5, SHA1, SHA256, and SHA512
|
||||||
</p>
|
</p>
|
||||||
{stats && (
|
{stats && (
|
||||||
<div className="flex items-center justify-center gap-4 mt-4 text-sm text-gray-500">
|
<div className="flex items-center justify-center gap-4 mt-4 text-sm text-gray-500">
|
||||||
@@ -214,7 +212,6 @@ export default function Home() {
|
|||||||
<HashDisplay label="SHA1" value={result.hashes!.sha1} field="sha1-gen" />
|
<HashDisplay label="SHA1" value={result.hashes!.sha1} field="sha1-gen" />
|
||||||
<HashDisplay label="SHA256" value={result.hashes!.sha256} field="sha256-gen" />
|
<HashDisplay label="SHA256" value={result.hashes!.sha256} field="sha256-gen" />
|
||||||
<HashDisplay label="SHA512" value={result.hashes!.sha512} field="sha512-gen" />
|
<HashDisplay label="SHA512" value={result.hashes!.sha512} field="sha512-gen" />
|
||||||
<HashDisplay label="Bcrypt" value={result.hashes!.bcrypt} field="bcrypt-gen" />
|
|
||||||
</div>
|
</div>
|
||||||
{result.wasGenerated && (
|
{result.wasGenerated && (
|
||||||
<div className="mt-6 bg-blue-50 border border-blue-200 rounded-xl p-4">
|
<div className="mt-6 bg-blue-50 border border-blue-200 rounded-xl p-4">
|
||||||
@@ -260,7 +257,6 @@ export default function Home() {
|
|||||||
<HashDisplay label="SHA1" value={item.hashes.sha1} field={`sha1-${idx}`} />
|
<HashDisplay label="SHA1" value={item.hashes.sha1} field={`sha1-${idx}`} />
|
||||||
<HashDisplay label="SHA256" value={item.hashes.sha256} field={`sha256-${idx}`} />
|
<HashDisplay label="SHA256" value={item.hashes.sha256} field={`sha256-${idx}`} />
|
||||||
<HashDisplay label="SHA512" value={item.hashes.sha512} field={`sha512-${idx}`} />
|
<HashDisplay label="SHA512" value={item.hashes.sha512} field={`sha512-${idx}`} />
|
||||||
<HashDisplay label="Bcrypt" value={item.hashes.bcrypt} field={`bcrypt-${idx}`} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -304,7 +300,7 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">Generate Hashes</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">Generate Hashes</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ export const INDEX_MAPPING = {
|
|||||||
sha512: {
|
sha512: {
|
||||||
type: 'keyword' as const
|
type: 'keyword' as const
|
||||||
},
|
},
|
||||||
bcrypt: {
|
|
||||||
type: 'keyword' as const
|
|
||||||
},
|
|
||||||
created_at: {
|
created_at: {
|
||||||
type: 'date' as const
|
type: 'date' as const
|
||||||
}
|
}
|
||||||
|
|||||||
23
lib/hash.ts
23
lib/hash.ts
@@ -1,5 +1,4 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
|
|
||||||
export interface HashResult {
|
export interface HashResult {
|
||||||
plaintext: string;
|
plaintext: string;
|
||||||
@@ -7,22 +6,18 @@ export interface HashResult {
|
|||||||
sha1: string;
|
sha1: string;
|
||||||
sha256: string;
|
sha256: string;
|
||||||
sha512: string;
|
sha512: string;
|
||||||
bcrypt: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate all common hashes for a given plaintext
|
* Generate all common hashes for a given plaintext
|
||||||
*/
|
*/
|
||||||
export async function generateHashes(plaintext: string): Promise<HashResult> {
|
export function generateHashes(plaintext: string): HashResult {
|
||||||
const bcryptHash = await bcrypt.hash(plaintext, 10);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plaintext,
|
plaintext,
|
||||||
md5: crypto.createHash('md5').update(plaintext).digest('hex'),
|
md5: crypto.createHash('md5').update(plaintext).digest('hex'),
|
||||||
sha1: crypto.createHash('sha1').update(plaintext).digest('hex'),
|
sha1: crypto.createHash('sha1').update(plaintext).digest('hex'),
|
||||||
sha256: crypto.createHash('sha256').update(plaintext).digest('hex'),
|
sha256: crypto.createHash('sha256').update(plaintext).digest('hex'),
|
||||||
sha512: crypto.createHash('sha512').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';
|
return 'sha512';
|
||||||
}
|
}
|
||||||
|
|
||||||
// BCrypt: starts with $2a$, $2b$, $2x$, or $2y$
|
|
||||||
if (/^\$2[abxy]\$/.test(cleanHash)) {
|
|
||||||
return 'bcrypt';
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +56,3 @@ export function detectHashType(hash: string): string | null {
|
|||||||
export function isHash(input: string): boolean {
|
export function isHash(input: string): boolean {
|
||||||
return detectHashType(input) !== null;
|
return detectHashType(input) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify a plaintext against a bcrypt hash
|
|
||||||
*/
|
|
||||||
export async function verifyBcrypt(plaintext: string, hash: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
return await bcrypt.compare(plaintext, hash);
|
|
||||||
} catch (_error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,8 +39,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "^9.2.0",
|
"@elastic/elasticsearch": "^9.2.0",
|
||||||
"@types/bcrypt": "^6.0.0",
|
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^0.555.0",
|
||||||
"next": "15.4.8",
|
"next": "15.4.8",
|
||||||
"react": "19.1.2",
|
"react": "19.1.2",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Hasher - Hash Search & Generator",
|
"name": "Hasher - Hash Search & Generator",
|
||||||
"short_name": "Hasher",
|
"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": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ interface HashDocument {
|
|||||||
sha1: string;
|
sha1: string;
|
||||||
sha256: string;
|
sha256: string;
|
||||||
sha512: string;
|
sha512: string;
|
||||||
bcrypt: string;
|
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,17 +156,13 @@ function deleteState(stateFile: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateHashes(plaintext: string): Promise<HashDocument> {
|
function generateHashes(plaintext: string): HashDocument {
|
||||||
const bcrypt = await import('bcrypt');
|
|
||||||
const bcryptHash = await bcrypt.default.hash(plaintext, 10);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plaintext,
|
plaintext,
|
||||||
md5: crypto.createHash('md5').update(plaintext).digest('hex'),
|
md5: crypto.createHash('md5').update(plaintext).digest('hex'),
|
||||||
sha1: crypto.createHash('sha1').update(plaintext).digest('hex'),
|
sha1: crypto.createHash('sha1').update(plaintext).digest('hex'),
|
||||||
sha256: crypto.createHash('sha256').update(plaintext).digest('hex'),
|
sha256: crypto.createHash('sha256').update(plaintext).digest('hex'),
|
||||||
sha512: crypto.createHash('sha512').update(plaintext).digest('hex'),
|
sha512: crypto.createHash('sha512').update(plaintext).digest('hex'),
|
||||||
bcrypt: bcryptHash,
|
|
||||||
created_at: new Date().toISOString()
|
created_at: new Date().toISOString()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -313,12 +308,10 @@ async function indexFile(filePath: string, batchSize: number, shouldResume: bool
|
|||||||
const bulkOperations: any[] = [];
|
const bulkOperations: any[] = [];
|
||||||
|
|
||||||
// Generate hashes for all items in batch first
|
// Generate hashes for all items in batch first
|
||||||
const batchWithHashes = await Promise.all(
|
const batchWithHashes = batch.map((plaintext: string) => ({
|
||||||
batch.map(async (plaintext: string) => ({
|
plaintext,
|
||||||
plaintext,
|
hashes: generateHashes(plaintext)
|
||||||
hashes: await generateHashes(plaintext)
|
}));
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (checkDuplicates) {
|
if (checkDuplicates) {
|
||||||
// Check which items already exist (by plaintext or any hash)
|
// Check which items already exist (by plaintext or any hash)
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user