├── .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 | [![GitHub license](https://img.shields.io/github/license/ramazancetinkaya/password-generator.svg)](LICENSE) 4 | [![GitHub issues](https://img.shields.io/github/issues/ramazancetinkaya/password-generator)](https://github.com/ramazancetinkaya/password-generator/issues) 5 | [![GitHub stars](https://img.shields.io/github/stars/ramazancetinkaya/password-generator)](https://github.com/ramazancetinkaya/password-generator/stargazers) 6 | [![GitHub forks](https://img.shields.io/github/forks/ramazancetinkaya/password-generator)](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 | Password Tab 63 |

Password Generation Tab

64 |
65 | 66 |
67 | PIN Tab 68 |

PIN Generation Tab

69 |
70 | 71 |
72 | Passphrase Tab 73 |

Passphrase Generation Tab

74 |
75 | 76 | ## Demo 77 | 78 | Experience the application instantly via GitHub Pages: 79 | 80 | [![Live Demo](https://img.shields.io/badge/Live%20Demo-Click%20Here-blue?style=for-the-badge)](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 | 48 |
49 |
50 |
Include Lowercase Letters
51 | 55 |
56 |
57 |
Include Numbers
58 | 62 |
63 |
64 |
Include Symbols
65 | 69 |
70 |
71 |
Exclude Similar Characters (i, l, 1, o, 0, etc.)
72 | 76 |
77 |
78 |
Exclude Ambiguous Symbols ({}, [], (), /, etc.)
79 | 83 |
84 |
85 |
86 | 87 |
88 | 89 | 92 |
93 | 94 | 97 | 98 |
99 |
100 | Password Strength 101 | - 102 |
103 |
104 |
105 |
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 | 150 |
151 | 152 | 155 | 156 |
157 |
158 | PIN Security 159 | - 160 |
161 |
162 |
163 |
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 | 197 |
198 |
199 | 211 |
212 |
213 |
Capitalize First Letter
214 | 218 |
219 |
220 |
Add Random Number
221 | 225 |
226 |
227 |
Add Random Symbol
228 | 232 |
233 |
234 |
235 | 236 |
237 | 238 | 241 |
242 | 243 | 246 | 247 |
248 |
249 | Passphrase Security 250 | - 251 |
252 |
253 |
254 |
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 | --------------------------------------------------------------------------------