├── .github
└── FUNDING.yml
├── screenshots
├── pin-tab.png
├── password-tab.png
└── passphrase-tab.png
├── LICENSE
├── README.md
├── index.html
├── styles.css
└── script.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [ramazancetinkaya]
2 |
--------------------------------------------------------------------------------
/screenshots/pin-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ramazancetinkaya/password-generator/HEAD/screenshots/pin-tab.png
--------------------------------------------------------------------------------
/screenshots/password-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ramazancetinkaya/password-generator/HEAD/screenshots/password-tab.png
--------------------------------------------------------------------------------
/screenshots/passphrase-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ramazancetinkaya/password-generator/HEAD/screenshots/passphrase-tab.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Ramazan Çetinkaya
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Password Generator
2 |
3 | [](LICENSE)
4 | [](https://github.com/ramazancetinkaya/password-generator/issues)
5 | [](https://github.com/ramazancetinkaya/password-generator/stargazers)
6 | [](https://github.com/ramazancetinkaya/password-generator/network)
7 |
8 | A modern, secure, and feature-rich password generator built with HTML, CSS, and vanilla JavaScript. This application helps users create strong passwords, PINs, and passphrases with various security options.
9 |
10 | ## Features
11 |
12 | ### Password Generation
13 | - **Multiple Algorithms**:
14 | - Standard (12 characters)
15 | - Balanced (16 characters)
16 | - Secure (24 characters with cryptographic strength)
17 | - Custom settings
18 | - **Customization Options**:
19 | - Length control (8-64 characters)
20 | - Character types (uppercase, lowercase, numbers, symbols)
21 | - Exclude similar characters (i, l, 1, o, 0)
22 | - Exclude ambiguous symbols ({}, [], (), /)
23 | - **Security Metrics**:
24 | - Entropy calculation
25 | - Estimated crack time
26 |
27 | ### PIN Generation
28 | - Support for both 4-digit and 6-digit PINs
29 | - Two security algorithms:
30 | - Random generation
31 | - Secure (avoids common patterns and sequential numbers)
32 | - Security metrics:
33 | - Attack resistance information
34 | - Brute force combinations
35 |
36 | ### Passphrase Generation
37 | - Word count selection (3-12 words)
38 | - Multiple word lists:
39 | - Common Words
40 | - EFF Wordlist
41 | - Diceware
42 | - BIP39 Words
43 | - Customization options:
44 | - 10 different separators
45 | - Capitalize first letter
46 | - Add random number
47 | - Add random symbol
48 | - Security metrics:
49 | - Entropy calculation
50 | - Uniqueness probability
51 |
52 | ### User Experience
53 | - Responsive design for all devices (desktop, tablet, mobile)
54 | - Clean, modern user interface
55 | - Copy to clipboard functionality
56 | - Real-time security strength indicators
57 | - Toast notifications for user feedback
58 |
59 | ## Screenshots
60 |
61 |
62 |
63 |
Password Generation Tab
64 |
65 |
66 |
67 |
68 |
PIN Generation Tab
69 |
70 |
71 |
72 |
73 |
Passphrase Generation Tab
74 |
75 |
76 | ## Demo
77 |
78 | Experience the application instantly via GitHub Pages:
79 |
80 | [](https://ramazancetinkaya.github.io/password-generator/)
81 |
82 | ## Technology Stack
83 |
84 | - HTML5
85 | - CSS3
86 | - Vanilla JavaScript
87 | - Font Awesome for icons
88 | - No external libraries or frameworks
89 |
90 | ## Security Information
91 |
92 | This application generates passwords using cryptographically secure methods:
93 | - Secure random algorithm uses `window.crypto.getRandomValues()`
94 | - Entropy calculations based on character set and length
95 | - Crack time estimates considering modern computing capabilities (10 trillion guesses/second)
96 |
97 | The application runs entirely in the browser with no server communication. All processing happens locally, ensuring your passwords never leave your device.
98 |
99 | ## Installation
100 |
101 | ### Option 1: Clone the Repository
102 |
103 | ```bash
104 | git clone https://github.com/ramazancetinkaya/password-generator.git
105 | cd password-generator
106 | ```
107 |
108 | ### Option 2: Download as ZIP
109 |
110 | 1. Go to the [repository page](https://github.com/ramazancetinkaya/password-generator)
111 | 2. Click the "Code" button
112 | 3. Select "Download ZIP"
113 | 4. Extract the ZIP file to your desired location
114 |
115 | ## Usage
116 |
117 | Simply open the `index.html` file in any modern web browser to start using the application. No server or additional dependencies are required.
118 |
119 | ### Password Generation
120 |
121 | 1. Select the "Password" tab
122 | 2. Choose an algorithm (Standard, Balanced, Secure, or Custom)
123 | 3. Adjust any custom settings if needed
124 | 4. Click "Generate Password"
125 | 5. Use the copy button to copy the password to your clipboard
126 |
127 | ### PIN Generation
128 |
129 | 1. Select the "PIN" tab
130 | 2. Choose between 4-digit or 6-digit PIN
131 | 3. Select an algorithm (Random or Secure)
132 | 4. Click "Generate PIN"
133 | 5. Copy the PIN using the copy button
134 |
135 | ### Passphrase Generation
136 |
137 | 1. Select the "Passphrase" tab
138 | 2. Choose the number of words (3-12)
139 | 3. Select a word list and separator
140 | 4. Enable/disable additional options (capitalization, numbers, symbols)
141 | 5. Click "Generate Passphrase"
142 | 6. Copy the passphrase using the copy button
143 |
144 | ## Browser Compatibility
145 |
146 | Tested and working on:
147 | - Chrome (latest)
148 | - Firefox (latest)
149 | - Safari (latest)
150 | - Edge (latest)
151 | - Opera (latest)
152 | - Mobile browsers
153 |
154 | ## Contributing
155 |
156 | Contributions are welcome! Please feel free to submit a pull request or open an issue for any enhancements or bug fixes.
157 |
158 | ## License
159 |
160 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
161 |
162 | ## Author
163 |
164 | Designed and developed by **Ramazan Çetinkaya**.
165 |
166 | ## Contact
167 |
168 | For any inquiries, please contact:
169 |
170 | - GitHub: [ramazancetinkaya](https://github.com/ramazancetinkaya)
171 | - Email: [ramazancetinkayasoftworks@protonmail.com](mailto:ramazancetinkayasoftworks@protonmail.com)
172 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Password Generator
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Password
16 |
17 |
18 | PIN
19 |
20 |
21 | Passphrase
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Algorithm
30 |
31 |
32 |
Standard
33 |
Balanced
34 |
Secure
35 |
Custom
36 |
37 |
38 |
39 |
Length: 12
40 |
41 |
42 |
43 |
Include Uppercase Letters
44 |
45 |
46 |
47 |
48 |
49 |
50 |
Include Lowercase Letters
51 |
52 |
53 |
54 |
55 |
56 |
57 |
Include Numbers
58 |
59 |
60 |
61 |
62 |
63 |
64 |
Include Symbols
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Exclude Similar Characters (i, l, 1, o, 0, etc.)
72 |
73 |
74 |
75 |
76 |
77 |
78 |
Exclude Ambiguous Symbols ({}, [], (), /, etc.)
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | Generate Password
96 |
97 |
98 |
99 |
100 | Password Strength
101 | -
102 |
103 |
106 |
107 |
108 |
Entropy
109 |
-
110 |
111 |
112 |
Crack Time
113 |
-
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | PIN Type
124 |
125 |
126 |
127 | 4-Digit
128 |
129 |
130 | 6-Digit
131 |
132 |
133 |
134 |
135 |
136 |
137 | Algorithm
138 |
139 |
140 |
Random
141 |
Secure
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | Generate PIN
154 |
155 |
156 |
157 |
158 | PIN Security
159 | -
160 |
161 |
164 |
165 |
166 |
Attack Resistance
167 |
-
168 |
169 |
170 |
Brute Force
171 |
-
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | Options
182 |
183 |
184 |
185 |
186 |
Number of Words: 4
187 |
188 |
189 |
190 |
191 |
192 | Common Words
193 | EFF Wordlist
194 | Diceware
195 | BIP39 Words
196 |
197 |
198 |
199 |
200 | Space
201 | Hyphen (-)
202 | Period (.)
203 | Comma (,)
204 | Underscore (_)
205 | At Sign (@)
206 | Ampersand (&)
207 | Asterisk (*)
208 | Plus (+)
209 | Equals (=)
210 |
211 |
212 |
213 |
Capitalize First Letter
214 |
215 |
216 |
217 |
218 |
219 |
220 |
Add Random Number
221 |
222 |
223 |
224 |
225 |
226 |
227 |
Add Random Symbol
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 | Generate Passphrase
245 |
246 |
247 |
248 |
249 | Passphrase Security
250 | -
251 |
252 |
255 |
256 |
257 |
Entropy
258 |
-
259 |
260 |
261 |
Uniqueness
262 |
-
263 |
264 |
265 |
266 |
267 |
268 |
269 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-color: #4a6ee0;
3 | --secondary-color: #6c84eb;
4 | --accent-color: #8a9ded;
5 | --text-color: #333333;
6 | --light-bg: #f5f7ff;
7 | --light-gray: #e0e5ff;
8 | --medium-gray: #ccd4f8;
9 | --dark-gray: #9aa5d8;
10 | --danger-color: #e74c3c;
11 | --warning-color: #f39c12;
12 | --success-color: #2ecc71;
13 | --info-color: #3498db;
14 | --border-radius: 8px;
15 | --box-shadow: 0 8px 24px rgba(149, 157, 165, 0.2);
16 | --transition-speed: 0.3s;
17 | }
18 |
19 | * {
20 | margin: 0;
21 | padding: 0;
22 | box-sizing: border-box;
23 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
24 | }
25 |
26 | body {
27 | background-color: #f0f2fa;
28 | background-image: linear-gradient(135deg, #f5f7ff 0%, #e0e5ff 100%);
29 | color: var(--text-color);
30 | min-height: 100vh;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 | padding: 20px;
35 | margin: 0;
36 | }
37 |
38 | .container {
39 | background-color: white;
40 | border-radius: var(--border-radius);
41 | box-shadow: var(--box-shadow);
42 | width: 100%;
43 | max-width: 480px;
44 | overflow: hidden;
45 | animation: fadeIn 0.5s ease-in-out;
46 | margin: 0 auto;
47 | }
48 |
49 | @keyframes fadeIn {
50 | from {
51 | opacity: 0;
52 | transform: translateY(-10px);
53 | }
54 | to {
55 | opacity: 1;
56 | transform: translateY(0);
57 | }
58 | }
59 |
60 | .tabs {
61 | display: flex;
62 | border-bottom: 1px solid var(--light-gray);
63 | background-color: var(--light-bg);
64 | }
65 |
66 | .tab {
67 | flex: 1;
68 | padding: 14px 8px;
69 | text-align: center;
70 | cursor: pointer;
71 | transition: all var(--transition-speed) ease;
72 | font-weight: 600;
73 | color: var(--text-color);
74 | position: relative;
75 | overflow: hidden;
76 | font-size: 15px;
77 | }
78 |
79 | .tab:hover {
80 | background-color: var(--light-gray);
81 | }
82 |
83 | .tab.active {
84 | color: var(--primary-color);
85 | background-color: white;
86 | }
87 |
88 | .tab.active::after {
89 | content: "";
90 | position: absolute;
91 | bottom: 0;
92 | left: 0;
93 | width: 100%;
94 | height: 3px;
95 | background-color: var(--primary-color);
96 | }
97 |
98 | .tab-content {
99 | display: none;
100 | padding: 22px;
101 | }
102 |
103 | .tab-content.active {
104 | display: block;
105 | animation: fadeIn 0.3s ease-in-out;
106 | }
107 |
108 | .option-group {
109 | margin-bottom: 20px;
110 | }
111 |
112 | .option-title {
113 | font-weight: 600;
114 | margin-bottom: 12px;
115 | display: flex;
116 | align-items: center;
117 | }
118 |
119 | .option-title i {
120 | margin-right: 8px;
121 | color: var(--primary-color);
122 | }
123 |
124 | .algorithm-options {
125 | display: flex;
126 | flex-wrap: wrap;
127 | gap: 10px;
128 | margin-bottom: 16px;
129 | }
130 |
131 | .algorithm-option {
132 | flex: 1;
133 | min-width: 100px;
134 | background-color: var(--light-bg);
135 | border: 2px solid var(--light-gray);
136 | border-radius: var(--border-radius);
137 | padding: 10px;
138 | cursor: pointer;
139 | transition: all var(--transition-speed) ease;
140 | text-align: center;
141 | font-weight: 500;
142 | font-size: 14px;
143 | }
144 |
145 | .algorithm-option:hover {
146 | background-color: var(--light-gray);
147 | }
148 |
149 | .algorithm-option.active {
150 | background-color: var(--primary-color);
151 | border-color: var(--primary-color);
152 | color: white;
153 | }
154 |
155 | .algorithm-option:hover {
156 | background-color: var(--light-gray);
157 | }
158 |
159 | .algorithm-option.active {
160 | background-color: var(--primary-color);
161 | border-color: var(--primary-color);
162 | color: white;
163 | }
164 |
165 | .algorithm-option:hover {
166 | background-color: var(--light-gray);
167 | }
168 |
169 | .algorithm-option.active {
170 | background-color: var(--primary-color);
171 | border-color: var(--primary-color);
172 | color: white;
173 | }
174 |
175 | .custom-options {
176 | background-color: var(--light-bg);
177 | border-radius: var(--border-radius);
178 | padding: 16px;
179 | margin-top: 16px;
180 | display: none;
181 | }
182 |
183 | .custom-options.visible {
184 | display: block;
185 | animation: fadeIn 0.3s ease-in-out;
186 | }
187 |
188 | .option-row {
189 | display: flex;
190 | align-items: center;
191 | margin-bottom: 12px;
192 | }
193 |
194 | .option-row:last-child {
195 | margin-bottom: 0;
196 | }
197 |
198 | .option-label {
199 | flex: 1;
200 | font-size: 14px;
201 | }
202 |
203 | .checkbox-wrapper {
204 | position: relative;
205 | display: inline-block;
206 | width: 42px;
207 | height: 24px;
208 | }
209 |
210 | .toggle {
211 | opacity: 0;
212 | width: 0;
213 | height: 0;
214 | }
215 |
216 | .toggle-slider {
217 | position: absolute;
218 | cursor: pointer;
219 | top: 0;
220 | left: 0;
221 | right: 0;
222 | bottom: 0;
223 | background-color: var(--dark-gray);
224 | transition: 0.4s;
225 | border-radius: 34px;
226 | }
227 |
228 | .toggle-slider:before {
229 | position: absolute;
230 | content: "";
231 | height: 18px;
232 | width: 18px;
233 | left: 3px;
234 | bottom: 3px;
235 | background-color: white;
236 | transition: 0.4s;
237 | border-radius: 50%;
238 | }
239 |
240 | .toggle:checked + .toggle-slider {
241 | background-color: var(--primary-color);
242 | }
243 |
244 | .toggle:checked + .toggle-slider:before {
245 | transform: translateX(18px);
246 | }
247 |
248 | .length-slider {
249 | flex: 1;
250 | margin-right: 0;
251 | height: 8px;
252 | -webkit-appearance: none;
253 | appearance: none;
254 | background-color: var(--medium-gray);
255 | border-radius: 10px;
256 | outline: none;
257 | }
258 |
259 | .length-slider::-webkit-slider-thumb {
260 | -webkit-appearance: none;
261 | appearance: none;
262 | width: 20px;
263 | height: 20px;
264 | border-radius: 50%;
265 | background-color: var(--primary-color);
266 | cursor: pointer;
267 | transition: all 0.2s ease;
268 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
269 | }
270 |
271 | .length-slider::-webkit-slider-thumb:hover {
272 | transform: scale(1.1);
273 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
274 | }
275 |
276 | .length-slider::-moz-range-thumb {
277 | width: 20px;
278 | height: 20px;
279 | border-radius: 50%;
280 | background-color: var(--primary-color);
281 | cursor: pointer;
282 | transition: all 0.2s ease;
283 | border: none;
284 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
285 | }
286 |
287 | .length-slider::-moz-range-thumb:hover {
288 | transform: scale(1.1);
289 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
290 | }
291 |
292 | .length-value {
293 | min-width: 40px;
294 | text-align: right;
295 | font-weight: 600;
296 | color: var(--primary-color);
297 | margin-left: 15px;
298 | }
299 |
300 | .result-container {
301 | margin-top: 20px;
302 | position: relative;
303 | }
304 |
305 | .result-input {
306 | width: 100%;
307 | padding: 12px 50px 12px 14px;
308 | border: 2px solid var(--light-gray);
309 | border-radius: var(--border-radius);
310 | font-size: 15px;
311 | color: var(--text-color);
312 | background-color: var(--light-bg);
313 | transition: all var(--transition-speed) ease;
314 | overflow: hidden;
315 | white-space: nowrap;
316 | text-overflow: ellipsis;
317 | }
318 |
319 | .result-input:focus {
320 | outline: none;
321 | border-color: var(--primary-color);
322 | box-shadow: 0 0 0 3px rgba(74, 110, 224, 0.2);
323 | }
324 |
325 | .pin-result {
326 | font-size: 24px;
327 | letter-spacing: 4px;
328 | text-align: center;
329 | font-family: "Courier New", monospace;
330 | font-weight: bold;
331 | }
332 |
333 | .copy-btn {
334 | position: absolute;
335 | right: 8px;
336 | top: 50%;
337 | transform: translateY(-50%);
338 | background: transparent;
339 | border: none;
340 | color: var(--primary-color);
341 | font-size: 16px;
342 | cursor: pointer;
343 | width: 32px;
344 | height: 32px;
345 | display: flex;
346 | align-items: center;
347 | justify-content: center;
348 | border-radius: 50%;
349 | transition: all 0.2s ease;
350 | }
351 |
352 | .copy-btn:hover {
353 | background-color: rgba(74, 110, 224, 0.1);
354 | }
355 |
356 | .generate-btn {
357 | display: block;
358 | width: 100%;
359 | padding: 14px;
360 | margin-top: 20px;
361 | background-color: var(--primary-color);
362 | color: white;
363 | border: none;
364 | border-radius: var(--border-radius);
365 | font-weight: 600;
366 | cursor: pointer;
367 | transition: all var(--transition-speed) ease;
368 | display: flex;
369 | align-items: center;
370 | justify-content: center;
371 | font-size: 15px;
372 | }
373 |
374 | .generate-btn i {
375 | margin-right: 8px;
376 | }
377 |
378 | .generate-btn:hover {
379 | background-color: var(--secondary-color);
380 | transform: translateY(-2px);
381 | box-shadow: 0 4px 12px rgba(74, 110, 224, 0.3);
382 | }
383 |
384 | .generate-btn:active {
385 | transform: translateY(0);
386 | box-shadow: none;
387 | }
388 |
389 | .strength-meter {
390 | margin-top: 20px;
391 | background-color: var(--light-bg);
392 | border-radius: var(--border-radius);
393 | padding: 16px;
394 | }
395 |
396 | .strength-title {
397 | font-weight: 600;
398 | margin-bottom: 12px;
399 | display: flex;
400 | align-items: center;
401 | justify-content: space-between;
402 | font-size: 15px;
403 | }
404 |
405 | .strength-label {
406 | font-weight: 600;
407 | }
408 |
409 | .strength-value {
410 | font-weight: 600;
411 | padding: 4px 10px;
412 | border-radius: 4px;
413 | font-size: 14px;
414 | }
415 |
416 | .strength-bar {
417 | height: 8px;
418 | width: 100%;
419 | background-color: var(--medium-gray);
420 | border-radius: 6px;
421 | overflow: hidden;
422 | margin-bottom: 16px;
423 | }
424 |
425 | .strength-progress {
426 | height: 100%;
427 | width: 0;
428 | transition: width 0.3s ease;
429 | }
430 |
431 | .strength-info {
432 | display: flex;
433 | flex-wrap: wrap;
434 | gap: 10px;
435 | }
436 |
437 | .info-item {
438 | flex: 1;
439 | min-width: 140px;
440 | background-color: white;
441 | border-radius: var(--border-radius);
442 | padding: 12px;
443 | font-size: 14px;
444 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
445 | }
446 |
447 | .info-label {
448 | color: var(--dark-gray);
449 | margin-bottom: 5px;
450 | }
451 |
452 | .info-value {
453 | font-weight: 600;
454 | color: var(--text-color);
455 | }
456 |
457 | .pin-options {
458 | display: flex;
459 | gap: 10px;
460 | margin-bottom: 16px;
461 | }
462 |
463 | .pin-option {
464 | flex: 1;
465 | display: flex;
466 | align-items: center;
467 | justify-content: center;
468 | gap: 8px;
469 | background-color: var(--light-bg);
470 | border: 2px solid var(--light-gray);
471 | border-radius: var(--border-radius);
472 | padding: 10px;
473 | cursor: pointer;
474 | transition: all var(--transition-speed) ease;
475 | font-weight: 500;
476 | font-size: 14px;
477 | }
478 |
479 | .pin-option:hover {
480 | background-color: var(--light-gray);
481 | }
482 |
483 | .pin-option.active {
484 | background-color: var(--primary-color);
485 | border-color: var(--primary-color);
486 | color: white;
487 | }
488 |
489 | .passphrase-options {
490 | display: grid;
491 | grid-template-columns: 1fr 1fr;
492 | gap: 14px;
493 | margin-bottom: 16px;
494 | }
495 |
496 | .passphrase-field {
497 | grid-column: 1 / -1;
498 | margin-bottom: 6px;
499 | }
500 |
501 | .select-wrapper {
502 | position: relative;
503 | }
504 |
505 | .select-wrapper::after {
506 | content: "\f107";
507 | font-family: "Font Awesome 6 Free";
508 | font-weight: 900;
509 | position: absolute;
510 | right: 12px;
511 | top: 50%;
512 | transform: translateY(-50%);
513 | color: var(--dark-gray);
514 | pointer-events: none;
515 | }
516 |
517 | select {
518 | width: 100%;
519 | padding: 10px 30px 10px 12px;
520 | border: 2px solid var(--light-gray);
521 | border-radius: var(--border-radius);
522 | background-color: white;
523 | appearance: none;
524 | cursor: pointer;
525 | transition: all var(--transition-speed) ease;
526 | font-size: 14px;
527 | }
528 |
529 | select:focus {
530 | outline: none;
531 | border-color: var(--primary-color);
532 | box-shadow: 0 0 0 3px rgba(74, 110, 224, 0.2);
533 | }
534 |
535 | /* Toast notifications */
536 | .toaster {
537 | position: fixed;
538 | top: 20px;
539 | right: 20px;
540 | max-width: 300px;
541 | z-index: 1000;
542 | }
543 |
544 | .toast {
545 | padding: 12px 16px;
546 | margin-bottom: 10px;
547 | border-radius: var(--border-radius);
548 | color: white;
549 | display: flex;
550 | align-items: center;
551 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
552 | transform: translateX(120%);
553 | transition: transform 0.3s ease;
554 | cursor: pointer;
555 | font-size: 14px;
556 | }
557 |
558 | .toast.show {
559 | transform: translateX(0);
560 | }
561 |
562 | .toast i {
563 | margin-right: 10px;
564 | font-size: 16px;
565 | }
566 |
567 | .toast-success {
568 | background-color: var(--success-color);
569 | }
570 |
571 | .toast-error {
572 | background-color: var(--danger-color);
573 | }
574 |
575 | .toast-info {
576 | background-color: var(--info-color);
577 | }
578 |
579 | .toast-warning {
580 | background-color: var(--warning-color);
581 | }
582 |
583 | .footer {
584 | border-top: 1px solid var(--light-gray);
585 | padding: 14px 16px;
586 | text-align: center;
587 | color: var(--dark-gray);
588 | font-size: 14px;
589 | background-color: var(--light-bg);
590 | margin-top: 10px;
591 | }
592 |
593 | .footer a {
594 | color: var(--primary-color);
595 | text-decoration: none;
596 | transition: color 0.2s ease;
597 | }
598 |
599 | .footer a:hover {
600 | color: var(--secondary-color);
601 | text-decoration: underline;
602 | }
603 |
604 | /* Responsive styles */
605 | @media screen and (max-width: 480px) {
606 | .container {
607 | border-radius: var(--border-radius);
608 | max-width: 100%;
609 | display: flex;
610 | flex-direction: column;
611 | margin: 0;
612 | min-height: auto;
613 | max-height: 100vh;
614 | }
615 |
616 | .tab-content {
617 | flex: 1;
618 | overflow-y: auto;
619 | padding: 18px;
620 | }
621 |
622 | body {
623 | padding: 15px;
624 | min-height: 100vh;
625 | display: flex;
626 | align-items: center;
627 | justify-content: center;
628 | }
629 |
630 | .passphrase-options {
631 | grid-template-columns: 1fr;
632 | }
633 |
634 | .algorithm-option {
635 | min-width: 80px;
636 | font-size: 13px;
637 | padding: 8px;
638 | }
639 |
640 | .pin-option {
641 | font-size: 13px;
642 | padding: 8px;
643 | }
644 |
645 | .option-label {
646 | font-size: 13px;
647 | }
648 |
649 | .tab {
650 | padding: 12px 5px;
651 | font-size: 14px;
652 | }
653 |
654 | .result-input {
655 | font-size: 14px;
656 | padding: 10px 40px 10px 12px;
657 | }
658 |
659 | .pin-result {
660 | font-size: 22px;
661 | }
662 |
663 | .footer {
664 | padding: 12px;
665 | font-size: 13px;
666 | }
667 | }
668 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", function () {
2 | // Word lists for passphrase generation
3 | const wordLists = {
4 | common: [
5 | "apple",
6 | "banana",
7 | "cherry",
8 | "date",
9 | "elephant",
10 | "frog",
11 | "giraffe",
12 | "horse",
13 | "igloo",
14 | "jaguar",
15 | "koala",
16 | "lemon",
17 | "monkey",
18 | "nugget",
19 | "orange",
20 | "panda",
21 | "quartz",
22 | "rabbit",
23 | "sunset",
24 | "tiger",
25 | "umbrella",
26 | "violet",
27 | "whisper",
28 | "xylophone",
29 | "yellow",
30 | "zebra",
31 | "acorn",
32 | "bridge",
33 | "candle",
34 | "dolphin",
35 | "echo",
36 | "feather",
37 | "garden",
38 | "harbor",
39 | "island",
40 | "jungle",
41 | "kettle",
42 | "lantern",
43 | "meadow",
44 | "nectar",
45 | "ocean",
46 | "planet",
47 | "quarry",
48 | "river",
49 | "sapphire",
50 | "thunder",
51 | "unicorn",
52 | "valley",
53 | "window",
54 | "yarn",
55 | ],
56 | eff: [
57 | "abacus",
58 | "anthem",
59 | "azure",
60 | "bagpipe",
61 | "bamboo",
62 | "bronze",
63 | "canyon",
64 | "cascade",
65 | "citrus",
66 | "crimson",
67 | "diamond",
68 | "embark",
69 | "enigma",
70 | "fable",
71 | "galaxy",
72 | "gossip",
73 | "hazel",
74 | "iceberg",
75 | "jasmine",
76 | "jubilee",
77 | "karma",
78 | "limbo",
79 | "magma",
80 | "navigate",
81 | "nebula",
82 | "obsidian",
83 | "oyster",
84 | "pajama",
85 | "pixel",
86 | "plaza",
87 | "quasar",
88 | "raven",
89 | "ruby",
90 | "saffron",
91 | "sandal",
92 | "tactic",
93 | "tundra",
94 | "utility",
95 | "vacation",
96 | "velvet",
97 | "voyage",
98 | "walnut",
99 | "western",
100 | "wisdom",
101 | "wombat",
102 | "yoga",
103 | "yogurt",
104 | "zenith",
105 | "zephyr",
106 | "zodiac",
107 | ],
108 | diceware: [
109 | "aardvark",
110 | "bacon",
111 | "cadet",
112 | "daemon",
113 | "eagle",
114 | "falcon",
115 | "gadget",
116 | "halibut",
117 | "ibex",
118 | "jackal",
119 | "kabob",
120 | "lagoon",
121 | "maestro",
122 | "nacho",
123 | "oblong",
124 | "phoenix",
125 | "quail",
126 | "radar",
127 | "sable",
128 | "taco",
129 | "umpire",
130 | "vortex",
131 | "waffle",
132 | "xerox",
133 | "yahoo",
134 | "zucchini",
135 | "atlas",
136 | "basket",
137 | "cabin",
138 | "dagger",
139 | "earwig",
140 | "fantasy",
141 | "galaxy",
142 | "hammer",
143 | "indoor",
144 | "javelin",
145 | "keyhole",
146 | "laptop",
147 | "magnet",
148 | "noodle",
149 | "octopus",
150 | "pancake",
151 | "quantum",
152 | "raccoon",
153 | "sausage",
154 | "teapot",
155 | "upward",
156 | "vagabond",
157 | "weasel",
158 | "xylophone",
159 | ],
160 | bip39: [
161 | "absent",
162 | "acid",
163 | "actor",
164 | "affair",
165 | "airport",
166 | "alarm",
167 | "alcohol",
168 | "alien",
169 | "alpha",
170 | "among",
171 | "amount",
172 | "analyst",
173 | "anchor",
174 | "ancient",
175 | "anger",
176 | "animal",
177 | "ankle",
178 | "answer",
179 | "antenna",
180 | "antique",
181 | "anxiety",
182 | "apart",
183 | "apology",
184 | "appear",
185 | "approve",
186 | "arctic",
187 | "area",
188 | "arena",
189 | "argue",
190 | "armor",
191 | "army",
192 | "arrange",
193 | "arrest",
194 | "arrive",
195 | "arrow",
196 | "art",
197 | "asthma",
198 | "athlete",
199 | "atom",
200 | "attack",
201 | "auction",
202 | "audit",
203 | "august",
204 | "aunt",
205 | "author",
206 | "auto",
207 | "autumn",
208 | "average",
209 | "avocado",
210 | "aware",
211 | ],
212 | };
213 |
214 | // DOM elements
215 | const tabs = document.querySelectorAll(".tab");
216 | const tabContents = document.querySelectorAll(".tab-content");
217 | const passwordResult = document.getElementById("password-result");
218 | const pinResult = document.getElementById("pin-result");
219 | const passphraseResult = document.getElementById("passphrase-result");
220 | const generatePasswordBtn = document.getElementById("generate-password-btn");
221 | const generatePinBtn = document.getElementById("generate-pin-btn");
222 | const generatePassphraseBtn = document.getElementById(
223 | "generate-passphrase-btn"
224 | );
225 | const passwordCopyBtn = document.getElementById("password-copy-btn");
226 | const pinCopyBtn = document.getElementById("pin-copy-btn");
227 | const passphraseCopyBtn = document.getElementById("passphrase-copy-btn");
228 | const passwordCustomOptions = document.getElementById(
229 | "password-custom-options"
230 | );
231 | const passwordLength = document.getElementById("password-length");
232 | const passwordLengthValue = document.getElementById("password-length-value");
233 | const wordsCount = document.getElementById("words-count");
234 | const wordsCountValue = document.getElementById("words-count-value");
235 | const passwordStrengthProgress = document.getElementById(
236 | "password-strength-progress"
237 | );
238 | const passwordStrengthValue = document.getElementById(
239 | "password-strength-value"
240 | );
241 | const passwordEntropy = document.getElementById("password-entropy");
242 | const passwordCrackTime = document.getElementById("password-crack-time");
243 | const pinStrengthProgress = document.getElementById("pin-strength-progress");
244 | const pinStrengthValue = document.getElementById("pin-strength-value");
245 | const pinAttackResistance = document.getElementById("pin-attack-resistance");
246 | const pinBruteForce = document.getElementById("pin-brute-force");
247 | const passphraseStrengthProgress = document.getElementById(
248 | "passphrase-strength-progress"
249 | );
250 | const passphraseStrengthValue = document.getElementById(
251 | "passphrase-strength-value"
252 | );
253 | const passphraseEntropy = document.getElementById("passphrase-entropy");
254 | const passphraseUniqueness = document.getElementById("passphrase-uniqueness");
255 | const passwordAlgorithmOptions = document.querySelectorAll(
256 | "#password-tab .algorithm-option"
257 | );
258 | const pinAlgorithmOptions = document.querySelectorAll(
259 | "#pin-tab .algorithm-option"
260 | );
261 | const pinOptions = document.querySelectorAll(".pin-option");
262 | const includeUppercase = document.getElementById("include-uppercase");
263 | const includeLowercase = document.getElementById("include-lowercase");
264 | const includeNumbers = document.getElementById("include-numbers");
265 | const includeSymbols = document.getElementById("include-symbols");
266 | const excludeSimilar = document.getElementById("exclude-similar");
267 | const excludeAmbiguous = document.getElementById("exclude-ambiguous");
268 | const wordList = document.getElementById("word-list");
269 | const separator = document.getElementById("separator");
270 | const capitalizeFirst = document.getElementById("capitalize-first");
271 | const addNumber = document.getElementById("add-number");
272 | const addSymbol = document.getElementById("add-symbol");
273 | const toaster = document.getElementById("toaster");
274 |
275 | // State variables
276 | let currentPasswordAlgorithm = "standard";
277 | let currentPinAlgorithm = "random";
278 | let currentPinLength = 4;
279 |
280 | // Tab switching
281 | tabs.forEach((tab) => {
282 | tab.addEventListener("click", () => {
283 | tabs.forEach((t) => t.classList.remove("active"));
284 | tabContents.forEach((tc) => tc.classList.remove("active"));
285 | tab.classList.add("active");
286 | document.getElementById(`${tab.dataset.tab}-tab`).classList.add("active");
287 | });
288 | });
289 |
290 | // Password algorithm selection
291 | passwordAlgorithmOptions.forEach((option) => {
292 | option.addEventListener("click", () => {
293 | passwordAlgorithmOptions.forEach((o) => o.classList.remove("active"));
294 | option.classList.add("active");
295 | currentPasswordAlgorithm = option.dataset.algorithm;
296 |
297 | // Show/hide custom options
298 | if (currentPasswordAlgorithm === "custom") {
299 | passwordCustomOptions.classList.add("visible");
300 | } else {
301 | passwordCustomOptions.classList.remove("visible");
302 |
303 | // Set predefined lengths
304 | if (currentPasswordAlgorithm === "standard") {
305 | passwordLength.value = 12;
306 | } else if (currentPasswordAlgorithm === "balanced") {
307 | passwordLength.value = 16;
308 | } else if (currentPasswordAlgorithm === "secure") {
309 | passwordLength.value = 24;
310 | }
311 |
312 | passwordLengthValue.textContent = passwordLength.value;
313 | }
314 | });
315 | });
316 |
317 | // PIN algorithm selection
318 | pinAlgorithmOptions.forEach((option) => {
319 | option.addEventListener("click", () => {
320 | pinAlgorithmOptions.forEach((o) => o.classList.remove("active"));
321 | option.classList.add("active");
322 | currentPinAlgorithm = option.dataset.algorithm;
323 | });
324 | });
325 |
326 | // PIN length selection
327 | pinOptions.forEach((option) => {
328 | option.addEventListener("click", () => {
329 | pinOptions.forEach((o) => o.classList.remove("active"));
330 | option.classList.add("active");
331 | currentPinLength = parseInt(option.dataset.pinLength);
332 | });
333 | });
334 |
335 | // Password length slider
336 | passwordLength.addEventListener("input", () => {
337 | passwordLengthValue.textContent = passwordLength.value;
338 | });
339 |
340 | // Words count slider
341 | wordsCount.addEventListener("input", () => {
342 | wordsCountValue.textContent = wordsCount.value;
343 | });
344 |
345 | // Password generation
346 | function generatePassword() {
347 | let length,
348 | includeUpper,
349 | includeLower,
350 | includeNum,
351 | includeSym,
352 | excludeSim,
353 | excludeAmb;
354 |
355 | // Set parameters based on algorithm
356 | if (currentPasswordAlgorithm === "standard") {
357 | length = 12;
358 | includeUpper = true;
359 | includeLower = true;
360 | includeNum = true;
361 | includeSym = true;
362 | excludeSim = false;
363 | excludeAmb = false;
364 | } else if (currentPasswordAlgorithm === "balanced") {
365 | length = 16;
366 | includeUpper = true;
367 | includeLower = true;
368 | includeNum = true;
369 | includeSym = true;
370 | excludeSim = false;
371 | excludeAmb = false;
372 | } else if (currentPasswordAlgorithm === "secure") {
373 | length = 24;
374 | includeUpper = true;
375 | includeLower = true;
376 | includeNum = true;
377 | includeSym = true;
378 | excludeSim = false;
379 | excludeAmb = false;
380 | } else {
381 | // custom
382 | length = parseInt(passwordLength.value);
383 | includeUpper = includeUppercase.checked;
384 | includeLower = includeLowercase.checked;
385 | includeNum = includeNumbers.checked;
386 | includeSym = includeSymbols.checked;
387 | excludeSim = excludeSimilar.checked;
388 | excludeAmb = excludeAmbiguous.checked;
389 | }
390 |
391 | // Ensure at least one character type is selected
392 | if (!includeUpper && !includeLower && !includeNum && !includeSym) {
393 | showToast("Please select at least one character type", "error");
394 | return;
395 | }
396 |
397 | let charset = "";
398 | let upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
399 | let lowerChars = "abcdefghijklmnopqrstuvwxyz";
400 | let numChars = "0123456789";
401 | let symChars = "!@#$%^&*()-_=+[]{}|;:,.<>?/";
402 |
403 | // Remove similar characters if selected
404 | if (excludeSim) {
405 | upperChars = upperChars.replace(/[IL]/g, "");
406 | lowerChars = lowerChars.replace(/[il]/g, "");
407 | numChars = numChars.replace(/[10]/g, "");
408 | }
409 |
410 | // Remove ambiguous symbols if selected
411 | if (excludeAmb) {
412 | symChars = symChars.replace(/[\[\]{}()<>\/\\]/g, "");
413 | }
414 |
415 | if (includeUpper) charset += upperChars;
416 | if (includeLower) charset += lowerChars;
417 | if (includeNum) charset += numChars;
418 | if (includeSym) charset += symChars;
419 |
420 | let password = "";
421 | let hasUpper = false;
422 | let hasLower = false;
423 | let hasNum = false;
424 | let hasSym = false;
425 |
426 | // Algorithm-specific generation logic
427 | if (currentPasswordAlgorithm === "secure") {
428 | // Secure algorithm uses cryptographically secure random values
429 | const array = new Uint8Array(length);
430 | window.crypto.getRandomValues(array);
431 |
432 | for (let i = 0; i < length; i++) {
433 | const randomIndex = array[i] % charset.length;
434 | const char = charset[randomIndex];
435 | password += char;
436 |
437 | if (upperChars.includes(char)) hasUpper = true;
438 | if (lowerChars.includes(char)) hasLower = true;
439 | if (numChars.includes(char)) hasNum = true;
440 | if (symChars.includes(char)) hasSym = true;
441 | }
442 |
443 | // Ensure all required character types are included
444 | const missingTypes = [];
445 | if (includeUpper && !hasUpper) missingTypes.push("upper");
446 | if (includeLower && !hasLower) missingTypes.push("lower");
447 | if (includeNum && !hasNum) missingTypes.push("num");
448 | if (includeSym && !hasSym) missingTypes.push("sym");
449 |
450 | // Replace random characters with missing types
451 | if (missingTypes.length > 0) {
452 | const positions = [];
453 | const array = new Uint8Array(missingTypes.length);
454 | window.crypto.getRandomValues(array);
455 |
456 | for (let i = 0; i < missingTypes.length; i++) {
457 | positions.push(array[i] % length);
458 | }
459 |
460 | for (let i = 0; i < missingTypes.length; i++) {
461 | let char;
462 | if (missingTypes[i] === "upper") {
463 | char = upperChars[array[i] % upperChars.length];
464 | } else if (missingTypes[i] === "lower") {
465 | char = lowerChars[array[i] % lowerChars.length];
466 | } else if (missingTypes[i] === "num") {
467 | char = numChars[array[i] % numChars.length];
468 | } else if (missingTypes[i] === "sym") {
469 | char = symChars[array[i] % symChars.length];
470 | }
471 |
472 | const pos = positions[i];
473 | password =
474 | password.substring(0, pos) + char + password.substring(pos + 1);
475 | }
476 | }
477 | } else {
478 | // Standard and Balanced algorithms
479 | for (let i = 0; i < length; i++) {
480 | const randomIndex = Math.floor(Math.random() * charset.length);
481 | const char = charset[randomIndex];
482 | password += char;
483 |
484 | if (upperChars.includes(char)) hasUpper = true;
485 | if (lowerChars.includes(char)) hasLower = true;
486 | if (numChars.includes(char)) hasNum = true;
487 | if (symChars.includes(char)) hasSym = true;
488 | }
489 |
490 | // For balanced and standard, ensure required character types
491 | if (
492 | (includeUpper && !hasUpper) ||
493 | (includeLower && !hasLower) ||
494 | (includeNum && !hasNum) ||
495 | (includeSym && !hasSym)
496 | ) {
497 | // Retry if not all required types are present
498 | return generatePassword();
499 | }
500 | }
501 |
502 | passwordResult.value = password;
503 | updatePasswordStrength(password);
504 | }
505 |
506 | // PIN generation
507 | function generatePIN() {
508 | let pin = "";
509 |
510 | if (currentPinAlgorithm === "random") {
511 | // Simple random algorithm
512 | for (let i = 0; i < currentPinLength; i++) {
513 | pin += Math.floor(Math.random() * 10);
514 | }
515 | } else {
516 | // secure
517 | // Use crypto.getRandomValues for more secure randomness
518 | const array = new Uint8Array(currentPinLength);
519 | window.crypto.getRandomValues(array);
520 |
521 | // Avoid common patterns and ensure uniqueness for secure PINs
522 | const usedDigits = new Set();
523 | let prevDigit = -1;
524 |
525 | for (let i = 0; i < currentPinLength; i++) {
526 | let digit = array[i] % 10;
527 |
528 | // Avoid repeating the same digit consecutively
529 | while (digit === prevDigit) {
530 | const newArray = new Uint8Array(1);
531 | window.crypto.getRandomValues(newArray);
532 | digit = newArray[0] % 10;
533 | }
534 |
535 | // For 4-digit PIN, try to avoid using the same digit more than once
536 | if (
537 | currentPinLength === 4 &&
538 | usedDigits.has(digit) &&
539 | usedDigits.size < 9
540 | ) {
541 | const newArray = new Uint8Array(1);
542 | window.crypto.getRandomValues(newArray);
543 | digit = newArray[0] % 10;
544 | while (usedDigits.has(digit)) {
545 | window.crypto.getRandomValues(newArray);
546 | digit = newArray[0] % 10;
547 | }
548 | }
549 |
550 | usedDigits.add(digit);
551 | prevDigit = digit;
552 | pin += digit;
553 | }
554 |
555 | // Avoid sequential patterns (e.g., 1234, 5678)
556 | if (currentPinLength === 4) {
557 | const isSequential =
558 | (pin[0] == parseInt(pin[1]) - 1 &&
559 | pin[1] == parseInt(pin[2]) - 1 &&
560 | pin[2] == parseInt(pin[3]) - 1) ||
561 | (pin[0] == parseInt(pin[1]) + 1 &&
562 | pin[1] == parseInt(pin[2]) + 1 &&
563 | pin[2] == parseInt(pin[3]) + 1);
564 |
565 | if (isSequential) {
566 | return generatePIN(); // Regenerate
567 | }
568 | }
569 |
570 | // Avoid common PINs like 1111, 1234, 0000, etc.
571 | const commonPins4 = [
572 | "1234",
573 | "0000",
574 | "1111",
575 | "2222",
576 | "3333",
577 | "4444",
578 | "5555",
579 | "6666",
580 | "7777",
581 | "8888",
582 | "9999",
583 | "1212",
584 | "1313",
585 | ];
586 | const commonPins6 = [
587 | "123456",
588 | "000000",
589 | "111111",
590 | "222222",
591 | "333333",
592 | "444444",
593 | "555555",
594 | "666666",
595 | "777777",
596 | "888888",
597 | "999999",
598 | "121212",
599 | "131313",
600 | ];
601 |
602 | if (
603 | (currentPinLength === 4 && commonPins4.includes(pin)) ||
604 | (currentPinLength === 6 && commonPins6.includes(pin))
605 | ) {
606 | return generatePIN(); // Regenerate
607 | }
608 | }
609 |
610 | pinResult.value = pin;
611 | updatePINStrength(pin);
612 | }
613 |
614 | // Passphrase generation
615 | function generatePassphrase() {
616 | const selectedWordList = wordLists[wordList.value];
617 | const numWords = parseInt(wordsCount.value);
618 | const sep = separator.value;
619 | const capFirst = capitalizeFirst.checked;
620 | const includeNumber = addNumber.checked;
621 | const includeSymbol = addSymbol.checked;
622 |
623 | let passphrase = "";
624 | const selectedWords = [];
625 |
626 | // Generate crypto-secure random indexes for word selection
627 | const array = new Uint8Array(numWords * 2); // Get extra values for uniqueness check
628 | window.crypto.getRandomValues(array);
629 |
630 | // Select words, ensuring no duplicates
631 | for (let i = 0; i < numWords; i++) {
632 | let index = array[i] % selectedWordList.length;
633 | let word = selectedWordList[index];
634 |
635 | // Try to avoid duplicates
636 | if (
637 | selectedWords.includes(word) &&
638 | selectedWords.length < selectedWordList.length
639 | ) {
640 | index = array[i + numWords] % selectedWordList.length;
641 | word = selectedWordList[index];
642 |
643 | // If still duplicate, try one more time with different method
644 | if (selectedWords.includes(word)) {
645 | const newArray = new Uint8Array(1);
646 | window.crypto.getRandomValues(newArray);
647 | index = newArray[0] % selectedWordList.length;
648 | word = selectedWordList[index];
649 | }
650 | }
651 |
652 | if (capFirst) {
653 | word = word.charAt(0).toUpperCase() + word.slice(1);
654 | }
655 |
656 | selectedWords.push(word);
657 | }
658 |
659 | passphrase = selectedWords.join(sep);
660 |
661 | // Add random number if selected
662 | if (includeNumber) {
663 | const numArray = new Uint8Array(2);
664 | window.crypto.getRandomValues(numArray);
665 | const randomNum = (numArray[0] % 10) * 10 + (numArray[1] % 10); // 0-99
666 | passphrase += randomNum;
667 | }
668 |
669 | // Add random symbol if selected
670 | if (includeSymbol) {
671 | const symbols = "!@#$%^&*()-_=+[]{}|;:,.<>?/";
672 | const symArray = new Uint8Array(1);
673 | window.crypto.getRandomValues(symArray);
674 | passphrase += symbols[symArray[0] % symbols.length];
675 | }
676 |
677 | passphraseResult.value = passphrase;
678 | updatePassphraseStrength(passphrase, numWords, selectedWordList.length);
679 | }
680 |
681 | // Copy to clipboard functions
682 | function copyToClipboard(text, type) {
683 | navigator.clipboard
684 | .writeText(text)
685 | .then(() => {
686 | showToast(`${type} copied to clipboard`, "success");
687 | })
688 | .catch((err) => {
689 | showToast("Failed to copy to clipboard", "error");
690 | });
691 | }
692 |
693 | // Toast notification system
694 | function showToast(message, type = "info") {
695 | const toast = document.createElement("div");
696 | toast.classList.add("toast", `toast-${type}`);
697 |
698 | let icon;
699 | switch (type) {
700 | case "success":
701 | icon = "fa-check-circle";
702 | break;
703 | case "error":
704 | icon = "fa-exclamation-circle";
705 | break;
706 | case "warning":
707 | icon = "fa-exclamation-triangle";
708 | break;
709 | default:
710 | icon = "fa-info-circle";
711 | }
712 |
713 | toast.innerHTML = `
714 |
715 | ${message}
716 | `;
717 |
718 | toaster.appendChild(toast);
719 |
720 | // Trigger reflow
721 | toast.offsetHeight;
722 |
723 | // Show toast
724 | setTimeout(() => {
725 | toast.classList.add("show");
726 | }, 10);
727 |
728 | // Remove toast after delay
729 | setTimeout(() => {
730 | toast.classList.remove("show");
731 | setTimeout(() => {
732 | toast.remove();
733 | }, 300);
734 | }, 3000);
735 |
736 | // Click to dismiss
737 | toast.addEventListener("click", () => {
738 | toast.classList.remove("show");
739 | setTimeout(() => {
740 | toast.remove();
741 | }, 300);
742 | });
743 | }
744 |
745 | // Calculate password strength
746 | function updatePasswordStrength(password) {
747 | if (!password) {
748 | resetPasswordStrength();
749 | return;
750 | }
751 |
752 | let entropy = calculatePasswordEntropy(password);
753 | let strength = getStrengthLevel(entropy);
754 | let crackTime = estimateCrackTime(entropy);
755 |
756 | // Update UI
757 | passwordEntropy.textContent = `${entropy.toFixed(2)} bits`;
758 | passwordCrackTime.textContent = crackTime;
759 | passwordStrengthValue.textContent = strength.label;
760 | passwordStrengthValue.style.backgroundColor = strength.color;
761 | passwordStrengthProgress.style.width = `${Math.min(
762 | 100,
763 | (entropy / 128) * 100
764 | )}%`;
765 | passwordStrengthProgress.style.backgroundColor = strength.color;
766 | }
767 |
768 | // Calculate PIN strength
769 | function updatePINStrength(pin) {
770 | if (!pin) {
771 | resetPINStrength();
772 | return;
773 | }
774 |
775 | // Different algorithms for 4 and 6 digits
776 | if (pin.length === 4) {
777 | const combinations = 10000;
778 | const attackResistance =
779 | currentPinAlgorithm === "secure"
780 | ? "Resistant to simple guessing attacks"
781 | : "Vulnerable to targeted attacks";
782 | const color = currentPinAlgorithm === "secure" ? "#f39c12" : "#e74c3c";
783 | const percentage = currentPinAlgorithm === "secure" ? 30 : 15;
784 |
785 | pinAttackResistance.textContent = attackResistance;
786 | pinBruteForce.textContent = formatNumber(combinations) + " combinations";
787 | pinStrengthValue.textContent =
788 | currentPinAlgorithm === "secure" ? "Basic" : "Minimal";
789 | pinStrengthValue.style.backgroundColor = color;
790 | pinStrengthProgress.style.width = `${percentage}%`;
791 | pinStrengthProgress.style.backgroundColor = color;
792 | } else {
793 | // 6 digits
794 | const combinations = 1000000;
795 | const attackResistance =
796 | currentPinAlgorithm === "secure"
797 | ? "Resistant to most common guessing attacks"
798 | : "May be vulnerable to targeted attacks";
799 | const color = currentPinAlgorithm === "secure" ? "#2ecc71" : "#f39c12";
800 | const percentage = currentPinAlgorithm === "secure" ? 60 : 40;
801 |
802 | pinAttackResistance.textContent = attackResistance;
803 | pinBruteForce.textContent = formatNumber(combinations) + " combinations";
804 | pinStrengthValue.textContent =
805 | currentPinAlgorithm === "secure" ? "Moderate" : "Basic";
806 | pinStrengthValue.style.backgroundColor = color;
807 | pinStrengthProgress.style.width = `${percentage}%`;
808 | pinStrengthProgress.style.backgroundColor = color;
809 | }
810 | }
811 |
812 | // Calculate passphrase strength
813 | function updatePassphraseStrength(passphrase, numWords, dictionarySize) {
814 | if (!passphrase) {
815 | resetPassphraseStrength();
816 | return;
817 | }
818 |
819 | // Base entropy calculation for word selection
820 | let baseEntropy = numWords * Math.log2(dictionarySize);
821 |
822 | // Factors that influence total entropy
823 | const useCapitalization = capitalizeFirst.checked;
824 | const useNumber = addNumber.checked;
825 | const useSymbol = addSymbol.checked;
826 | const separatorEntropy = separator.value === " " ? 0 : Math.log2(10); // 10 different separators
827 |
828 | // Additional entropy from options
829 | let additionalEntropy = 0;
830 |
831 | // Capitalization adds 1 bit per word (2 options per word)
832 | if (useCapitalization) {
833 | additionalEntropy += numWords * 1;
834 | }
835 |
836 | // Number adds ~6.64 bits (100 options)
837 | if (useNumber) {
838 | additionalEntropy += Math.log2(100);
839 | }
840 |
841 | // Symbol adds ~4.7 bits (26 symbols)
842 | if (useSymbol) {
843 | additionalEntropy += Math.log2(26);
844 | }
845 |
846 | // Separator adds entropy only if not using space (default)
847 | additionalEntropy += separatorEntropy;
848 |
849 | // Total entropy
850 | const totalEntropy = baseEntropy + additionalEntropy;
851 |
852 | // Uniqueness (possible combinations)
853 | const uniqueness = Math.pow(2, totalEntropy);
854 |
855 | // Calculate strength
856 | let strength = getStrengthLevel(totalEntropy);
857 |
858 | // Update UI
859 | passphraseEntropy.textContent = `${totalEntropy.toFixed(2)} bits`;
860 | passphraseUniqueness.textContent = `1 in ${formatExponential(uniqueness)}`;
861 | passphraseStrengthValue.textContent = strength.label;
862 | passphraseStrengthValue.style.backgroundColor = strength.color;
863 | passphraseStrengthProgress.style.width = `${Math.min(
864 | 100,
865 | (totalEntropy / 128) * 100
866 | )}%`;
867 | passphraseStrengthProgress.style.backgroundColor = strength.color;
868 | }
869 |
870 | // Reset strength indicators
871 | function resetPasswordStrength() {
872 | passwordEntropy.textContent = "-";
873 | passwordCrackTime.textContent = "-";
874 | passwordStrengthValue.textContent = "-";
875 | passwordStrengthValue.style.backgroundColor = "";
876 | passwordStrengthProgress.style.width = "0";
877 | }
878 |
879 | function resetPINStrength() {
880 | pinAttackResistance.textContent = "-";
881 | pinBruteForce.textContent = "-";
882 | pinStrengthValue.textContent = "-";
883 | pinStrengthValue.style.backgroundColor = "";
884 | pinStrengthProgress.style.width = "0";
885 | }
886 |
887 | function resetPassphraseStrength() {
888 | passphraseEntropy.textContent = "-";
889 | passphraseUniqueness.textContent = "-";
890 | passphraseStrengthValue.textContent = "-";
891 | passphraseStrengthValue.style.backgroundColor = "";
892 | passphraseStrengthProgress.style.width = "0";
893 | }
894 |
895 | // Helper functions
896 | function calculatePasswordEntropy(password) {
897 | let charset = 0;
898 |
899 | // Calculate charset size
900 | if (/[a-z]/.test(password)) charset += 26;
901 | if (/[A-Z]/.test(password)) charset += 26;
902 | if (/[0-9]/.test(password)) charset += 10;
903 | if (/[^a-zA-Z0-9]/.test(password)) charset += 33;
904 |
905 | // Calculate entropy
906 | return Math.log2(Math.pow(charset, password.length));
907 | }
908 |
909 | function getStrengthLevel(entropy) {
910 | if (entropy < 28) {
911 | return { label: "Very Weak", color: "#e74c3c" };
912 | } else if (entropy < 36) {
913 | return { label: "Weak", color: "#e67e22" };
914 | } else if (entropy < 60) {
915 | return { label: "Moderate", color: "#f39c12" };
916 | } else if (entropy < 80) {
917 | return { label: "Strong", color: "#2ecc71" };
918 | } else if (entropy < 100) {
919 | return { label: "Very Strong", color: "#27ae60" };
920 | } else if (entropy < 128) {
921 | return { label: "Excellent", color: "#16a085" };
922 | } else {
923 | return { label: "Unbreakable", color: "#2980b9" };
924 | }
925 | }
926 |
927 | function estimateCrackTime(entropy) {
928 | // Assume 10 trillion guesses per second for a sophisticated attacker with dedicated hardware
929 | const guessesPerSecond = 10000000000000;
930 | const seconds = Math.pow(2, entropy) / guessesPerSecond / 2; // Divide by 2 for average case
931 |
932 | return formatTime(seconds);
933 | }
934 |
935 | function formatTime(seconds) {
936 | if (seconds < 60) {
937 | return "Instantly";
938 | } else if (seconds < 3600) {
939 | return `${Math.floor(seconds / 60)} minutes`;
940 | } else if (seconds < 86400) {
941 | return `${Math.floor(seconds / 3600)} hours`;
942 | } else if (seconds < 2592000) {
943 | return `${Math.floor(seconds / 86400)} days`;
944 | } else if (seconds < 31536000) {
945 | return `${Math.floor(seconds / 2592000)} months`;
946 | } else if (seconds < 315360000) {
947 | // 10 years
948 | return `${Math.floor(seconds / 31536000)} years`;
949 | } else if (seconds < 3153600000) {
950 | // 100 years
951 | return `${Math.floor(seconds / 31536000)} years`;
952 | } else {
953 | return "Centuries";
954 | }
955 | }
956 |
957 | function formatNumber(num) {
958 | return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
959 | }
960 |
961 | function formatExponential(num) {
962 | // Define thresholds and labels for human-readable numbers
963 | const thresholds = [
964 | { value: 1e3, label: "thousand" },
965 | { value: 1e6, label: "million" },
966 | { value: 1e9, label: "billion" },
967 | { value: 1e12, label: "trillion" },
968 | { value: 1e15, label: "quadrillion" },
969 | { value: 1e18, label: "quintillion" },
970 | { value: 1e21, label: "sextillion" },
971 | { value: 1e24, label: "septillion" },
972 | { value: 1e27, label: "octillion" },
973 | { value: 1e30, label: "nonillion" },
974 | { value: 1e33, label: "decillion" },
975 | { value: 1e36, label: "undecillion" },
976 | { value: 1e39, label: "duodecillion" },
977 | { value: 1e42, label: "tredecillion" },
978 | { value: 1e45, label: "quattuordecillion" },
979 | { value: 1e48, label: "quindecillion" },
980 | { value: 1e51, label: "sexdecillion" },
981 | { value: 1e54, label: "septendecillion" },
982 | { value: 1e57, label: "octodecillion" },
983 | { value: 1e60, label: "novemdecillion" },
984 | { value: 1e63, label: "vigintillion" },
985 | ];
986 |
987 | // If number is less than 1000, just format it
988 | if (num < 1e3) {
989 | return formatNumber(Math.round(num));
990 | }
991 |
992 | // Find the appropriate threshold
993 | for (let i = thresholds.length - 1; i >= 0; i--) {
994 | if (num >= thresholds[i].value) {
995 | // Calculate how many of this unit
996 | const value = num / thresholds[i].value;
997 | // Round to 2 decimal places
998 | const roundedValue = Math.round(value * 100) / 100;
999 |
1000 | // Format as "X million" or "X.XX million"
1001 | if (roundedValue === Math.floor(roundedValue)) {
1002 | return `${roundedValue} ${thresholds[i].label}`;
1003 | } else {
1004 | return `${roundedValue.toFixed(2)} ${thresholds[i].label}`;
1005 | }
1006 | }
1007 | }
1008 |
1009 | // Fallback for extremely large numbers
1010 | const exp = Math.floor(Math.log10(num));
1011 | return `10^${exp}`;
1012 | }
1013 |
1014 | // Event Listeners
1015 | generatePasswordBtn.addEventListener("click", generatePassword);
1016 | generatePinBtn.addEventListener("click", generatePIN);
1017 | generatePassphraseBtn.addEventListener("click", generatePassphrase);
1018 |
1019 | passwordCopyBtn.addEventListener("click", () => {
1020 | if (passwordResult.value) {
1021 | copyToClipboard(passwordResult.value, "Password");
1022 | }
1023 | });
1024 |
1025 | pinCopyBtn.addEventListener("click", () => {
1026 | if (pinResult.value) {
1027 | copyToClipboard(pinResult.value, "PIN");
1028 | }
1029 | });
1030 |
1031 | passphraseCopyBtn.addEventListener("click", () => {
1032 | if (passphraseResult.value) {
1033 | copyToClipboard(passphraseResult.value, "Passphrase");
1034 | }
1035 | });
1036 |
1037 | // Initialize with defaults
1038 | generatePassword();
1039 | generatePIN();
1040 | generatePassphrase();
1041 | });
1042 |
--------------------------------------------------------------------------------