├── LICENSE ├── README.md ├── aes.js ├── aes2.js ├── aes3.js ├── aes3.min.js ├── encryption.js ├── encryption.min.js └── passwords.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mike Fuller 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 | # Web-Cryptography 2 | Web Cryptography Examples using the crypto.subtle API (SubtleCrypto) aka window.crypto.subtle 3 | 4 | ## Formula for encrypted communication with advanced extraterrestrial lifeforms (Aliens) familar with Elliptic Curve Diffie-Hellman and JavaScript 5 | ```javascript 6 | 7 | // The starship will generate an Elliptic Curve Diffie-Hellman keypair 8 | var starship = await crypto.subtle.generateKey({ 9 | "name": "ECDH", 10 | "namedCurve": "P-256" 11 | }, true, ['deriveBits']); 12 | 13 | // The alienship will generate an Elliptic Curve Diffie-Hellman keypair 14 | var alienship = await crypto.subtle.generateKey({ 15 | "name": "ECDH", 16 | "namedCurve": "P-256" 17 | }, true, ['deriveBits']); 18 | 19 | // alienship sends alienship.publicKey to starship 20 | // starship sends starship.publicKey to alienship 21 | // TIP: You can paint your public ECDH x and y coordinates on your vessel for all to see. 22 | 23 | // sharedBits - Both ships can now compute the shared bits. 24 | // The ship's private key is used as the "key", the other ship's public key is used as "public". 25 | var sharedBits = await crypto.subtle.deriveBits({ 26 | "name": "ECDH", 27 | "public": alienship.publicKey 28 | }, starship.privateKey, 256); 29 | 30 | // The first half of the resulting raw bits is used as a salt. 31 | var sharedDS = sharedBits.slice(0, 16); 32 | 33 | // The second half of the resulting raw bits is imported as a shared derivation key. 34 | var sharedDK = await crypto.subtle.importKey('raw', sharedBits.slice(16, 32), "PBKDF2", false, ['deriveKey']); 35 | 36 | // A new shared AES-GCM encryption / decryption key is generated using PBKDF2 37 | // This is computed separately by both parties and the result is always the same. 38 | var key = await crypto.subtle.deriveKey({ 39 | "name": "PBKDF2", 40 | "salt": sharedDS, 41 | "iterations": 100000, 42 | "hash": "SHA-256" 43 | }, sharedDK, { 44 | "name": "AES-GCM", 45 | "length": 256 46 | }, true, ['encrypt', 'decrypt']); 47 | 48 | // The raw bits of the actual encryption key can be exported and saved in the ship's computer. 49 | // These bits should be stored encrypted and should reference the specfic ship you are communicating with. 50 | var exported = await crypto.subtle.exportKey('raw', key); 51 | 52 | // The alienship can construct a message and encode it. 53 | var message = new TextEncoder().encode('TO SERVE MAN...'); 54 | 55 | // A random iv can be generated and used for encryption 56 | var iv = crypto.getRandomValues(new Uint8Array(12)); 57 | 58 | // The iv and the message are used to create an encrypted series of bits. 59 | var encrypted = await crypto.subtle.encrypt({ 60 | "name": "AES-GCM", 61 | "iv": iv 62 | }, key, message); 63 | 64 | // The alienship sends the bits and the iv to the starship 65 | 66 | // The starship decrypts the message using the shared key and publicly provided iv. 67 | var decrypted = await crypto.subtle.decrypt({ 68 | "name": "AES-GCM", 69 | "iv": iv 70 | }, key, encrypted); 71 | 72 | // The humans decode the message into human readable text... 73 | var decoded = new TextDecoder().decode(decrypted); 74 | 75 | // The humans output the message to the console and gasp! 76 | console.log(decoded); 77 | 78 | 79 | ``` 80 | 81 | 82 | ## AES-GCM encryption / decryption with PBKDF2 key derivation 83 | 84 | ### What does this do? 85 | This function creates a JavaScipt object containing an AES encrypt function and an AES decrypt function built using the browser's built-in Web Crypto library. For security, the encryption key is derived from the password and a random salt using the PBKDF2 algorithm. 86 | 87 | #### Encryption 88 | 89 | The encrypt function encodes a byteArray from a provided password and imports it as a PBKDF2 cryptoKey. Optionally, the password can be left null and a byteArray can be provided as the passwordBits. The imported cryptoKey is used with a randomly generated salt in a PBKDF2 function to derive new bits. If an iterations value is not provided, a default value of 500000 is used. The resulting bits (resulting byteArray) is imported as an AES-256 cryptoKey. The cryptoKey is used with a randomly generated initialization vector (iv) to encrypt the provided message (string data). 90 | 91 | The value returned from this function is a string of the these concatenated values separated by periods: 92 | - the iterations value converted to a string and then base64 encoded 93 | - the salt converted from a byteArray to a base64 encoded string 94 | - the iv converted from a byteArray to a base64 encoded string 95 | - the encrypted message converted from a byteArray to a base64 encoded string 96 | 97 | #### Decryption 98 | 99 | The decrypt function follows essentially the same process as the encrypt function in reverse. The encrypted data, encoded as a string from the encrypt function, is provided with the password (or passwordBits) used for encryption. The encoded values are split, decoded and then used to derive the encryption key. The encryption key is then used to decrypt the data. 100 | 101 | The value returned from this function is a string of the original message (string data). 102 | 103 | ### The Function 104 | ```javascript 105 | 106 | function AES() { 107 | 108 | let aes = {}; 109 | 110 | aes.encrypt = async (message, password, passwordBits, iterations) => { 111 | 112 | let rounds = iterations || 500000; 113 | let msg = new TextEncoder().encode(message); 114 | let pass; 115 | 116 | if (password) { 117 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 118 | "name": "PBKDF2" 119 | }, false, ['deriveBits']); 120 | } 121 | 122 | if (passwordBits) { 123 | pass = await crypto.subtle.importKey('raw',new Uint8Array(passwordBits),{ 124 | "name": "PBKDF2" 125 | },false,['deriveBits']) 126 | } 127 | 128 | let salt = crypto.getRandomValues(new Uint8Array(32)); 129 | let iv = crypto.getRandomValues(new Uint8Array(12)); 130 | 131 | let bits = await crypto.subtle.deriveBits({ 132 | "name": "PBKDF2", 133 | "salt": salt, 134 | "iterations": rounds, 135 | "hash": { 136 | "name": "SHA-256" 137 | } 138 | }, pass, 256); 139 | 140 | let key = await crypto.subtle.importKey('raw', bits, { 141 | "name": "AES-GCM" 142 | }, false, ['encrypt']); 143 | 144 | let enc = await crypto.subtle.encrypt({ 145 | "name": "AES-GCM", 146 | "iv": iv 147 | }, key, msg); 148 | 149 | let iterationsHash = btoa(rounds.toString()); 150 | 151 | let saltHash = btoa(Array.from(new Uint8Array(salt)).map(val => { 152 | return String.fromCharCode(val) 153 | }).join('')); 154 | 155 | let ivHash = btoa(Array.from(new Uint8Array(iv)).map(val => { 156 | return String.fromCharCode(val) 157 | }).join('')); 158 | 159 | let encHash = btoa(Array.from(new Uint8Array(enc)).map(val => { 160 | return String.fromCharCode(val) 161 | }).join('')); 162 | 163 | return iterationsHash + '.' + saltHash + '.' + ivHash + '.' + encHash; 164 | 165 | }; 166 | 167 | aes.decrypt = async (encrypted, password, passwordBits) => { 168 | 169 | let parts = encrypted.split('.'); 170 | let rounds = parseInt(atob(parts[0])); 171 | 172 | let salt = new Uint8Array(atob(parts[1]).split('').map(val => { 173 | return val.charCodeAt(0); 174 | })); 175 | 176 | let iv = new Uint8Array(atob(parts[2]).split('').map(val => { 177 | return val.charCodeAt(0); 178 | })); 179 | 180 | let enc = new Uint8Array(atob(parts[3]).split('').map(val => { 181 | return val.charCodeAt(0); 182 | })); 183 | 184 | let pass; 185 | 186 | if (password) { 187 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 188 | "name": "PBKDF2" 189 | }, false, ['deriveBits']); 190 | } 191 | 192 | if (passwordBits) { 193 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 194 | "name": "PBKDF2" 195 | }, false, ['deriveBits']); 196 | } 197 | 198 | let bits = await crypto.subtle.deriveBits({ 199 | "name": "PBKDF2", 200 | "salt": salt, 201 | "iterations": rounds, 202 | "hash": { 203 | "name": "SHA-256" 204 | } 205 | }, pass, 256); 206 | 207 | let key = await crypto.subtle.importKey('raw', bits, { 208 | "name": "AES-GCM" 209 | }, false, ['decrypt']); 210 | 211 | let dec = await crypto.subtle.decrypt({ 212 | "name": "AES-GCM", 213 | "iv": iv 214 | }, key, enc); 215 | 216 | return (new TextDecoder().decode(dec)); 217 | 218 | }; 219 | 220 | return aes; 221 | 222 | } 223 | 224 | ``` 225 | 226 | ### Examples 227 | 228 | 229 | #### Encrypt / Decrypt with a password 230 | ```javascript 231 | 232 | let message = "Hello world"; 233 | let password = "password"; 234 | 235 | let encrypted = await AES().encrypt(message,password); 236 | let decrypted = await AES().decrypt(encrypted,password); 237 | 238 | console.log(encrypted); 239 | console.log(decrypted); 240 | 241 | // "MTAwMDAw./Q0Kbaebl4eaTB9YiQLTH64s9g6N3R84zkohvq6S3Ao=.uOA4INOHbqmlVGRi.03GJ+KxFEEYV5jSkPmCByZf5mqjr8y8SzvJC" 242 | // "hello world" 243 | 244 | ``` 245 | 246 | 247 | #### Encrypt / Decrypt with a byteArray derived elsewhere 248 | ```javascript 249 | 250 | let message = "Hello world"; 251 | let passwordBits = crypto.getRandomValues(new Uint8Array(32)); 252 | 253 | let encrypted = await AES().encrypt(message,null,passwordBits); 254 | let decrypted = await AES().decrypt(encrypted,null,passwordBits); 255 | 256 | console.log(encrypted); 257 | console.log(decrypted); 258 | 259 | // "NTAwMDAw.zAySc5+w1eziSEWkYehc7D/OSE/YTiI3Lvq07axvZgQ=.D3amG1ThKfxTI8ss.zKoyTs4pYgqnpE879Nus9l24foFTk0yaoOjh" 260 | // "hello world" 261 | 262 | ``` 263 | 264 | ### HMAC Sign / Verify 265 | See the aes2.js file for a similar function that utilzes HMAC for signing / verifying for integrity. 266 | 267 | ### Base64url encoding 268 | See the aes3.js file for a similar function that utilzes HMAC for signing / verifying and outputs the parts encoded for base64url. 269 | 270 | ## Additional Examples 271 | More to come... 272 | 273 | ## TODO 274 | + DONE! see aes3.js ~~Create example that further encodes the the base64 output to base64url~~ 275 | -------------------------------------------------------------------------------- /aes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function AES() { 4 | 5 | let aes = {}; 6 | 7 | aes.encrypt = async (message, password, passwordBits, iterations) => { 8 | let rounds = iterations || 500000; 9 | let msg = new TextEncoder().encode(message); 10 | let pass; 11 | if (password) { 12 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 13 | "name": "PBKDF2" 14 | }, false, ['deriveBits']); 15 | } 16 | if (passwordBits) { 17 | pass = await crypto.subtle.importKey('raw',new Uint8Array(passwordBits),{ 18 | "name": "PBKDF2" 19 | },false,['deriveBits']) 20 | } 21 | let salt = crypto.getRandomValues(new Uint8Array(32)); 22 | let iv = crypto.getRandomValues(new Uint8Array(12)); 23 | let bits = await crypto.subtle.deriveBits({ 24 | "name": "PBKDF2", 25 | "salt": salt, 26 | "iterations": rounds, 27 | "hash": { 28 | "name": "SHA-256" 29 | } 30 | }, pass, 256); 31 | let key = await crypto.subtle.importKey('raw', bits, { 32 | "name": "AES-GCM" 33 | }, false, ['encrypt']); 34 | let enc = await crypto.subtle.encrypt({ 35 | "name": "AES-GCM", 36 | "iv": iv 37 | }, key, msg); 38 | let iterationsHash = btoa(rounds.toString()); 39 | let saltHash = btoa(Array.from(new Uint8Array(salt)).map(val => { 40 | return String.fromCharCode(val) 41 | }).join('')); 42 | let ivHash = btoa(Array.from(new Uint8Array(iv)).map(val => { 43 | return String.fromCharCode(val) 44 | }).join('')); 45 | let encHash = btoa(Array.from(new Uint8Array(enc)).map(val => { 46 | return String.fromCharCode(val) 47 | }).join('')); 48 | return iterationsHash + '.' + saltHash + '.' + ivHash + '.' + encHash; 49 | }; 50 | 51 | aes.decrypt = async (encrypted, password, passwordBits) => { 52 | let parts = encrypted.split('.'); 53 | let rounds = parseInt(atob(parts[0])); 54 | let salt = new Uint8Array(atob(parts[1]).split('').map(val => { 55 | return val.charCodeAt(0); 56 | })); 57 | let iv = new Uint8Array(atob(parts[2]).split('').map(val => { 58 | return val.charCodeAt(0); 59 | })); 60 | let enc = new Uint8Array(atob(parts[3]).split('').map(val => { 61 | return val.charCodeAt(0); 62 | })); 63 | let pass; 64 | if (password) { 65 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 66 | "name": "PBKDF2" 67 | }, false, ['deriveBits']); 68 | } 69 | if (passwordBits) { 70 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 71 | "name": "PBKDF2" 72 | }, false, ['deriveBits']); 73 | } 74 | let bits = await crypto.subtle.deriveBits({ 75 | "name": "PBKDF2", 76 | "salt": salt, 77 | "iterations": rounds, 78 | "hash": { 79 | "name": "SHA-256" 80 | } 81 | }, pass, 256); 82 | let key = await crypto.subtle.importKey('raw', bits, { 83 | "name": "AES-GCM" 84 | }, false, ['decrypt']); 85 | let dec = await crypto.subtle.decrypt({ 86 | "name": "AES-GCM", 87 | "iv": iv 88 | }, key, enc); 89 | return (new TextDecoder().decode(dec)); 90 | }; 91 | 92 | return aes; 93 | 94 | } 95 | -------------------------------------------------------------------------------- /aes2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function AES2() { 4 | 5 | let aes = {}; 6 | 7 | aes.encrypt = async (message, password, passwordBits, iterations) => { 8 | 9 | let rounds = iterations || 500000; 10 | let iterationsHash = btoa(rounds.toString()); 11 | 12 | let msg = new TextEncoder().encode(message); 13 | 14 | let pass; 15 | if (password) { 16 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 17 | "name": "PBKDF2" 18 | }, false, ['deriveBits']); 19 | } 20 | 21 | if (passwordBits) { 22 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 23 | "name": "PBKDF2" 24 | }, false, ['deriveBits']) 25 | } 26 | 27 | let salt = crypto.getRandomValues(new Uint8Array(32)); 28 | let saltHash = btoa(Array.from(new Uint8Array(salt)).map(val => { 29 | return String.fromCharCode(val) 30 | }).join('')); 31 | 32 | let iv = crypto.getRandomValues(new Uint8Array(12)); 33 | let ivHash = btoa(Array.from(new Uint8Array(iv)).map(val => { 34 | return String.fromCharCode(val) 35 | }).join('')); 36 | 37 | let bits = await crypto.subtle.deriveBits({ 38 | "name": "PBKDF2", 39 | "salt": salt, 40 | "iterations": rounds, 41 | "hash": { 42 | "name": "SHA-256" 43 | } 44 | }, pass, 512); 45 | 46 | let aesBits = bits.slice(32, 64); 47 | let aesKey = await crypto.subtle.importKey('raw', aesBits, { 48 | "name": "AES-GCM" 49 | }, false, ['encrypt']); 50 | 51 | let hmacBits = bits.slice(0, 32); 52 | let hmacKey = await crypto.subtle.importKey('raw', hmacBits, { 53 | "name": "HMAC", 54 | "hash": { 55 | "name": "SHA-256" 56 | } 57 | }, false, ['sign']); 58 | 59 | let enc = await crypto.subtle.encrypt({ 60 | "name": "AES-GCM", 61 | "iv": iv 62 | }, aesKey, msg); 63 | 64 | let encHash = btoa(Array.from(new Uint8Array(enc)).map(val => { 65 | return String.fromCharCode(val) 66 | }).join('')); 67 | 68 | let encrypted = iterationsHash + '.' + saltHash + '.' + ivHash + '.' + encHash; 69 | 70 | let sigData = new TextEncoder().encode(encrypted); 71 | let signature = await crypto.subtle.sign({ 72 | "name": "HMAC" 73 | }, hmacKey, sigData); 74 | 75 | let sigHash = btoa(Array.from(new Uint8Array(signature)).map(val => { 76 | return String.fromCharCode(val) 77 | }).join('')); 78 | 79 | return encrypted + '.' + sigHash; 80 | 81 | }; 82 | 83 | aes.decrypt = async (encrypted, password, passwordBits) => { 84 | 85 | let parts = encrypted.split('.'); 86 | 87 | let rounds = parseInt(atob(parts[0])); 88 | 89 | let salt = new Uint8Array(atob(parts[1]).split('').map(val => { 90 | return val.charCodeAt(0); 91 | })); 92 | 93 | let iv = new Uint8Array(atob(parts[2]).split('').map(val => { 94 | return val.charCodeAt(0); 95 | })); 96 | 97 | let enc = new Uint8Array(atob(parts[3]).split('').map(val => { 98 | return val.charCodeAt(0); 99 | })); 100 | 101 | let sig = new Uint8Array(atob(parts[4]).split('').map(val => { 102 | return val.charCodeAt(0); 103 | })); 104 | 105 | let pass; 106 | 107 | if (password) { 108 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 109 | "name": "PBKDF2" 110 | }, false, ['deriveBits']); 111 | } 112 | 113 | if (passwordBits) { 114 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 115 | "name": "PBKDF2" 116 | }, false, ['deriveBits']); 117 | } 118 | 119 | let bits = await crypto.subtle.deriveBits({ 120 | "name": "PBKDF2", 121 | "salt": salt, 122 | "iterations": rounds, 123 | "hash": { 124 | "name": "SHA-256" 125 | } 126 | }, pass, 512); 127 | 128 | let aesBits = bits.slice(32, 64); 129 | let aesKey = await crypto.subtle.importKey('raw', aesBits, { 130 | "name": "AES-GCM" 131 | }, false, ['decrypt']); 132 | 133 | let hmacBits = bits.slice(0, 32); 134 | let hmacKey = await crypto.subtle.importKey('raw', hmacBits, { 135 | "name": "HMAC", 136 | "hash": { 137 | "name": "SHA-256" 138 | } 139 | }, false, ['verify']); 140 | 141 | let sigData = new TextEncoder().encode(encrypted.split('.').slice(0, 4).join('.')); 142 | let verified = await crypto.subtle.verify({ 143 | "name": "HMAC" 144 | }, hmacKey, sig, sigData); 145 | 146 | if (!verified) { 147 | return Promise.reject({ 148 | "error": "Signature does not match." 149 | }); 150 | } 151 | 152 | let dec = await crypto.subtle.decrypt({ 153 | "name": "AES-GCM", 154 | "iv": iv 155 | }, aesKey, enc); 156 | return (new TextDecoder().decode(dec)); 157 | }; 158 | 159 | return aes; 160 | 161 | } 162 | -------------------------------------------------------------------------------- /aes3.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // AES3 4 | function AES3() { 5 | 6 | let aes = {}; 7 | 8 | aes.encrypt = async (message, password, passwordBits, iterations) => { 9 | 10 | let rounds = iterations || 500000; 11 | let iterationsHash = btoa(rounds.toString()).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 12 | 13 | let msg = new TextEncoder().encode(message); 14 | 15 | let pass; 16 | if (password) { 17 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 18 | "name": "PBKDF2" 19 | }, false, ['deriveBits']); 20 | } 21 | 22 | if (passwordBits) { 23 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 24 | "name": "PBKDF2" 25 | }, false, ['deriveBits']); 26 | } 27 | 28 | let salt = crypto.getRandomValues(new Uint8Array(32)); 29 | let saltHash = btoa(Array.from(new Uint8Array(salt)).map(val => { 30 | return String.fromCharCode(val); 31 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 32 | 33 | let iv = crypto.getRandomValues(new Uint8Array(12)); 34 | let ivHash = btoa(Array.from(new Uint8Array(iv)).map(val => { 35 | return String.fromCharCode(val) 36 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 37 | 38 | let bits = await crypto.subtle.deriveBits({ 39 | "name": "PBKDF2", 40 | "salt": salt, 41 | "iterations": rounds, 42 | "hash": { 43 | "name": "SHA-256" 44 | } 45 | }, pass, 512); 46 | 47 | let aesBits = bits.slice(32, 64); 48 | let aesKey = await crypto.subtle.importKey('raw', aesBits, { 49 | "name": "AES-GCM" 50 | }, false, ['encrypt']); 51 | 52 | let hmacBits = bits.slice(0, 32); 53 | let hmacKey = await crypto.subtle.importKey('raw', hmacBits, { 54 | "name": "HMAC", 55 | "hash": { 56 | "name": "SHA-256" 57 | } 58 | }, false, ['sign']); 59 | 60 | let enc = await crypto.subtle.encrypt({ 61 | "name": "AES-GCM", 62 | "iv": iv 63 | }, aesKey, msg); 64 | 65 | let encHash = btoa(Array.from(new Uint8Array(enc)).map(val => { 66 | return String.fromCharCode(val); 67 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 68 | 69 | let encrypted = iterationsHash + '.' + saltHash + '.' + ivHash + '.' + encHash; 70 | 71 | let sigData = new TextEncoder().encode(encrypted); 72 | let signature = await crypto.subtle.sign({ 73 | "name": "HMAC" 74 | }, hmacKey, sigData); 75 | 76 | let sigHash = btoa(Array.from(new Uint8Array(signature)).map(val => { 77 | return String.fromCharCode(val); 78 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 79 | 80 | return encrypted + '.' + sigHash; 81 | 82 | }; 83 | 84 | aes.decrypt = async (encrypted, password, passwordBits) => { 85 | 86 | let parts = encrypted.split('.'); 87 | 88 | let rounds = parseInt(atob(parts[0])); 89 | 90 | let salt = new Uint8Array(atob(parts[1].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 91 | return val.charCodeAt(0); 92 | })); 93 | 94 | let iv = new Uint8Array(atob(parts[2].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 95 | return val.charCodeAt(0); 96 | })); 97 | 98 | let enc = new Uint8Array(atob(parts[3].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 99 | return val.charCodeAt(0); 100 | })); 101 | 102 | let sig = new Uint8Array(atob(parts[4].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 103 | return val.charCodeAt(0); 104 | })); 105 | 106 | let pass; 107 | 108 | if (password) { 109 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 110 | "name": "PBKDF2" 111 | }, false, ['deriveBits']); 112 | } 113 | 114 | if (passwordBits) { 115 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 116 | "name": "PBKDF2" 117 | }, false, ['deriveBits']); 118 | } 119 | 120 | let bits = await crypto.subtle.deriveBits({ 121 | "name": "PBKDF2", 122 | "salt": salt, 123 | "iterations": rounds, 124 | "hash": { 125 | "name": "SHA-256" 126 | } 127 | }, pass, 512); 128 | 129 | let aesBits = bits.slice(32, 64); 130 | let aesKey = await crypto.subtle.importKey('raw', aesBits, { 131 | "name": "AES-GCM" 132 | }, false, ['decrypt']); 133 | 134 | let hmacBits = bits.slice(0, 32); 135 | let hmacKey = await crypto.subtle.importKey('raw', hmacBits, { 136 | "name": "HMAC", 137 | "hash": { 138 | "name": "SHA-256" 139 | } 140 | }, false, ['verify']); 141 | 142 | let sigData = new TextEncoder().encode(encrypted.split('.').slice(0, 4).join('.')); 143 | let verified = await crypto.subtle.verify({ 144 | "name": "HMAC" 145 | }, hmacKey, sig, sigData); 146 | 147 | if (!verified) { 148 | return Promise.reject({ 149 | "error": "Password or signature does not match." 150 | }); 151 | } 152 | 153 | let dec = await crypto.subtle.decrypt({ 154 | "name": "AES-GCM", 155 | "iv": iv 156 | }, aesKey, enc); 157 | return (new TextDecoder().decode(dec)); 158 | }; 159 | 160 | return aes; 161 | 162 | } 163 | -------------------------------------------------------------------------------- /aes3.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function AES3(){let aes={encrypt:async(message,password,passwordBits,iterations)=>{let pass,rounds=iterations||5e5,iterationsHash=btoa(rounds.toString()).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),msg=(new TextEncoder).encode(message);password&&(pass=await crypto.subtle.importKey("raw",(new TextEncoder).encode(password),{name:"PBKDF2"},!1,["deriveBits"])),passwordBits&&(pass=await crypto.subtle.importKey("raw",new Uint8Array(passwordBits),{name:"PBKDF2"},!1,["deriveBits"]));let salt=crypto.getRandomValues(new Uint8Array(32)),saltHash=btoa(Array.from(new Uint8Array(salt)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),iv=crypto.getRandomValues(new Uint8Array(12)),ivHash=btoa(Array.from(new Uint8Array(iv)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),bits=await crypto.subtle.deriveBits({name:"PBKDF2",salt:salt,iterations:rounds,hash:{name:"SHA-256"}},pass,512),aesBits=bits.slice(32,64),aesKey=await crypto.subtle.importKey("raw",aesBits,{name:"AES-GCM"},!1,["encrypt"]),hmacBits=bits.slice(0,32),hmacKey=await crypto.subtle.importKey("raw",hmacBits,{name:"HMAC",hash:{name:"SHA-256"}},!1,["sign"]),enc=await crypto.subtle.encrypt({name:"AES-GCM",iv:iv},aesKey,msg),encrypted=iterationsHash+"."+saltHash+"."+ivHash+"."+btoa(Array.from(new Uint8Array(enc)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),sigData=(new TextEncoder).encode(encrypted),signature=await crypto.subtle.sign({name:"HMAC"},hmacKey,sigData);return encrypted+"."+btoa(Array.from(new Uint8Array(signature)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_")},decrypt:async(encrypted,password,passwordBits)=>{let pass,parts=encrypted.split("."),rounds=parseInt(atob(parts[0])),salt=new Uint8Array(atob(parts[1].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0))),iv=new Uint8Array(atob(parts[2].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0))),enc=new Uint8Array(atob(parts[3].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0))),sig=new Uint8Array(atob(parts[4].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0)));password&&(pass=await crypto.subtle.importKey("raw",(new TextEncoder).encode(password),{name:"PBKDF2"},!1,["deriveBits"])),passwordBits&&(pass=await crypto.subtle.importKey("raw",new Uint8Array(passwordBits),{name:"PBKDF2"},!1,["deriveBits"]));let bits=await crypto.subtle.deriveBits({name:"PBKDF2",salt:salt,iterations:rounds,hash:{name:"SHA-256"}},pass,512),aesBits=bits.slice(32,64),aesKey=await crypto.subtle.importKey("raw",aesBits,{name:"AES-GCM"},!1,["decrypt"]),hmacBits=bits.slice(0,32),hmacKey=await crypto.subtle.importKey("raw",hmacBits,{name:"HMAC",hash:{name:"SHA-256"}},!1,["verify"]),sigData=(new TextEncoder).encode(encrypted.split(".").slice(0,4).join("."));if(!await crypto.subtle.verify({name:"HMAC"},hmacKey,sig,sigData))return Promise.reject({error:"Password or signature does not match."});let dec=await crypto.subtle.decrypt({name:"AES-GCM",iv:iv},aesKey,enc);return(new TextDecoder).decode(dec)}};return aes} -------------------------------------------------------------------------------- /encryption.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Encryption() { 4 | 5 | let encryption = {}; 6 | 7 | encryption.encrypt = async (message, password, passwordBits, iterations, hmacBits = null) => { 8 | 9 | let rounds = iterations || 500000; 10 | let iterationsHash = btoa(rounds.toString()).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 11 | 12 | let msg = new TextEncoder().encode(message); 13 | 14 | let pass; 15 | if (password) { 16 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 17 | "name": "PBKDF2" 18 | }, false, ['deriveBits']); 19 | } 20 | 21 | if (passwordBits) { 22 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 23 | "name": "PBKDF2" 24 | }, false, ['deriveBits']); 25 | } 26 | 27 | let salt = crypto.getRandomValues(new Uint8Array(32)); 28 | let saltHash = btoa(Array.from(new Uint8Array(salt)).map(val => { 29 | return String.fromCharCode(val); 30 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 31 | 32 | let iv = crypto.getRandomValues(new Uint8Array(12)); 33 | let ivHash = btoa(Array.from(new Uint8Array(iv)).map(val => { 34 | return String.fromCharCode(val) 35 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 36 | 37 | let bits = await crypto.subtle.deriveBits({ 38 | "name": "PBKDF2", 39 | "salt": salt, 40 | "iterations": rounds, 41 | "hash": { 42 | "name": "SHA-256" 43 | } 44 | }, pass, 512); 45 | 46 | let aesBits = bits.slice(32, 64); 47 | 48 | let aesKey = await crypto.subtle.importKey('raw', aesBits, { 49 | "name": "AES-GCM" 50 | }, false, ['encrypt']); 51 | 52 | if (!hmacBits) { 53 | hmacBits = bits.slice(0, 32); 54 | } 55 | 56 | let hmacKey = await crypto.subtle.importKey('raw', hmacBits, { 57 | "name": "HMAC", 58 | "hash": { 59 | "name": "SHA-256" 60 | } 61 | }, false, ['sign']); 62 | 63 | let enc = await crypto.subtle.encrypt({ 64 | "name": "AES-GCM", 65 | "iv": iv 66 | }, aesKey, msg); 67 | 68 | let encHash = btoa(Array.from(new Uint8Array(enc)).map(val => { 69 | return String.fromCharCode(val); 70 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 71 | 72 | let encrypted = iterationsHash + '.' + saltHash + '.' + ivHash + '.' + encHash; 73 | 74 | let sigData = new TextEncoder().encode(encrypted); 75 | let signature = await crypto.subtle.sign({ 76 | "name": "HMAC" 77 | }, hmacKey, sigData); 78 | 79 | let sigHash = btoa(Array.from(new Uint8Array(signature)).map(val => { 80 | return String.fromCharCode(val); 81 | }).join('')).replace(/\=/g,'').replace(/\+/g,'-').replace(/\//g,'_'); 82 | 83 | return encrypted + '.' + sigHash; 84 | 85 | }; 86 | 87 | encryption.decrypt = async (encrypted, password, passwordBits, hmacBits) => { 88 | 89 | let parts = encrypted.split('.'); 90 | 91 | let rounds = parseInt(atob(parts[0])); 92 | 93 | let salt = new Uint8Array(atob(parts[1].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 94 | return val.charCodeAt(0); 95 | })); 96 | 97 | let iv = new Uint8Array(atob(parts[2].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 98 | return val.charCodeAt(0); 99 | })); 100 | 101 | let enc = new Uint8Array(atob(parts[3].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 102 | return val.charCodeAt(0); 103 | })); 104 | 105 | let sig = new Uint8Array(atob(parts[4].replace(/-/g,'+').replace(/_/g,'/')).split('').map(val => { 106 | return val.charCodeAt(0); 107 | })); 108 | 109 | let pass; 110 | 111 | if (password) { 112 | pass = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), { 113 | "name": "PBKDF2" 114 | }, false, ['deriveBits']); 115 | } 116 | 117 | if (passwordBits) { 118 | pass = await crypto.subtle.importKey('raw', new Uint8Array(passwordBits), { 119 | "name": "PBKDF2" 120 | }, false, ['deriveBits']); 121 | } 122 | 123 | let bits = await crypto.subtle.deriveBits({ 124 | "name": "PBKDF2", 125 | "salt": salt, 126 | "iterations": rounds, 127 | "hash": { 128 | "name": "SHA-256" 129 | } 130 | }, pass, 512); 131 | 132 | let aesBits = bits.slice(32, 64); 133 | 134 | let aesKey = await crypto.subtle.importKey('raw', aesBits, { 135 | "name": "AES-GCM" 136 | }, false, ['decrypt']); 137 | 138 | if (!hmacBits) { 139 | hmacBits = bits.slice(0, 32); 140 | } 141 | 142 | let hmacKey = await crypto.subtle.importKey('raw', hmacBits, { 143 | "name": "HMAC", 144 | "hash": { 145 | "name": "SHA-256" 146 | } 147 | }, false, ['verify']); 148 | 149 | let sigData = new TextEncoder().encode(encrypted.split('.').slice(0, 4).join('.')); 150 | let verified = await crypto.subtle.verify({ 151 | "name": "HMAC" 152 | }, hmacKey, sig, sigData); 153 | 154 | if (!verified) { 155 | return Promise.reject({ 156 | "error": "Password or signature does not match." 157 | }); 158 | } 159 | 160 | let dec = await crypto.subtle.decrypt({ 161 | "name": "AES-GCM", 162 | "iv": iv 163 | }, aesKey, enc); 164 | return (new TextDecoder().decode(dec)); 165 | }; 166 | 167 | return encryption; 168 | 169 | } 170 | -------------------------------------------------------------------------------- /encryption.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function Encryption(){let encryption={encrypt:async(message,password,passwordBits,iterations,hmacBits=null)=>{let pass,rounds=iterations||5e5,iterationsHash=btoa(rounds.toString()).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),msg=(new TextEncoder).encode(message);password&&(pass=await crypto.subtle.importKey("raw",(new TextEncoder).encode(password),{name:"PBKDF2"},!1,["deriveBits"])),passwordBits&&(pass=await crypto.subtle.importKey("raw",new Uint8Array(passwordBits),{name:"PBKDF2"},!1,["deriveBits"]));let salt=crypto.getRandomValues(new Uint8Array(32)),saltHash=btoa(Array.from(new Uint8Array(salt)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),iv=crypto.getRandomValues(new Uint8Array(12)),ivHash=btoa(Array.from(new Uint8Array(iv)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),bits=await crypto.subtle.deriveBits({name:"PBKDF2",salt:salt,iterations:rounds,hash:{name:"SHA-256"}},pass,512),aesBits=bits.slice(32,64),aesKey=await crypto.subtle.importKey("raw",aesBits,{name:"AES-GCM"},!1,["encrypt"]);hmacBits||(hmacBits=bits.slice(0,32));let hmacKey=await crypto.subtle.importKey("raw",hmacBits,{name:"HMAC",hash:{name:"SHA-256"}},!1,["sign"]),enc=await crypto.subtle.encrypt({name:"AES-GCM",iv:iv},aesKey,msg),encrypted=iterationsHash+"."+saltHash+"."+ivHash+"."+btoa(Array.from(new Uint8Array(enc)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_"),sigData=(new TextEncoder).encode(encrypted),signature=await crypto.subtle.sign({name:"HMAC"},hmacKey,sigData);return encrypted+"."+btoa(Array.from(new Uint8Array(signature)).map(val=>String.fromCharCode(val)).join("")).replace(/\=/g,"").replace(/\+/g,"-").replace(/\//g,"_")},decrypt:async(encrypted,password,passwordBits,hmacBits)=>{let pass,parts=encrypted.split("."),rounds=parseInt(atob(parts[0])),salt=new Uint8Array(atob(parts[1].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0))),iv=new Uint8Array(atob(parts[2].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0))),enc=new Uint8Array(atob(parts[3].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0))),sig=new Uint8Array(atob(parts[4].replace(/-/g,"+").replace(/_/g,"/")).split("").map(val=>val.charCodeAt(0)));password&&(pass=await crypto.subtle.importKey("raw",(new TextEncoder).encode(password),{name:"PBKDF2"},!1,["deriveBits"])),passwordBits&&(pass=await crypto.subtle.importKey("raw",new Uint8Array(passwordBits),{name:"PBKDF2"},!1,["deriveBits"]));let bits=await crypto.subtle.deriveBits({name:"PBKDF2",salt:salt,iterations:rounds,hash:{name:"SHA-256"}},pass,512),aesBits=bits.slice(32,64),aesKey=await crypto.subtle.importKey("raw",aesBits,{name:"AES-GCM"},!1,["decrypt"]);hmacBits||(hmacBits=bits.slice(0,32));let hmacKey=await crypto.subtle.importKey("raw",hmacBits,{name:"HMAC",hash:{name:"SHA-256"}},!1,["verify"]),sigData=(new TextEncoder).encode(encrypted.split(".").slice(0,4).join("."));if(!await crypto.subtle.verify({name:"HMAC"},hmacKey,sig,sigData))return Promise.reject({error:"Password or signature does not match."});let dec=await crypto.subtle.decrypt({name:"AES-GCM",iv:iv},aesKey,enc);return(new TextDecoder).decode(dec)}};return encryption} -------------------------------------------------------------------------------- /passwords.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function PASSWORDS() { 4 | 5 | let passwords = {}; 6 | 7 | passwords.hash = async (password, iterations) => { 8 | let time = Date.now(); 9 | let pass = new TextEncoder().encode(password); 10 | let salt = crypto.getRandomValues(new Uint8Array(32)); 11 | let key = await crypto.subtle.importKey('raw', pass, "PBKDF2", false, ['deriveBits']) 12 | 13 | let result = await crypto.subtle.deriveBits({ 14 | "name": "PBKDF2", 15 | "salt": salt, 16 | "iterations": iterations, 17 | "hash": "SHA-256" 18 | }, key, 256); 19 | 20 | let bitsString = btoa(Array.from(new Uint8Array(result)).map(val => { 21 | return String.fromCharCode(val) 22 | }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); 23 | 24 | let saltString = btoa(Array.from(new Uint8Array(salt)).map(val => { 25 | return String.fromCharCode(val) 26 | }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); 27 | 28 | let hash = iterations + '.' + saltString + '.' + bitsString; 29 | 30 | return hash; 31 | 32 | }; 33 | 34 | passwords.compare = async (hash, password) => { 35 | 36 | let parts = hash.split('.'); 37 | let iterations = parts[0]; 38 | let salt = new Uint8Array(atob(parts[1].replace(/\-/g, '+').replace(/\_/g, '/')).split('').map(val => { 39 | return val.charCodeAt(0); 40 | })); 41 | 42 | let pass = new TextEncoder().encode(password); 43 | let key = await crypto.subtle.importKey('raw', pass, { 44 | "name": "PBKDF2" 45 | }, false, ['deriveBits']); 46 | 47 | let bitBuffer = await crypto.subtle.deriveBits({ 48 | "name": "PBKDF2", 49 | "salt": salt, 50 | "iterations": iterations, 51 | "hash": "SHA-256" 52 | }, key, 256); 53 | 54 | let bitsString = btoa(Array.from(new Uint8Array(bitBuffer)).map(val => { 55 | return String.fromCharCode(val) 56 | }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); 57 | 58 | let result = parts[2] === bitsString; 59 | 60 | return result; 61 | 62 | }; 63 | 64 | return passwords; 65 | 66 | } 67 | --------------------------------------------------------------------------------