├── .gitignore ├── LICENSE ├── README.md ├── mpw.js ├── pbkdf2.js └── scrypt.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore docs files 2 | _gh_pages 3 | _site 4 | .ruby-version 5 | 6 | # Numerous always-ignore extensions 7 | *.diff 8 | *.err 9 | *.orig 10 | *.log 11 | *.rej 12 | *.swo 13 | *.swp 14 | *.zip 15 | *.vi 16 | *~ 17 | 18 | # OS or Editor folders 19 | .DS_Store 20 | ._* 21 | Thumbs.db 22 | .cache 23 | .project 24 | .settings 25 | .tmproj 26 | *.esproj 27 | nbproject 28 | *.sublime-project 29 | *.sublime-workspace 30 | .idea 31 | 32 | # Komodo 33 | *.komodoproject 34 | .komodotools 35 | 36 | # grunt-html-validation 37 | validation-status.json 38 | validation-report.json 39 | 40 | # Folders to ignore 41 | node_modules 42 | bower_components -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International Public License 2 | 3 | By exercising the Licensed Rights (defined below), You accept and agree 4 | to be bound by the terms and conditions of this Creative Commons 5 | Attribution 4.0 International Public License ("Public License"). To the 6 | extent this Public License may be interpreted as a contract, You are 7 | granted the Licensed Rights in consideration of Your acceptance of 8 | these terms and conditions, and the Licensor grants You such rights in 9 | consideration of benefits the Licensor receives from making the 10 | Licensed Material available under these terms and conditions. 11 | 12 | 13 | Section 1 -- Definitions. 14 | 15 | a. Adapted Material means material subject to Copyright and Similar 16 | Rights that is derived from or based upon the Licensed Material 17 | and in which the Licensed Material is translated, altered, 18 | arranged, transformed, or otherwise modified in a manner requiring 19 | permission under the Copyright and Similar Rights held by the 20 | Licensor. For purposes of this Public License, where the Licensed 21 | Material is a musical work, performance, or sound recording, 22 | Adapted Material is always produced where the Licensed Material is 23 | synched in timed relation with a moving image. 24 | 25 | b. Adapter's License means the license You apply to Your Copyright 26 | and Similar Rights in Your contributions to Adapted Material in 27 | accordance with the terms and conditions of this Public License. 28 | 29 | c. Copyright and Similar Rights means copyright and/or similar rights 30 | closely related to copyright including, without limitation, 31 | performance, broadcast, sound recording, and Sui Generis Database 32 | Rights, without regard to how the rights are labeled or 33 | categorized. For purposes of this Public License, the rights 34 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 35 | Rights. 36 | 37 | d. Effective Technological Measures means those measures that, in the 38 | absence of proper authority, may not be circumvented under laws 39 | fulfilling obligations under Article 11 of the WIPO Copyright 40 | Treaty adopted on December 20, 1996, and/or similar international 41 | agreements. 42 | 43 | e. Exceptions and Limitations means fair use, fair dealing, and/or 44 | any other exception or limitation to Copyright and Similar Rights 45 | that applies to Your use of the Licensed Material. 46 | 47 | f. Licensed Material means the artistic or literary work, database, 48 | or other material to which the Licensor applied this Public 49 | License. 50 | 51 | g. Licensed Rights means the rights granted to You subject to the 52 | terms and conditions of this Public License, which are limited to 53 | all Copyright and Similar Rights that apply to Your use of the 54 | Licensed Material and that the Licensor has authority to license. 55 | 56 | h. Licensor means the individual(s) or entity(ies) granting rights 57 | under this Public License. 58 | 59 | i. Share means to provide material to the public by any means or 60 | process that requires permission under the Licensed Rights, such 61 | as reproduction, public display, public performance, distribution, 62 | dissemination, communication, or importation, and to make material 63 | available to the public including in ways that members of the 64 | public may access the material from a place and at a time 65 | individually chosen by them. 66 | 67 | j. Sui Generis Database Rights means rights other than copyright 68 | resulting from Directive 96/9/EC of the European Parliament and of 69 | the Council of 11 March 1996 on the legal protection of databases, 70 | as amended and/or succeeded, as well as other essentially 71 | equivalent rights anywhere in the world. 72 | 73 | k. You means the individual or entity exercising the Licensed Rights 74 | under this Public License. Your has a corresponding meaning. 75 | 76 | 77 | Section 2 -- Scope. 78 | 79 | a. License grant. 80 | 81 | 1. Subject to the terms and conditions of this Public License, 82 | the Licensor hereby grants You a worldwide, royalty-free, 83 | non-sublicensable, non-exclusive, irrevocable license to 84 | exercise the Licensed Rights in the Licensed Material to: 85 | 86 | a. reproduce and Share the Licensed Material, in whole or 87 | in part; and 88 | 89 | b. produce, reproduce, and Share Adapted Material. 90 | 91 | 2. Exceptions and Limitations. For the avoidance of doubt, where 92 | Exceptions and Limitations apply to Your use, this Public 93 | License does not apply, and You do not need to comply with 94 | its terms and conditions. 95 | 96 | 3. Term. The term of this Public License is specified in Section 97 | 6(a). 98 | 99 | 4. Media and formats; technical modifications allowed. The 100 | Licensor authorizes You to exercise the Licensed Rights in 101 | all media and formats whether now known or hereafter created, 102 | and to make technical modifications necessary to do so. The 103 | Licensor waives and/or agrees not to assert any right or 104 | authority to forbid You from making technical modifications 105 | necessary to exercise the Licensed Rights, including 106 | technical modifications necessary to circumvent Effective 107 | Technological Measures. For purposes of this Public License, 108 | simply making modifications authorized by this Section 2(a) 109 | (4) never produces Adapted Material. 110 | 111 | 5. Downstream recipients. 112 | 113 | a. Offer from the Licensor -- Licensed Material. Every 114 | recipient of the Licensed Material automatically 115 | receives an offer from the Licensor to exercise the 116 | Licensed Rights under the terms and conditions of this 117 | Public License. 118 | 119 | b. No downstream restrictions. You may not offer or impose 120 | any additional or different terms or conditions on, or 121 | apply any Effective Technological Measures to, the 122 | Licensed Material if doing so restricts exercise of the 123 | Licensed Rights by any recipient of the Licensed 124 | Material. 125 | 126 | 6. No endorsement. Nothing in this Public License constitutes or 127 | may be construed as permission to assert or imply that You 128 | are, or that Your use of the Licensed Material is, connected 129 | with, or sponsored, endorsed, or granted official status by, 130 | the Licensor or others designated to receive attribution as 131 | provided in Section 3(a)(1)(A)(i). 132 | 133 | b. Other rights. 134 | 135 | 1. Moral rights, such as the right of integrity, are not 136 | licensed under this Public License, nor are publicity, 137 | privacy, and/or other similar personality rights; however, to 138 | the extent possible, the Licensor waives and/or agrees not to 139 | assert any such rights held by the Licensor to the limited 140 | extent necessary to allow You to exercise the Licensed 141 | Rights, but not otherwise. 142 | 143 | 2. Patent and trademark rights are not licensed under this 144 | Public License. 145 | 146 | 3. To the extent possible, the Licensor waives any right to 147 | collect royalties from You for the exercise of the Licensed 148 | Rights, whether directly or through a collecting society 149 | under any voluntary or waivable statutory or compulsory 150 | licensing scheme. In all other cases the Licensor expressly 151 | reserves any right to collect such royalties. 152 | 153 | 154 | Section 3 -- License Conditions. 155 | 156 | Your exercise of the Licensed Rights is expressly made subject to the 157 | following conditions. 158 | 159 | a. Attribution. 160 | 161 | 1. If You Share the Licensed Material (including in modified 162 | form), You must: 163 | 164 | a. retain the following if it is supplied by the Licensor 165 | with the Licensed Material: 166 | 167 | i. identification of the creator(s) of the Licensed 168 | Material and any others designated to receive 169 | attribution, in any reasonable manner requested by 170 | the Licensor (including by pseudonym if 171 | designated); 172 | 173 | ii. a copyright notice; 174 | 175 | iii. a notice that refers to this Public License; 176 | 177 | iv. a notice that refers to the disclaimer of 178 | warranties; 179 | 180 | v. a URI or hyperlink to the Licensed Material to the 181 | extent reasonably practicable; 182 | 183 | b. indicate if You modified the Licensed Material and 184 | retain an indication of any previous modifications; and 185 | 186 | c. indicate the Licensed Material is licensed under this 187 | Public License, and include the text of, or the URI or 188 | hyperlink to, this Public License. 189 | 190 | 2. You may satisfy the conditions in Section 3(a)(1) in any 191 | reasonable manner based on the medium, means, and context in 192 | which You Share the Licensed Material. For example, it may be 193 | reasonable to satisfy the conditions by providing a URI or 194 | hyperlink to a resource that includes the required 195 | information. 196 | 197 | 3. If requested by the Licensor, You must remove any of the 198 | information required by Section 3(a)(1)(A) to the extent 199 | reasonably practicable. 200 | 201 | 4. If You Share Adapted Material You produce, the Adapter's 202 | License You apply must not prevent recipients of the Adapted 203 | Material from complying with this Public License. 204 | 205 | 206 | Section 4 -- Sui Generis Database Rights. 207 | 208 | Where the Licensed Rights include Sui Generis Database Rights that 209 | apply to Your use of the Licensed Material: 210 | 211 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 212 | to extract, reuse, reproduce, and Share all or a substantial 213 | portion of the contents of the database; 214 | 215 | b. if You include all or a substantial portion of the database 216 | contents in a database in which You have Sui Generis Database 217 | Rights, then the database in which You have Sui Generis Database 218 | Rights (but not its individual contents) is Adapted Material; and 219 | 220 | c. You must comply with the conditions in Section 3(a) if You Share 221 | all or a substantial portion of the contents of the database. 222 | 223 | For the avoidance of doubt, this Section 4 supplements and does not 224 | replace Your obligations under this Public License where the Licensed 225 | Rights include other Copyright and Similar Rights. 226 | 227 | 228 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 229 | 230 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 231 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 232 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 233 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 234 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 235 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 236 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 237 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 238 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 239 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 240 | 241 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 242 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 243 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 244 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 245 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 246 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 247 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 248 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 249 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 250 | 251 | c. The disclaimer of warranties and limitation of liability provided 252 | above shall be interpreted in a manner that, to the extent 253 | possible, most closely approximates an absolute disclaimer and 254 | waiver of all liability. 255 | 256 | 257 | Section 6 -- Term and Termination. 258 | 259 | a. This Public License applies for the term of the Copyright and 260 | Similar Rights licensed here. However, if You fail to comply with 261 | this Public License, then Your rights under this Public License 262 | terminate automatically. 263 | 264 | b. Where Your right to use the Licensed Material has terminated under 265 | Section 6(a), it reinstates: 266 | 267 | 1. automatically as of the date the violation is cured, provided 268 | it is cured within 30 days of Your discovery of the 269 | violation; or 270 | 271 | 2. upon express reinstatement by the Licensor. 272 | 273 | For the avoidance of doubt, this Section 6(b) does not affect any 274 | right the Licensor may have to seek remedies for Your violations 275 | of this Public License. 276 | 277 | c. For the avoidance of doubt, the Licensor may also offer the 278 | Licensed Material under separate terms or conditions or stop 279 | distributing the Licensed Material at any time; however, doing so 280 | will not terminate this Public License. 281 | 282 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 283 | License. 284 | 285 | 286 | Section 7 -- Other Terms and Conditions. 287 | 288 | a. The Licensor shall not be bound by any additional or different 289 | terms or conditions communicated by You unless expressly agreed. 290 | 291 | b. Any arrangements, understandings, or agreements regarding the 292 | Licensed Material not stated herein are separate from and 293 | independent of the terms and conditions of this Public License. 294 | 295 | 296 | Section 8 -- Interpretation. 297 | 298 | a. For the avoidance of doubt, this Public License does not, and 299 | shall not be interpreted to, reduce, limit, restrict, or impose 300 | conditions on any use of the Licensed Material that could lawfully 301 | be made without permission under this Public License. 302 | 303 | b. To the extent possible, if any provision of this Public License is 304 | deemed unenforceable, it shall be automatically reformed to the 305 | minimum extent necessary to make it enforceable. If the provision 306 | cannot be reformed, it shall be severed from this Public License 307 | without affecting the enforceability of the remaining terms and 308 | conditions. 309 | 310 | c. No term or condition of this Public License will be waived and no 311 | failure to comply consented to unless expressly agreed to by the 312 | Licensor. 313 | 314 | d. Nothing in this Public License constitutes or may be interpreted 315 | as a limitation upon, or waiver of, any privileges and immunities 316 | that apply to the Licensor or You, including from the legal 317 | processes of any jurisdiction or authority. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mpw-js 2 | ====== 3 | 4 | mpw-js is a JavaScript (+ Web Crypto) implementation of the Master Password App ([Lyndir/MasterPassword](https://github.com/Lyndir/MasterPassword)) algorithm (). 5 | 6 | mpw-js relies on `window.crypto.subtle` when it is available on [modern browser](http://caniuse.com/#feat=cryptography) but will fallback to using [crypto-js](https://code.google.com/p/crypto-js/). It is written in ES6 JS but is transpiled to ES5 JS for browsers that do not yet support ES6 JS. It is purely intended as a proof-of-concept. 7 | 8 | The `MPW` constructor `constructor(name, password)` accepts two mandatory arguments; `.name` is set to the argument `name` and both `name` and `password` are subsequently passed to `calculateKey`, `.key` being set to the result. 9 | 10 | The `MPW` class implements `static calculateKey(name, password)` which is an implementation of step 1 of the algorithm, *Calculate the __master key__ from a user's name and master password*, it accepts two mandatory arguments, the users full name and the users master password; `calculateKey` is invoked automatically by the constructor. 11 | 12 | `MPW` also implements `calculateSeed(site, counter = 1, context = null, NS = MPW.NS)`, an implementation of step 2 of the algorithm, *Calculate the __template seed__ from the site's name and counter*, it accepts four arguments, the name of the site -- potentially it's FQDN -- and a counter -- *This is an integer that can be incremented when the user needs a new password for the site*. The latter two arguments are for extended functionality; `context` is used to provide context to generated security answers and `NS` is used to alter the use of seed, either `MPW.AuthenticationNS` for passwords, `MPW.IdentificationNS` for usernames or `MPW.RecoveryNS` for security answers. `calculateSeed` is invoked automatically by `generate`. 13 | 14 | `MPW` implements the generic `generate(site, counter = 1, context = null, template = "long", NS = MPW.NS)` which is an implementation of step 3 of the algorithm, *Encode a __site password__ using the site's type template*, it accepts five arguments, the name of the site -- potentially it's FQDN -- and a counter -- *This is an integer that can be incremented when the user needs a new password for the site.* -- which are passed to `calculateSeed`, `context` whose use is equal to `calculateSeed` (see above), `template` which refers to any of the 'Password Type Templates' supported by the algorithm -- maximum, long, medium, short, basic or pin, and `NS` whose use is equal to `calculateSeed` (see above). 15 | 16 | `MPW` also implements the specialised functions `generateAuthentication(site, counter = 1, context = "", template = "long")` for generating passwords, the purpose of the arguments of this are equal to those of `generate` (see above); `generateIdentification(site, counter = 1, context = "", template = "name")` for generating usernames, the purpose of the arguments of this are equal to those of `generate` (see above); and `generateRecovery(site, counter = 1, context = "", template = "phrase")` for generating usernames, the purpose of the arguments of this are equal to those of `generate` (see above). 17 | 18 | `MPW` also implements `invalidate()` which sets `.key` to a `Promise.reject`, preventing further access to the non-exportable key. 19 | 20 | `MPW` finally implements `static test()` which provides a simple, non-rigorous test to ensure correct functionality of the algorithm. 21 | 22 | Demo 23 | ---- 24 | 25 | A working demo can be found in the [gh-pages branch](https://github.com/tmthrgd/mpw-js/tree/gh-pages) and at . 26 | 27 | Dependencies 28 | ------------ 29 | 30 | npm install -g traceur 31 | 32 | License 33 | ------- 34 | 35 | This work is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license, visit or see [LICENSE](https://github.com/tmthrgd/mpw-js/blob/master/LICENSE). -------------------------------------------------------------------------------- /mpw.js: -------------------------------------------------------------------------------- 1 | /*! by Tom Thorogood */ 2 | /*! This work is licensed under the Creative Commons Attribution 4.0 3 | International License. To view a copy of this license, visit 4 | http://creativecommons.org/licenses/by/4.0/ or see LICENSE. */ 5 | 6 | // JS Web Crypto implementation of http://masterpasswordapp.com/algorithm.html 7 | class MPW { 8 | constructor(name, password, version = MPW.VERSION) { 9 | // The algorithm version 10 | this.version = version; 11 | 12 | // Store name on the object, this is not used at all internally 13 | this.name = name; 14 | 15 | // Check for valid algorithm versions 16 | if (version >= 0 && version <= MPW.VERSION) { 17 | // Calculate the master key which will be used to calculate 18 | // the password seed 19 | this.key = MPW.calculateKey(name, password, version); 20 | } else { 21 | this.key = Promise.reject(new Error(`Algorithm version ${version} not implemented`)); 22 | } 23 | } 24 | 25 | // calculateKey takes ~ 1450.000ms to complete 26 | static calculateKey(name, password, version = MPW.VERSION) { 27 | if (!name || !name.length) { 28 | return Promise.reject(new Error("Argument name not present")); 29 | } 30 | 31 | if (!password || !password.length) { 32 | return Promise.reject(new Error("Argument password not present")); 33 | } 34 | 35 | try { 36 | // Cache the number of characters in name for older buggy 37 | // versions of MPW 38 | let nameCharLength = name.length; 39 | 40 | // Convert password string to a Uint8Array w/ UTF-8 41 | password = MPW.txtencoder.encode(password); 42 | 43 | // Convert name string to a Uint8Array w/ UTF-8 44 | name = MPW.txtencoder.encode(name); 45 | 46 | // Convert MPW.NS string to a Uint8Array w/ UTF-8 47 | let NS = MPW.txtencoder.encode(MPW.NS); 48 | 49 | // Create salt array and a DataView representing it 50 | var salt = new Uint8Array( 51 | NS.length 52 | + 4/*sizeof(uint32)*/ + name.length 53 | ); 54 | let saltView = new DataView(salt.buffer, salt.byteOffset, salt.byteLength); 55 | let i = 0; 56 | 57 | // Set salt[0,] to NS 58 | salt.set(NS, i); i += NS.length; 59 | 60 | if (version < 3) { 61 | // Set data[i,i+4] to nameCharLength UINT32 in big-endian form 62 | saltView.setUint32(i, nameCharLength, false/*big-endian*/); i += 4/*sizeof(uint32)*/; 63 | } else { 64 | // Set salt[i,i+4] to name.length UINT32 in big-endian form 65 | saltView.setUint32(i, name.length, false/*big-endian*/); i += 4/*sizeof(uint32)*/; 66 | } 67 | 68 | // Set salt[i,] to name 69 | salt.set(name, i); i += name.length; 70 | } catch (e) { 71 | return Promise.reject(e); 72 | } 73 | 74 | // Derive the master key w/ scrypt 75 | // why is buflen 64*8==512 and not 32*8==256 ? 76 | let key = window.scrypt(password, salt, 32768/*= n*/, 8/*= r*/, 2/*= p*/, 64/*= buflen*/); 77 | 78 | // If the Web Crypto API is supported import the key, otherwise return 79 | return window.crypto.subtle 80 | ? key.then( 81 | // Import the key into WebCrypto to use later with sign while 82 | // being non-extractable 83 | key => window.crypto.subtle.importKey("raw", key, { 84 | name: "HMAC", 85 | hash: { 86 | name: "SHA-256" 87 | } 88 | }, false/*not extractable*/, [ "sign" ])/*= key*/ 89 | ) 90 | : key; 91 | } 92 | 93 | // calculateSeed takes ~ 3.000ms to complete + the time of calculateKey once 94 | calculateSeed(site, counter = 1, context = null, NS = MPW.NS) { 95 | if (!site) { 96 | return Promise.reject(new Error("Argument site not present")); 97 | } 98 | 99 | if (counter < 1 || counter > 4294967295/*Math.pow(2, 32) - 1*/) { 100 | return Promise.reject(new Error("Argument counter out of range")); 101 | } 102 | 103 | try { 104 | // Cache the number of characters in site for older buggy 105 | // versions of MPW 106 | let siteCharLength = site.length; 107 | 108 | // Convert salt string to a Uint8Array w/ UTF-8 109 | site = MPW.txtencoder.encode(site); 110 | 111 | // Convert NS string to a Uint8Array w/ UTF-8 112 | NS = MPW.txtencoder.encode(NS); 113 | 114 | if (context) { 115 | // Convert context string to a Uint8Array w/ UTF-8 116 | context = MPW.txtencoder.encode(context); 117 | } 118 | 119 | // Create data array and a DataView representing it 120 | var data = new Uint8Array( 121 | NS.length 122 | + 4/*sizeof(uint32)*/ + site.length 123 | + 4/*sizeof(int32)*/ 124 | + (context 125 | ? 4/*sizeof(uint32)*/ + context.length 126 | : 0) 127 | ); 128 | let dataView = new DataView(data.buffer, data.byteOffset, data.byteLength); 129 | let i = 0; 130 | 131 | // Set data[0,] to NS 132 | data.set(NS, i); i += NS.length; 133 | 134 | if (this.version < 2) { 135 | // Set data[i,i+4] to siteCharLength UINT32 in big-endian form 136 | dataView.setUint32(i, siteCharLength, false/*big-endian*/); i += 4/*sizeof(uint32)*/; 137 | } else { 138 | // Set data[i,i+4] to site.length UINT32 in big-endian form 139 | dataView.setUint32(i, site.length, false/*big-endian*/); i += 4/*sizeof(uint32)*/; 140 | } 141 | 142 | // Set data[i,] to site 143 | data.set(site, i); i += site.length; 144 | 145 | // Set data[i,i+4] to counter INT32 in big-endian form 146 | dataView.setInt32(i, counter, false/*big-endian*/); i += 4/*sizeof(int32)*/; 147 | 148 | if (context) { 149 | // Set data[i,i+4] to context.length UINT32 in big-endian form 150 | dataView.setUint32(i, context.length, false/*big-endian*/); i += 4/*sizeof(uint32)*/; 151 | 152 | // Set data[i,] to context 153 | data.set(context, i); i += context.length; 154 | } 155 | } catch (e) { 156 | return Promise.reject(e); 157 | } 158 | 159 | // If the Web Crypto API is supported use it, otherwise rely on crypto-js 160 | if (window.crypto.subtle) { 161 | return this.key.then( 162 | // Sign data using HMAC-SHA-256 w/ this.key 163 | key => window.crypto.subtle.sign({ 164 | name: "HMAC", 165 | hash: { 166 | name: "SHA-256" 167 | } 168 | }, key, data)/*= seed*/ 169 | ).then( 170 | // Convert the seed to Uint8Array from ArrayBuffer 171 | seed => new Uint8Array(seed)/*= seed*/ 172 | ); 173 | } else { 174 | return this.key.then(function (key) { 175 | // Create crypto-js WordArrays from Uint8Arrays data and key 176 | data = CryptoJS.lib.WordArray.create(data); 177 | key = CryptoJS.lib.WordArray.create(key); 178 | 179 | // Sign data using HMAC-SHA-256 w/ key 180 | return CryptoJS.HmacSHA256(data, key)/*= seed*/; 181 | }).then(function (hash) { 182 | // Create seed array and a DataView representing it 183 | let seed = new Uint8Array(hash.words.length * 4/*sizeof(int32)*/); 184 | let seedView = new DataView(seed.buffer, seed.byteOffset, seed.byteLength); 185 | 186 | // Loop over hash.words which are INT32 187 | for (let i = 0; i < hash.words.length; i++) { 188 | // Set seed[i*4,i*4+4] to hash.words[i] INT32 in big-endian form 189 | seedView.setInt32(i * 4/*sizeof(int32)*/, hash.words[i], false/*big-endian*/); 190 | } 191 | 192 | // Return the seed Uint8Array 193 | return seed; 194 | }); 195 | } 196 | } 197 | 198 | // generate takes ~ 0.200ms to complete + the time of calculateSeed 199 | generate(site, counter = 1, context = null, template = "long", NS = MPW.NS) { 200 | // Does the requested template exist? 201 | if (!(template in MPW.templates)) { 202 | return Promise.reject(new Error("Argument template invalid")); 203 | } 204 | 205 | // Calculate the seed 206 | let seed = this.calculateSeed(site, counter, context, NS); 207 | 208 | if (this.version < 1) { 209 | // Convert seed from host byte order to network byte 210 | // to be compatible with v0 of MPW 211 | // Follows the implementation at https://github.com/... 212 | // Lyndir/MasterPassword/blob/master/MasterPassword/... 213 | // Java/masterpassword-algorithm/src/main/java/com/... 214 | // lyndir/masterpassword/MasterKeyV0.java#L105 215 | seed = seed.then(function (seedBytes) { 216 | let seed = new Uint16Array(seedBytes.length); 217 | 218 | for (let i = 0; i < seed.length; i++) { 219 | seed[i] = (seedBytes[i] > 127 ? 0x00ff : 0x0000) | (seedBytes[i] << 8); 220 | } 221 | 222 | return seed; 223 | }); 224 | } 225 | 226 | return seed.then(function (seed) { 227 | // Find the selected template array 228 | template = MPW.templates[template]; 229 | 230 | // Select the specific template based on seed[0] 231 | template = template[seed[0] % template.length]; 232 | 233 | // Split the template string (e.g. xxx...xxx) 234 | return template.split("").map(function (c, i) { 235 | // Use MPW.passchars to map the template string (e.g. xxx...xxx) 236 | // to characters (e.g. c -> bcdfghjklmnpqrstvwxyz) 237 | let chars = MPW.passchars[c]; 238 | 239 | // Select the character using seed[i + 1] 240 | return chars[seed[i + 1] % chars.length]; 241 | }).join(""); 242 | })/*= password*/; 243 | } 244 | 245 | // generate a password with the password namespace 246 | generateAuthentication(site, counter = 1, context = "", template = "long") { 247 | return this.generate(site, counter, context, template, MPW.AuthenticationNS); 248 | } 249 | 250 | // generate a username with the login namespace 251 | generateIdentification(site, counter = 1, context = "", template = "name") { 252 | return this.generate(site, counter, context, template, MPW.IdentificationNS); 253 | } 254 | 255 | // generate a security answer with the answer namespace 256 | generateRecovery(site, counter = 1, context = "", template = "phrase") { 257 | return this.generate(site, counter, context, template, MPW.RecoveryNS); 258 | } 259 | 260 | // generate a password with the password namespace 261 | // 262 | // Deprecated: use generateAuthentication instead. 263 | generatePassword(site, counter = 1, template = "long") { 264 | return this.generate(site, counter, null, template, MPW.PasswordNS); 265 | } 266 | 267 | // generate a username with the login namespace 268 | // 269 | // Deprecated: use generateIdentification instead. 270 | generateLogin(site, counter = 1, template = "name") { 271 | return this.generate(site, counter, null, template, MPW.LoginNS); 272 | } 273 | 274 | // generate a security answer with the answer namespace 275 | // 276 | // Deprecated: use generateRecovery instead. 277 | generateAnswer(site, counter = 1, context = "", template = "phrase") { 278 | return this.generate(site, counter, context, template, MPW.AnswerNS); 279 | } 280 | 281 | invalidate() { 282 | // Replace this.key w/ a Promise.reject 283 | // Preventing all future access 284 | this.key = Promise.reject(new Error("invalid state")); 285 | } 286 | 287 | static test() { 288 | // Pretty simple test here 289 | return new MPW("user", "password").generate("example.com", 1, null, "long", MPW.NS).then(function (password) { 290 | console.assert(password === "ZedaFaxcZaso9*", `Self-test failed; expected: ZedaFaxcZaso9*; got: ${password}`); 291 | return password === "ZedaFaxcZaso9*" 292 | ? Promise.resolve() 293 | : Promise.reject(new Error(`Self-test failed; expected: ZedaFaxcZaso9*; got: ${password}`)); 294 | }); 295 | } 296 | } 297 | 298 | // A TextEncoder in UTF-8 to convert strings to `Uint8Array`s 299 | MPW.txtencoder = new TextEncoder; 300 | 301 | // The latest version of MPW supported 302 | MPW.VERSION = 3; 303 | 304 | // The namespace used in calculateKey 305 | MPW.NS = "com.lyndir.masterpassword"; 306 | 307 | // The namespaces used in calculateSeed 308 | MPW.AuthenticationNS = "com.lyndir.masterpassword"; 309 | MPW.IdentificationNS = "com.lyndir.masterpassword.login"; 310 | MPW.RecoveryNS = "com.lyndir.masterpassword.answer"; 311 | 312 | // Legacy namespaces used in calculateSeed 313 | MPW.PasswordNS = MPW.AuthenticationNS; 314 | MPW.LoginNS = MPW.IdentificationNS; 315 | MPW.AnswerNS = MPW.RecoveryNS; 316 | 317 | // The templates that passwords may be created from 318 | // The characters map to MPW.passchars 319 | MPW.templates = { 320 | maximum: [ 321 | "anoxxxxxxxxxxxxxxxxx", 322 | "axxxxxxxxxxxxxxxxxno" 323 | ], 324 | long: [ 325 | "CvcvnoCvcvCvcv", 326 | "CvcvCvcvnoCvcv", 327 | "CvcvCvcvCvcvno", 328 | "CvccnoCvcvCvcv", 329 | "CvccCvcvnoCvcv", 330 | "CvccCvcvCvcvno", 331 | "CvcvnoCvccCvcv", 332 | "CvcvCvccnoCvcv", 333 | "CvcvCvccCvcvno", 334 | "CvcvnoCvcvCvcc", 335 | "CvcvCvcvnoCvcc", 336 | "CvcvCvcvCvccno", 337 | "CvccnoCvccCvcv", 338 | "CvccCvccnoCvcv", 339 | "CvccCvccCvcvno", 340 | "CvcvnoCvccCvcc", 341 | "CvcvCvccnoCvcc", 342 | "CvcvCvccCvccno", 343 | "CvccnoCvcvCvcc", 344 | "CvccCvcvnoCvcc", 345 | "CvccCvcvCvccno" 346 | ], 347 | medium: [ 348 | "CvcnoCvc", 349 | "CvcCvcno" 350 | ], 351 | basic: [ 352 | "aaanaaan", 353 | "aannaaan", 354 | "aaannaaa" 355 | ], 356 | short: [ 357 | "Cvcn" 358 | ], 359 | pin: [ 360 | "nnnn" 361 | ], 362 | name: [ 363 | "cvccvcvcv" 364 | ], 365 | phrase: [ 366 | "cvcc cvc cvccvcv cvc", 367 | "cvc cvccvcvcv cvcv", 368 | "cv cvccv cvc cvcvccv" 369 | ] 370 | }; 371 | 372 | // The password character mapping 373 | // c in template becomes bcdfghjklmnpqrstvwxyz 374 | MPW.passchars = { 375 | V: "AEIOU", 376 | C: "BCDFGHJKLMNPQRSTVWXYZ", 377 | v: "aeiou", 378 | c: "bcdfghjklmnpqrstvwxyz", 379 | A: "AEIOUBCDFGHJKLMNPQRSTVWXYZ", 380 | a: "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", 381 | n: "0123456789", 382 | o: "@&%?,=[]_:-+*$#!'^~;()/.", 383 | x: "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()", 384 | " ": " " 385 | }; 386 | -------------------------------------------------------------------------------- /pbkdf2.js: -------------------------------------------------------------------------------- 1 | /*! by Tom Thorogood */ 2 | /*! This work is licensed under the Creative Commons Attribution 4.0 3 | International License. To view a copy of this license, visit 4 | http://creativecommons.org/licenses/by/4.0/ or see LICENSE. */ 5 | 6 | // https://bugzilla.mozilla.org/show_bug.cgi?id=554827 7 | window.pbkdf2 = function () { 8 | // https://github.com/golang/crypto/blob/master/pbkdf2/pbkdf2.go 9 | function pbkdf2_js(password, salt, iter, keyLen, hash) { 10 | switch ((hash.name || hash).toUpperCase()) { 11 | case "SHA1": 12 | var hashLen = 160 / 8; 13 | break; 14 | case "SHA224": 15 | case "SHA-224": 16 | var hashLen = 224 / 8; 17 | break; 18 | case "SHA256": 19 | case "SHA-256": 20 | var hashLen = 256 / 8; 21 | break; 22 | case "SHA384": 23 | case "SHA-384": 24 | var hashLen = 384 / 8; 25 | break; 26 | case "SHA512": 27 | case "SHA-512": 28 | var hashLen = 512 / 8; 29 | break; 30 | default: 31 | let err = new Error("A parameter or an operation is not supported by the underlying object"); 32 | err.name = "InvalidAccessError"; 33 | return Promise.reject(err); 34 | } 35 | 36 | let numBlocks = ((keyLen + hashLen - 1) / hashLen) | 0; 37 | 38 | let data = new Uint8Array(salt.length + 4/*sizeof(uint32)*/); 39 | let dataView = new DataView(data.buffer, data.byteOffset, data.byteLength); 40 | 41 | data.set(salt); 42 | 43 | return window.crypto.subtle.importKey("raw", password, { 44 | name: "HMAC", 45 | hash: hash 46 | }, false/*not extractable*/, [ "sign" ]).then(function (key) { 47 | let dk = new Uint8Array(numBlocks * hashLen); 48 | let x = Promise.resolve(); 49 | 50 | for (let block = 1, dki = 0; block <= numBlocks; block++, dki += hashLen) { 51 | x = x.then(() => dataView.setUint32(salt.length, block, false/*big-endian*/)) 52 | .then(() => window.crypto.subtle.sign({ 53 | name: "HMAC", 54 | hash: hash 55 | }, key, data)) 56 | .then(pdk => (dk.set(new Uint8Array(pdk), dki), pdk)); 57 | 58 | for (let n = 2; n <= iter; n++) { 59 | x = x.then(U => window.crypto.subtle.sign({ 60 | name: "HMAC", 61 | hash: hash 62 | }, key, U)).then(function (U) { 63 | let Ux = new Uint8Array(U); 64 | 65 | for (let i = 0; i < Ux.length; i++) { 66 | dk[dki + i] ^= Ux[i]; 67 | } 68 | 69 | return U; 70 | }); 71 | } 72 | } 73 | 74 | return x.then(() => dk.subarray(0, keyLen)); 75 | }); 76 | } 77 | 78 | if (window.crypto.subtle) { 79 | return function (password, salt, iter, keyLen, hash) { 80 | let self = this; 81 | let args = arguments; 82 | 83 | return window.crypto.subtle.importKey("raw", password, { 84 | name: "PBKDF2", 85 | hash: hash 86 | }, false/*not extractable*/, [ "deriveBits" ]) 87 | .then(key => window.crypto.subtle.deriveBits({ 88 | name: "PBKDF2", 89 | salt: salt, 90 | iterations: iter, 91 | hash: hash 92 | }, key, keyLen * 8)) 93 | .then(key => new Uint8Array(key)) 94 | .catch(err => 95 | // PBKDF2-HMAC is not supported by the Web Crytpto API if either a 96 | // NotSupportedError or a OperationError are emmited 97 | (err.name === "OperationError" || err.name === "NotSupportedError") 98 | ? (window.pbkdf2 = pbkdf2_js).apply(self, args) 99 | // Limited support for PBKDF2-HMAC-SHA exists if InvalidAccessError 100 | // is emmited for PBKDF2-HMAC-SHA{256,384,512} 101 | : (err.name === "InvalidAccessError") 102 | ? pbkdf2_js.apply(self, args) 103 | : Promise.reject(err)); 104 | }; 105 | } else { 106 | return function (password, salt, iter, keyLen, hash) { 107 | switch ((hash.name || hash).toUpperCase()) { 108 | case "SHA1": 109 | var hashAlg = CryptoJS.algo.SHA; 110 | break; 111 | case "SHA224": 112 | case "SHA-224": 113 | var hashAlg = CryptoJS.algo.SHA224; 114 | break; 115 | case "SHA256": 116 | case "SHA-256": 117 | var hashAlg = CryptoJS.algo.SHA256; 118 | break; 119 | case "SHA384": 120 | case "SHA-384": 121 | var hashAlg = CryptoJS.algo.SHA384; 122 | break; 123 | case "SHA512": 124 | case "SHA-512": 125 | var hashAlg = CryptoJS.algo.SHA512; 126 | break; 127 | default: 128 | let err = new Error("A parameter or an operation is not supported by the underlying object"); 129 | err.name = "InvalidAccessError"; 130 | return Promise.reject(err); 131 | } 132 | 133 | return new Promise(function (resolve, reject) { 134 | // setImmediate (a 0-delay setTimeout of sorts) is needed 135 | // here so that this code is asynchronous and will not block 136 | // the UI thread 137 | window.setImmediate(function () { 138 | // Create crypto-js WordArrays from Uint8Arrays password and salt 139 | password = CryptoJS.lib.WordArray.create(password); 140 | salt = CryptoJS.lib.WordArray.create(salt); 141 | 142 | // Derive key using PBKDF2 w/ password and salt 143 | let Ckey = CryptoJS.PBKDF2(password, salt, { 144 | keySize: keyLen*8/32, 145 | iterations: iter, 146 | hasher: hashAlg 147 | }); 148 | 149 | // Create key array and a DataView representing it 150 | let key = new Uint8Array(Ckey.words.length * 4/*sizeof(int32)*/); 151 | let keyView = new DataView(key.buffer, key.byteOffset, key.byteLength); 152 | 153 | // Loop over Ckey.words which are INT32 154 | for (let i = 0; i < Ckey.words.length; i++) { 155 | // Set key[i*4,i*4+4] to Ckey.words[i] INT32 in big-endian form 156 | keyView.setInt32(i * 4/*sizeof(int32)*/, Ckey.words[i], false/*big-endian*/); 157 | } 158 | 159 | // Return the key Uint8Array 160 | resolve(key); 161 | }); 162 | }); 163 | }; 164 | } 165 | }(); -------------------------------------------------------------------------------- /scrypt.js: -------------------------------------------------------------------------------- 1 | /*! by Tom Thorogood */ 2 | /*! This work is licensed under the Creative Commons Attribution 4.0 3 | International License. To view a copy of this license, visit 4 | http://creativecommons.org/licenses/by/4.0/ or see LICENSE. */ 5 | 6 | window.scrypt = function () { 7 | // https://github.com/golang/crypto/blob/master/scrypt/scrypt.go 8 | function salsaXOR(tmp, inp, out) { 9 | let w = [ ]; 10 | let x = [ ]; 11 | 12 | for (let i = 0; i < 16; i++) { 13 | x[i] = w[i] = tmp[i] ^ inp[i]; 14 | } 15 | 16 | for (let i = 0, u; i < 8; i += 2) { 17 | u = x[0] + x[12]; x[4] ^= (u << 7) | (u >>> (32 - 7)); 18 | u = x[4] + x[0]; x[8] ^= (u << 9) | (u >>> (32 - 9)); 19 | u = x[8] + x[4]; x[12] ^= (u << 13) | (u >>> (32 - 13)); 20 | u = x[12] + x[8]; x[0] ^= (u << 18) | (u >>> (32 - 18)); 21 | 22 | u = x[5] + x[1]; x[9] ^= (u << 7) | (u >>> (32 - 7)); 23 | u = x[9] + x[5]; x[13] ^= (u << 9) | (u >>> (32 - 9)); 24 | u = x[13] + x[9]; x[1] ^= (u << 13) | (u >>> (32 - 13)); 25 | u = x[1] + x[13]; x[5] ^= (u << 18) | (u >>> (32 - 18)); 26 | 27 | u = x[10] + x[6]; x[14] ^= (u << 7) | (u >>> (32 - 7)); 28 | u = x[14] + x[10]; x[2] ^= (u << 9) | (u >>> (32 - 9)); 29 | u = x[2] + x[14]; x[6] ^= (u << 13) | (u >>> (32 - 13)); 30 | u = x[6] + x[2]; x[10] ^= (u << 18) | (u >>> (32 - 18)); 31 | 32 | u = x[15] + x[11]; x[3] ^= (u << 7) | (u >>> (32 - 7)); 33 | u = x[3] + x[15]; x[7] ^= (u << 9) | (u >>> (32 - 9)); 34 | u = x[7] + x[3]; x[11] ^= (u << 13) | (u >>> (32 - 13)); 35 | u = x[11] + x[7]; x[15] ^= (u << 18) | (u >>> (32 - 18)); 36 | 37 | u = x[0] + x[3]; x[1] ^= (u << 7) | (u >>> (32 - 7)); 38 | u = x[1] + x[0]; x[2] ^= (u << 9) | (u >>> (32 - 9)); 39 | u = x[2] + x[1]; x[3] ^= (u << 13) | (u >>> (32 - 13)); 40 | u = x[3] + x[2]; x[0] ^= (u << 18) | (u >>> (32 - 18)); 41 | 42 | u = x[5] + x[4]; x[6] ^= (u << 7) | (u >>> (32 - 7)); 43 | u = x[6] + x[5]; x[7] ^= (u << 9) | (u >>> (32 - 9)); 44 | u = x[7] + x[6]; x[4] ^= (u << 13) | (u >>> (32 - 13)); 45 | u = x[4] + x[7]; x[5] ^= (u << 18) | (u >>> (32 - 18)); 46 | 47 | u = x[10] + x[9]; x[11] ^= (u << 7) | (u >>> (32 - 7)); 48 | u = x[11] + x[10]; x[8] ^= (u << 9) | (u >>> (32 - 9)); 49 | u = x[8] + x[11]; x[9] ^= (u << 13) | (u >>> (32 - 13)); 50 | u = x[9] + x[8]; x[10] ^= (u << 18) | (u >>> (32 - 18)); 51 | 52 | u = x[15] + x[14]; x[12] ^= (u << 7) | (u >>> (32 - 7)); 53 | u = x[12] + x[15]; x[13] ^= (u << 9) | (u >>> (32 - 9)); 54 | u = x[13] + x[12]; x[14] ^= (u << 13) | (u >>> (32 - 13)); 55 | u = x[14] + x[13]; x[15] ^= (u << 18) | (u >>> (32 - 18)); 56 | } 57 | 58 | for (let i = 0; i < 16; i++) { 59 | out[i] = tmp[i] = x[i] + w[i]; 60 | } 61 | } 62 | 63 | function blockMix(inp, out, r) { 64 | let tmp = inp.slice((2 * r - 1) * 16, (2 * r) * 16); 65 | 66 | for (let i = 0; i < 2 * r; i += 2) { 67 | salsaXOR(tmp, inp.subarray(i * 16), out.subarray(i * 8)); 68 | salsaXOR(tmp, inp.subarray((i + 1) * 16), out.subarray((i + 2 * r) * 8)); 69 | } 70 | } 71 | 72 | function smix(b, r, N, v, x, y) { 73 | let bView = new DataView(b.buffer, b.byteOffset, b.byteLength); 74 | 75 | for (let i = 0, j = 0; i < x.length; i++, j += 4) { 76 | x[i] = bView.getUint32(j, true/*little-endian*/); 77 | } 78 | 79 | for (let i = 0; i < N; i += 2) { 80 | v.set(x, i * 32 * r); 81 | blockMix(x, y, r); 82 | 83 | v.set(y, (i + 1) * 32 * r); 84 | blockMix(y, x, r); 85 | } 86 | 87 | for (let i = 0, j, sh32 = Math.pow(2, 32); i < N; i += 2) { 88 | j = (x[(2 * r - 1) * 16] | (x[(2 * r - 1) * 16 + 1] * sh32)) & (N - 1); 89 | 90 | for (let k = 0; k < x.length; k++) { 91 | x[k] ^= v[j * 32 * r + k]; 92 | } 93 | 94 | blockMix(x, y, r); 95 | 96 | j = (y[(2 * r - 1) * 16] | (y[(2 * r - 1) * 16 + 1] * sh32)) & (N - 1); 97 | 98 | for (let k = 0; k < y.length; k++) { 99 | y[k] ^= v[j * 32 * r + k]; 100 | } 101 | 102 | blockMix(y, x, r); 103 | } 104 | 105 | for (let i = 0, j = 0; i < x.length; i++, j += 4) { 106 | bView.setUint32(j, x[i], true/*little-endian*/); 107 | } 108 | } 109 | 110 | // This is the scrypt function 111 | // It returns a promise which will resolve asynchronously 112 | return function (passphrase, salt, N, r, p, keyLen) { 113 | // Check that p and r are not too large 114 | if (r * p >= Math.pow(2, 30)) { 115 | return Promise.reject(new Error("Parameters r and p are too large")); 116 | } 117 | 118 | // Check that N is a power of 2 greater than 1 and that we can safely work on it in JS 119 | if (N < 2 || N & (N - 1) != 0 || N > Number.MAX_SAFE_INTEGER) { 120 | return Promise.reject(new Error("Argument N is invalid; N must be > 1, a power of 2 and less than 2^53")); 121 | } 122 | 123 | let x = new Uint32Array(32 * r); 124 | let y = new Uint32Array(32 * r); 125 | let v = new Uint32Array(32 * N * r); 126 | 127 | let b = pbkdf2(passphrase, salt, 1, p * 128 * r, "SHA-256"); 128 | 129 | for (let i = 0; i < p; i++) { 130 | // setImmediate (a 0-delay setTimeout of sorts) is needed 131 | // here so that this code is asynchronous and will not block 132 | // the UI thread 133 | b = b.then(b => new Promise((resolve, reject) => window.setImmediate(() => (smix(b.subarray(i * 128 * r), r, N, v, x, y), resolve(b))))); 134 | } 135 | 136 | return b.then(b => pbkdf2(passphrase, b, 1, keyLen, "SHA-256")); 137 | }; 138 | }(); --------------------------------------------------------------------------------