A modern, secure web application for generating PGP and SSH key pairs with automatic cleanup and rate limiting.
A hardening patch on top of v1.1.0:
- Private temp storage. The key directory (
app.temp-dir) and every per-request token directory are now0700, so other local users on the host can't list request tokens. - Per-client rate limiting behind a proxy. With
app.trusted-proxy=true, the client is identified byX-Real-IP, which the proxy overwrites. If that header is missing, only the lastX-Forwarded-Forhop (the one our proxy appended) is used. The first hop is client-controlled and is no longer trusted, so rotating a fakeX-Forwarded-Fordoesn't get around the limit. Production (nginx behind Cloudflare, withreal_ip_header CF-Connecting-IP) now runs with--app.trusted-proxy=true. Before this, every visitor counted as127.0.0.1and shared one bucket. - Tests no longer write into the production temp directory. They use
target/test-keys.
KeyJolt can now generate just an SSH key pair, with no PGP key, name or email needed.
Pick SSH only in the toggle at the top of the form (or open keyjolt.dev/#ssh):
| Field | Options |
|---|---|
| Key type | Ed25519 (default, recommended) or RSA 4096 for legacy systems |
| Comment | Optional, e.g. you@laptop, appended to the public key |
| Passphrase | Optional (8+ characters), encrypts the private key |
You get id_ed25519 + id_ed25519.pub (or id_rsa + id_rsa.pub), the key's
SHA256: fingerprint, and a one-click Copy button for the public key line.
# after downloading
chmod 600 ~/Downloads/id_ed25519
mv ~/Downloads/id_ed25519 ~/Downloads/id_ed25519.pub ~/.ssh/
ssh-keygen -lf ~/.ssh/id_ed25519.pub # fingerprint matches the one KeyJolt showedUnder the hood:
- Keys are written in the modern OpenSSH format (
-----BEGIN OPENSSH PRIVATE KEY-----) by Apache MINA sshd, replacing JSch, which cannot write Ed25519 private keys. - A passphrase uses bcrypt KDF + AES-256-CTR, the same as
ssh-keygen's default. - Every key type, with and without a passphrase, is cross-checked against
ssh-keygenin the test suite. - The combined PGP + SSH mode is unchanged, but its SSH key now also uses the OpenSSH format, and its comment is your email address.
New endpoint for scripts:
curl -s -X POST https://keyjolt.dev/api/generate/ssh \
-H 'Content-Type: application/json' \
-d '{"keyType":"ed25519","comment":"you@laptop"}'
# β { "success": true, "fingerprint": "SHA256:β¦", "publicKey": "ssh-ed25519 AAAAβ¦ you@laptop",
# "files": [ { "filename": "id_ed25519.pub", "downloadUrl": "/download/<token>/id_ed25519.pub", β¦ },
# { "filename": "id_ed25519", "downloadUrl": "/download/<token>/id_ed25519", β¦ } ] }In v1.0.x a download link was built only from the key's metadata
(/download/email_name_seckey_KEYID.asc). Anyone who could guess or observe those
values could fetch someone else's private key during its 5-minute window.
From v1.1.0:
- Unguessable links. Every generation request gets its own directory named by a random
128-bit token from
SecureRandom, and links look like/download/<32-hex-token>/<filename>. Knowing a name, email or key ID is no longer enough to download anything. - No clashes. Two people generating
id_ed25519at the same moment get separate directories. - Strict validation. The token must be exactly 32 lowercase hex characters, and the filename must
match a short allow-list (
A-Z a-z 0-9 @ . _ + -). The resolved path must stay inside that request's directory, so anything else is rejected with400before the filesystem is touched. Cache-Control: no-storeon every download, so browsers and proxies don't keep copies of keys.- Private key files are created
0600on the server, then overwritten with random data and deleted after 5 minutes as before. The empty request directory is removed with them. - The old
/download/{filename}route is gone.
- RSA Encryption: Support for 2048, 3072, and 4096-bit key strengths
- Automatic Cleanup: Generated keys are securely deleted after 5 minutes
- Unguessable Download Links: Per-request random 128-bit token in every download URL
- Rate Limiting: 10 requests per hour with burst capacity protection
- Security Headers: Comprehensive HTTP security headers (HSTS, CSP, X-Frame-Options)
- Secure File Handling: Private keys are overwritten with random data before deletion
- Responsive Design: Mobile-first approach with clean, minimal interface
- Twitter Blue Theme: Professional color scheme with accessibility features
- Real-time Validation: Live form validation with clear error messages
- Progress Indicators: Loading states and generation progress feedback
- Tooltips: Helpful explanations for technical concepts
- PGP Key Pairs: RSA-based PGP keys with configurable expiration
- SSH Key Pairs: Alongside PGP, or on their own (SSH-only mode: Ed25519 or RSA 4096, optional passphrase)
- Custom Metadata: User name and email integration
- Flexible Expiry: Keys can expire from 1 day to 10 years (or never)
- Spring Security: Comprehensive security configuration
- Input Validation: Server-side and client-side validation
- Error Handling: Graceful error management and user feedback
- Logging: Detailed security and operation logging
- Framework: Spring Boot 3.2.0
- Language: Java 17
- Security: Spring Security with custom headers
- Cryptography: Bouncy Castle (PGP), Apache MINA sshd-common (SSH)
- Rate Limiting: Bucket4j
- Build Tool: Maven
- Template Engine: Thymeleaf
- Styling: Custom CSS with CSS Grid and Flexbox
- JavaScript: Vanilla JS with modern ES6+ features
- Icons: Feather Icons
- Responsive: Mobile-first design patterns
- HTTPS Ready: Security headers for production deployment
- CSP: Content Security Policy for XSS protection
- Rate Limiting: Token bucket algorithm
- Memory Management: Automatic resource cleanup
- Java 17 or higher
- Maven 3.6+
-
Clone the repository
git clone https://github.com/hexawulf/KeyJolt.git cd KeyJolt -
Build the application
mvn clean install
-
Run the server
mvn spring-boot:run
-
Access the application
http://localhost:5005
The application can be configured via application.properties:
# Server Configuration (uses PORT env var if set, else 5005)
server.port=${PORT:5005}
server.address=0.0.0.0
# Rate Limiting
app.rate-limit.requests-per-hour=10
app.rate-limit.burst-capacity=5
# Behind a reverse proxy that sets X-Real-IP (nginx): true = limit per client.
# Leave false when exposed directly, or clients could spoof their IP.
app.trusted-proxy=false
# File Cleanup (milliseconds)
app.key-cleanup-delay=300000
# Encryption Defaults
app.encryption.default-strength=4096
app.encryption.max-expiry-days=3650Copy src/main/resources/application-example.properties to
src/main/resources/application.properties and adjust values for
your environment. Do not commit secrets to version control.
- Keep the toggle on "PGP + SSH" and fill out the form with your name and email address
- Select encryption strength (2048, 3072, or 4096 bits)
- Set key expiry (0 for never expires, or 1-3650 days)
- Optionally enable SSH key generation
- Click "Generate Keys" and wait for processing
- Download your keys immediately (they're deleted after 5 minutes)
- Switch the toggle to "SSH only" (or open
/#ssh) - Choose a key type: Ed25519 (recommended) or RSA 4096
- Optionally add a comment and a passphrase
- Click "Generate SSH Key", then copy the public key or download both files
Generated files follow this pattern:
- PGP Public:
email_name_pubkey_KEYID.asc - PGP Private:
email_name_seckey_KEYID.asc - SSH Public:
email_name_ssh_pub.key - SSH Private:
email_name_ssh_priv.key - SSH-only mode:
id_ed25519/id_ed25519.puborid_rsa/id_rsa.pub
All files are served from /download/<request-token>/<filename>.
keyjolt/
βββ src/main/java/com/keyjolt/
β βββ KeyjoltApplication.java # Spring Boot main class
β βββ config/
β β βββ SecurityConfig.java # Security configuration
β βββ controller/
β β βββ KeyController.java # REST endpoints
β βββ service/
β β βββ PgpKeyService.java # PGP key generation
β β βββ SshKeyService.java # SSH key generation
β βββ model/
β β βββ KeyRequest.java # Request DTO (PGP + SSH)
β β βββ SshKeyRequest.java # Request DTO (SSH only)
β β βββ KeyResponse.java # Response DTO
β βββ util/
β βββ FileUtils.java # Secure file operations
βββ src/main/resources/
β βββ templates/
β β βββ index.html # Main UI template
β βββ static/
β β βββ css/style.css # Styling
β β βββ js/script.js # Frontend logic
β βββ application.properties # Configuration
βββ pom.xml # Maven dependencies
- Stateless Sessions: No server-side session storage
- CSRF Protection: Disabled for API endpoints (stateless design)
- Rate Limiting: Per-IP token bucket with hourly limits
- File Security: Per-request token directories, 0600 private keys, automatic cleanup with secure deletion
- Input Validation: Both client and server-side validation
- Use HTTPS in production environments
- Configure proper firewall rules
- Set up monitoring and alerting
- Regular security updates
- Restrict access to
/actuatorendpoints or disable them - Set
SPRING_PROFILES_ACTIVE=productionfor production builds
- Private keys are generated server-side and immediately deleted
- No keys are logged or permanently stored
- Users must download keys immediately
- Secure random number generation used throughout
- Default: 10 requests per hour per IP
- Burst capacity: 5 requests per minute
- Configurable via application properties
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
0xWulf
- Email: dev@0xwulf.dev
- GitHub: @hexawulf
- Bouncy Castle for robust cryptographic libraries
- Spring Boot for the excellent framework
- Feather Icons for beautiful, lightweight icons
- Apache MINA sshd for OpenSSH key generation
Made with β€οΈ for secure key generation
KeyJolt - Secure Keys, Instantly