├── .github └── dependabot.yml ├── .gitignore ├── .npmignore ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── src └── decrypt.ts ├── test └── index.ts └── tsconfig.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: "@types/node" 10 | versions: 11 | - 14.14.22 12 | - 14.14.24 13 | - 14.14.25 14 | - 14.14.26 15 | - 14.14.28 16 | - 14.14.30 17 | - 14.14.31 18 | - 14.14.32 19 | - 14.14.37 20 | - 15.0.0 21 | - dependency-name: typescript 22 | versions: 23 | - 4.1.3 24 | - 4.1.4 25 | - 4.1.5 26 | - 4.2.2 27 | - dependency-name: mime-types 28 | versions: 29 | - 2.1.28 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | # dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | ./dist 106 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | src 3 | test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@open-wa/wa-decrypt.svg?color=green)](https://www.npmjs.com/package/@open-wa/wa-decrypt) 2 | [![Maintainability](https://api.codeclimate.com/v1/badges/a452db764ce137f35e99/maintainability)](https://codeclimate.com/github/smashah/wa-decrypt/maintainability) 3 | [![Buy me a coffee][buymeacoffee-shield]][buymeacoffee] 4 | 5 | # wa-decrypt 6 | 7 | > This is a single function library born out of the [open-wa/wa-automate-nodejs](https://github.com/open-wa/wa-automate-nodejs) project. The reason for this project is so you can easily decrypt messages on a remote process without needing to install all of the dependencies (e.g puppeteer) that come with open-wa/wa-automate-nodejs. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | > npm i --save @open-wa/wa-decrypt 13 | ``` 14 | 15 | ## Decrypting Media 16 | 17 | Here is a sample of how to decrypt media. This has been tested on images, videos, documents, audio and voice notes. 18 | 19 | ```javascript 20 | import { decryptMedia } from '@open-wa/wa-decrypt'; 21 | const mime = require('mime-types'); 22 | const fs = require('fs'); 23 | 24 | const processMessage = async message => { 25 | if (message.mimetype) { 26 | const filename = `${message.t}.${mime.extension(message.mimetype)}`; 27 | const mediaData = await decryptMedia(message); 28 | const imageBase64 = `data:${message.mimetype};base64,${mediaData.toString( 29 | 'base64' 30 | )}`; 31 | fs.writeFile(filename, mediaData, function(err) { 32 | if (err) { 33 | return console.log(err); 34 | } 35 | console.log('The file was saved!'); 36 | }); 37 | } 38 | } 39 | ``` 40 | 41 | ## Verifying Decryption 42 | 43 | As of v2.0.0 of `wa-decrypt`, you are now able to verify the decryption output hash with the actual hash of the file as represented by `message.filehash`. You can see an example of this in `test/index.ts`. Please note that YOU CANNOT VERIFY THE HASH OF STICKERS, so don't even try it right now! 44 | 45 | ```javascript 46 | import crypto from 'crypto'; 47 | ... 48 | 49 | const mediaData = await decryptMedia(message); 50 | let output_hash = crypto.createHash('sha256').update(mediaData).digest('base64'); 51 | let hashValid = message.filehash == output_hash; 52 | console.log('Hash Validated:', hashValid); 53 | 54 | ``` 55 | 56 | ## Troubleshooting 57 | 58 | If you're having issues with 404s or are unable to decrypt stickers, please see this documentation: 59 | 60 | [docs.openwa.dev](https://docs.openwa.dev/pages/How%20to/decrypt-media.html) 61 | 62 | ## Contributing 63 | 64 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 65 | 66 | ## License 67 | 68 | [MIT](https://choosealicense.com/licenses/mit/) 69 | 70 | 71 | ## Legal 72 | 73 | This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by WhatsApp or any of its affiliates or subsidiaries. This is an independent and unofficial software. Use at your own risk. 74 | 75 | ## Cryptography Notice 76 | 77 | This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. See [http://www.wassenaar.org/](http://www.wassenaar.org/) for more information. 78 | 79 | The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms. The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. 80 | 81 | [buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg 82 | [buymeacoffee]: https://www.buymeacoffee.com/smashah -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export {decryptMedia} from './dist/decrypt.js' -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@open-wa/wa-decrypt", 3 | "version": "4.4.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@open-wa/wa-decrypt", 9 | "version": "4.4.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@open-wa/wa-automate-types-only": "*", 13 | "atob": "^2.1.2", 14 | "axios": "^0.27.2", 15 | "futoin-hkdf": "^1.3.3", 16 | "type-fest": "^2.1.0" 17 | }, 18 | "devDependencies": { 19 | "@types/atob": "^2.1.2", 20 | "@types/mime-types": "^2.1.0", 21 | "@types/node": "^17.0.7", 22 | "mime-types": "^2.1.30", 23 | "ts-node": "^10.0.0", 24 | "typescript": "^4.2.4" 25 | }, 26 | "engines": { 27 | "node": ">=12" 28 | } 29 | }, 30 | "node_modules/@cspotcode/source-map-consumer": { 31 | "version": "0.8.0", 32 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", 33 | "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", 34 | "dev": true, 35 | "engines": { 36 | "node": ">= 12" 37 | } 38 | }, 39 | "node_modules/@cspotcode/source-map-support": { 40 | "version": "0.7.0", 41 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", 42 | "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", 43 | "dev": true, 44 | "dependencies": { 45 | "@cspotcode/source-map-consumer": "0.8.0" 46 | }, 47 | "engines": { 48 | "node": ">=12" 49 | } 50 | }, 51 | "node_modules/@open-wa/wa-automate-types-only": { 52 | "version": "4.35.11", 53 | "resolved": "https://registry.npmjs.org/@open-wa/wa-automate-types-only/-/wa-automate-types-only-4.35.11.tgz", 54 | "integrity": "sha512-gHz/H6zzNdC4hi/eRc/Rs9FfrfU2KDJ/qLml2czvJDNDplmACUM+RvyLFQqxLmqVHHwFYfzLqhw7HxUGsPyL0g==", 55 | "engines": { 56 | "node": ">=12.18.3", 57 | "npm": ">=7.9.0" 58 | } 59 | }, 60 | "node_modules/@tsconfig/node10": { 61 | "version": "1.0.8", 62 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", 63 | "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", 64 | "dev": true 65 | }, 66 | "node_modules/@tsconfig/node12": { 67 | "version": "1.0.9", 68 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", 69 | "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", 70 | "dev": true 71 | }, 72 | "node_modules/@tsconfig/node14": { 73 | "version": "1.0.1", 74 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", 75 | "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", 76 | "dev": true 77 | }, 78 | "node_modules/@tsconfig/node16": { 79 | "version": "1.0.2", 80 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", 81 | "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", 82 | "dev": true 83 | }, 84 | "node_modules/@types/atob": { 85 | "version": "2.1.2", 86 | "resolved": "https://registry.npmjs.org/@types/atob/-/atob-2.1.2.tgz", 87 | "integrity": "sha512-8GAYQ1jDRUQkSpHzJUqXwAkYFOxuWAOGLhIR4aPd/Y/yL12Q/9m7LsKpHKlfKdNE/362Hc9wPI1Yh6opDfxVJg==", 88 | "dev": true 89 | }, 90 | "node_modules/@types/mime-types": { 91 | "version": "2.1.1", 92 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", 93 | "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", 94 | "dev": true 95 | }, 96 | "node_modules/@types/node": { 97 | "version": "17.0.29", 98 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", 99 | "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", 100 | "dev": true 101 | }, 102 | "node_modules/acorn": { 103 | "version": "8.7.0", 104 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 105 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", 106 | "dev": true, 107 | "bin": { 108 | "acorn": "bin/acorn" 109 | }, 110 | "engines": { 111 | "node": ">=0.4.0" 112 | } 113 | }, 114 | "node_modules/acorn-walk": { 115 | "version": "8.2.0", 116 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 117 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 118 | "dev": true, 119 | "engines": { 120 | "node": ">=0.4.0" 121 | } 122 | }, 123 | "node_modules/arg": { 124 | "version": "4.1.3", 125 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 126 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 127 | "dev": true 128 | }, 129 | "node_modules/asynckit": { 130 | "version": "0.4.0", 131 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 132 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 133 | }, 134 | "node_modules/atob": { 135 | "version": "2.1.2", 136 | "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", 137 | "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", 138 | "bin": { 139 | "atob": "bin/atob.js" 140 | }, 141 | "engines": { 142 | "node": ">= 4.5.0" 143 | } 144 | }, 145 | "node_modules/axios": { 146 | "version": "0.27.2", 147 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", 148 | "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", 149 | "dependencies": { 150 | "follow-redirects": "^1.14.9", 151 | "form-data": "^4.0.0" 152 | } 153 | }, 154 | "node_modules/combined-stream": { 155 | "version": "1.0.8", 156 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 157 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 158 | "dependencies": { 159 | "delayed-stream": "~1.0.0" 160 | }, 161 | "engines": { 162 | "node": ">= 0.8" 163 | } 164 | }, 165 | "node_modules/create-require": { 166 | "version": "1.1.1", 167 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 168 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 169 | "dev": true 170 | }, 171 | "node_modules/delayed-stream": { 172 | "version": "1.0.0", 173 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 174 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 175 | "engines": { 176 | "node": ">=0.4.0" 177 | } 178 | }, 179 | "node_modules/diff": { 180 | "version": "4.0.2", 181 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 182 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 183 | "dev": true, 184 | "engines": { 185 | "node": ">=0.3.1" 186 | } 187 | }, 188 | "node_modules/follow-redirects": { 189 | "version": "1.14.9", 190 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", 191 | "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", 192 | "funding": [ 193 | { 194 | "type": "individual", 195 | "url": "https://github.com/sponsors/RubenVerborgh" 196 | } 197 | ], 198 | "engines": { 199 | "node": ">=4.0" 200 | }, 201 | "peerDependenciesMeta": { 202 | "debug": { 203 | "optional": true 204 | } 205 | } 206 | }, 207 | "node_modules/form-data": { 208 | "version": "4.0.0", 209 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 210 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 211 | "dependencies": { 212 | "asynckit": "^0.4.0", 213 | "combined-stream": "^1.0.8", 214 | "mime-types": "^2.1.12" 215 | }, 216 | "engines": { 217 | "node": ">= 6" 218 | } 219 | }, 220 | "node_modules/futoin-hkdf": { 221 | "version": "1.5.0", 222 | "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.0.tgz", 223 | "integrity": "sha512-4CerDhtTgx4i5PKccQIpEp4T9wqmosPIP9Kep35SdCpYkQeriD3zddUVhrO1Fc4QvGhsAnd2rXyoOr5047mJEg==", 224 | "engines": { 225 | "node": ">=8" 226 | } 227 | }, 228 | "node_modules/make-error": { 229 | "version": "1.3.6", 230 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 231 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 232 | "dev": true 233 | }, 234 | "node_modules/mime-db": { 235 | "version": "1.52.0", 236 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 237 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 238 | "engines": { 239 | "node": ">= 0.6" 240 | } 241 | }, 242 | "node_modules/mime-types": { 243 | "version": "2.1.35", 244 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 245 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 246 | "dependencies": { 247 | "mime-db": "1.52.0" 248 | }, 249 | "engines": { 250 | "node": ">= 0.6" 251 | } 252 | }, 253 | "node_modules/ts-node": { 254 | "version": "10.7.0", 255 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", 256 | "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", 257 | "dev": true, 258 | "dependencies": { 259 | "@cspotcode/source-map-support": "0.7.0", 260 | "@tsconfig/node10": "^1.0.7", 261 | "@tsconfig/node12": "^1.0.7", 262 | "@tsconfig/node14": "^1.0.0", 263 | "@tsconfig/node16": "^1.0.2", 264 | "acorn": "^8.4.1", 265 | "acorn-walk": "^8.1.1", 266 | "arg": "^4.1.0", 267 | "create-require": "^1.1.0", 268 | "diff": "^4.0.1", 269 | "make-error": "^1.1.1", 270 | "v8-compile-cache-lib": "^3.0.0", 271 | "yn": "3.1.1" 272 | }, 273 | "bin": { 274 | "ts-node": "dist/bin.js", 275 | "ts-node-cwd": "dist/bin-cwd.js", 276 | "ts-node-esm": "dist/bin-esm.js", 277 | "ts-node-script": "dist/bin-script.js", 278 | "ts-node-transpile-only": "dist/bin-transpile.js", 279 | "ts-script": "dist/bin-script-deprecated.js" 280 | }, 281 | "peerDependencies": { 282 | "@swc/core": ">=1.2.50", 283 | "@swc/wasm": ">=1.2.50", 284 | "@types/node": "*", 285 | "typescript": ">=2.7" 286 | }, 287 | "peerDependenciesMeta": { 288 | "@swc/core": { 289 | "optional": true 290 | }, 291 | "@swc/wasm": { 292 | "optional": true 293 | } 294 | } 295 | }, 296 | "node_modules/type-fest": { 297 | "version": "2.12.2", 298 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.12.2.tgz", 299 | "integrity": "sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ==", 300 | "engines": { 301 | "node": ">=12.20" 302 | }, 303 | "funding": { 304 | "url": "https://github.com/sponsors/sindresorhus" 305 | } 306 | }, 307 | "node_modules/typescript": { 308 | "version": "4.6.3", 309 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", 310 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", 311 | "dev": true, 312 | "bin": { 313 | "tsc": "bin/tsc", 314 | "tsserver": "bin/tsserver" 315 | }, 316 | "engines": { 317 | "node": ">=4.2.0" 318 | } 319 | }, 320 | "node_modules/v8-compile-cache-lib": { 321 | "version": "3.0.0", 322 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", 323 | "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", 324 | "dev": true 325 | }, 326 | "node_modules/yn": { 327 | "version": "3.1.1", 328 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 329 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 330 | "dev": true, 331 | "engines": { 332 | "node": ">=6" 333 | } 334 | } 335 | }, 336 | "dependencies": { 337 | "@cspotcode/source-map-consumer": { 338 | "version": "0.8.0", 339 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", 340 | "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", 341 | "dev": true 342 | }, 343 | "@cspotcode/source-map-support": { 344 | "version": "0.7.0", 345 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", 346 | "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", 347 | "dev": true, 348 | "requires": { 349 | "@cspotcode/source-map-consumer": "0.8.0" 350 | } 351 | }, 352 | "@open-wa/wa-automate-types-only": { 353 | "version": "4.35.11", 354 | "resolved": "https://registry.npmjs.org/@open-wa/wa-automate-types-only/-/wa-automate-types-only-4.35.11.tgz", 355 | "integrity": "sha512-gHz/H6zzNdC4hi/eRc/Rs9FfrfU2KDJ/qLml2czvJDNDplmACUM+RvyLFQqxLmqVHHwFYfzLqhw7HxUGsPyL0g==" 356 | }, 357 | "@tsconfig/node10": { 358 | "version": "1.0.8", 359 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", 360 | "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", 361 | "dev": true 362 | }, 363 | "@tsconfig/node12": { 364 | "version": "1.0.9", 365 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", 366 | "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", 367 | "dev": true 368 | }, 369 | "@tsconfig/node14": { 370 | "version": "1.0.1", 371 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", 372 | "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", 373 | "dev": true 374 | }, 375 | "@tsconfig/node16": { 376 | "version": "1.0.2", 377 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", 378 | "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", 379 | "dev": true 380 | }, 381 | "@types/atob": { 382 | "version": "2.1.2", 383 | "resolved": "https://registry.npmjs.org/@types/atob/-/atob-2.1.2.tgz", 384 | "integrity": "sha512-8GAYQ1jDRUQkSpHzJUqXwAkYFOxuWAOGLhIR4aPd/Y/yL12Q/9m7LsKpHKlfKdNE/362Hc9wPI1Yh6opDfxVJg==", 385 | "dev": true 386 | }, 387 | "@types/mime-types": { 388 | "version": "2.1.1", 389 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", 390 | "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", 391 | "dev": true 392 | }, 393 | "@types/node": { 394 | "version": "17.0.29", 395 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", 396 | "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", 397 | "dev": true 398 | }, 399 | "acorn": { 400 | "version": "8.7.0", 401 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 402 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", 403 | "dev": true 404 | }, 405 | "acorn-walk": { 406 | "version": "8.2.0", 407 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 408 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 409 | "dev": true 410 | }, 411 | "arg": { 412 | "version": "4.1.3", 413 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 414 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 415 | "dev": true 416 | }, 417 | "asynckit": { 418 | "version": "0.4.0", 419 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 420 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 421 | }, 422 | "atob": { 423 | "version": "2.1.2", 424 | "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", 425 | "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" 426 | }, 427 | "axios": { 428 | "version": "0.27.2", 429 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", 430 | "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", 431 | "requires": { 432 | "follow-redirects": "^1.14.9", 433 | "form-data": "^4.0.0" 434 | } 435 | }, 436 | "combined-stream": { 437 | "version": "1.0.8", 438 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 439 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 440 | "requires": { 441 | "delayed-stream": "~1.0.0" 442 | } 443 | }, 444 | "create-require": { 445 | "version": "1.1.1", 446 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 447 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 448 | "dev": true 449 | }, 450 | "delayed-stream": { 451 | "version": "1.0.0", 452 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 453 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 454 | }, 455 | "diff": { 456 | "version": "4.0.2", 457 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 458 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 459 | "dev": true 460 | }, 461 | "follow-redirects": { 462 | "version": "1.14.9", 463 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", 464 | "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" 465 | }, 466 | "form-data": { 467 | "version": "4.0.0", 468 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 469 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 470 | "requires": { 471 | "asynckit": "^0.4.0", 472 | "combined-stream": "^1.0.8", 473 | "mime-types": "^2.1.12" 474 | } 475 | }, 476 | "futoin-hkdf": { 477 | "version": "1.5.0", 478 | "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.0.tgz", 479 | "integrity": "sha512-4CerDhtTgx4i5PKccQIpEp4T9wqmosPIP9Kep35SdCpYkQeriD3zddUVhrO1Fc4QvGhsAnd2rXyoOr5047mJEg==" 480 | }, 481 | "make-error": { 482 | "version": "1.3.6", 483 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 484 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 485 | "dev": true 486 | }, 487 | "mime-db": { 488 | "version": "1.52.0", 489 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 490 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 491 | }, 492 | "mime-types": { 493 | "version": "2.1.35", 494 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 495 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 496 | "requires": { 497 | "mime-db": "1.52.0" 498 | } 499 | }, 500 | "ts-node": { 501 | "version": "10.7.0", 502 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", 503 | "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", 504 | "dev": true, 505 | "requires": { 506 | "@cspotcode/source-map-support": "0.7.0", 507 | "@tsconfig/node10": "^1.0.7", 508 | "@tsconfig/node12": "^1.0.7", 509 | "@tsconfig/node14": "^1.0.0", 510 | "@tsconfig/node16": "^1.0.2", 511 | "acorn": "^8.4.1", 512 | "acorn-walk": "^8.1.1", 513 | "arg": "^4.1.0", 514 | "create-require": "^1.1.0", 515 | "diff": "^4.0.1", 516 | "make-error": "^1.1.1", 517 | "v8-compile-cache-lib": "^3.0.0", 518 | "yn": "3.1.1" 519 | } 520 | }, 521 | "type-fest": { 522 | "version": "2.12.2", 523 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.12.2.tgz", 524 | "integrity": "sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ==" 525 | }, 526 | "typescript": { 527 | "version": "4.6.3", 528 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", 529 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", 530 | "dev": true 531 | }, 532 | "v8-compile-cache-lib": { 533 | "version": "3.0.0", 534 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", 535 | "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", 536 | "dev": true 537 | }, 538 | "yn": { 539 | "version": "3.1.1", 540 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 541 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 542 | "dev": true 543 | } 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@open-wa/wa-decrypt", 3 | "version": "4.4.0", 4 | "description": "A lightweight implementation of the open-wa/wa-automate-nodejs media decryption code", 5 | "main": "./dist/decrypt.js", 6 | "types": "dist/decrypt.d.ts", 7 | "scripts": { 8 | "test": "ts-node ./test/index.ts", 9 | "build": "tsc" 10 | }, 11 | "engines": { 12 | "node": ">=12" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "author": "SMASHAH (smashah.dev)", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@open-wa/wa-automate-types-only": "*", 21 | "atob": "^2.1.2", 22 | "axios": "^0.27.2", 23 | "futoin-hkdf": "^1.3.3", 24 | "type-fest": "^2.1.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/open-wa/wa-decrypt-nodejs.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/open-wa/wa-decrypt-nodejs/issues" 32 | }, 33 | "homepage": "https://github.com/open-wa/wa-decrypt-nodejs#readme", 34 | "devDependencies": { 35 | "@types/atob": "^2.1.2", 36 | "@types/mime-types": "^2.1.0", 37 | "@types/node": "^17.0.7", 38 | "mime-types": "^2.1.30", 39 | "ts-node": "^10.0.0", 40 | "typescript": "^4.2.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/decrypt.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import hkdf from 'futoin-hkdf'; 3 | import atob from 'atob'; 4 | import axios from 'axios'; 5 | import { ResponseType } from 'axios'; 6 | import { RequireAtLeastOne } from 'type-fest/source/require-at-least-one'; 7 | import { Message } from '@open-wa/wa-automate-types-only/dist/api/model/message'; 8 | const makeOptions = (useragentOverride: string | undefined) => ({ 9 | responseType: 'arraybuffer' as ResponseType, 10 | headers: { 11 | 'User-Agent': processUA(useragentOverride), 12 | 'DNT': 1, 13 | 'Upgrade-Insecure-Requests': 1, 14 | 'origin': 'https://web.whatsapp.com/', 15 | 'referer': 'https://web.whatsapp.com/' 16 | } 17 | }); 18 | 19 | const nonSizeTypes = [ 20 | "sticker" 21 | ] 22 | 23 | const timeout = (ms: number) => new Promise(res => setTimeout(res, ms)); 24 | export const mediaTypes : { 25 | [k: string] : string 26 | } = { 27 | IMAGE: 'Image', 28 | VIDEO: 'Video', 29 | AUDIO: 'Audio', 30 | PTT: 'Audio', 31 | DOCUMENT: 'Document', 32 | STICKER: 'Image' 33 | }; 34 | 35 | export type RequiredDecryptionMessage = { 36 | mediaKey: string, 37 | filehash: string, 38 | mimetype: string, 39 | type: string, 40 | size: number 41 | } 42 | 43 | export type DecryptableMessage = RequireAtLeastOne<{ 44 | clientUrl ?: string, 45 | deprecatedMms3Url ?: string, 46 | }, 'clientUrl' | 'deprecatedMms3Url'> & RequiredDecryptionMessage 47 | 48 | export class MissingCriticalDataError extends Error { 49 | constructor(public message: string) { 50 | super(); 51 | this.name = "MissingCriticalDataError" 52 | this.message = message; 53 | } 54 | } 55 | 56 | //@ts-ignore 57 | export const decryptMedia : (message: DecryptableMessage | Message | boolean, useragentOverride?: string) => Promise = async (message: DecryptableMessage | Message | boolean, useragentOverride?: string) => { 58 | const options = makeOptions(useragentOverride); 59 | if(!message || (message as any) === false || typeof message === "boolean") return new Error("Message is not a valid message"); 60 | let missingProps = []; 61 | message = message as DecryptableMessage; 62 | if (!message.mediaKey) missingProps.push('mediaKey'); 63 | if (!message.filehash) missingProps.push('filehash'); 64 | if (!message.mimetype) missingProps.push('mimetype'); 65 | if (!message.type) missingProps.push('type'); 66 | if (!message.size) missingProps.push('size'); 67 | 68 | if (!message || !message.mediaKey || !message.filehash || !message.mimetype || !message.type || !message.size) { 69 | if (missingProps.length == 1 && missingProps[0]==="size") { 70 | if(!nonSizeTypes.includes(message.type)) console.warn("@open-wa/wa-decrypt - WARN: size property is missing. File will fail an integrity check.") 71 | } 72 | else throw new MissingCriticalDataError(`Message is missing critical data: ${missingProps.join(', ')}`); 73 | } 74 | let haventGottenImageYet = true; 75 | let res: any; 76 | try { 77 | while (haventGottenImageYet) { 78 | res = await axios.get(message.deprecatedMms3Url.trim(), options); 79 | if (res.status == 200) { 80 | haventGottenImageYet = false; 81 | } else if (res.status == 404) { 82 | console.error('This media does not exist, or is no longer available on the server. Please see: https://docs.openwa.dev/pages/How%20to/decrypt-media.html#40439d') 83 | haventGottenImageYet = false; 84 | } else { 85 | await timeout(2000); 86 | } 87 | } 88 | } catch (error) { 89 | throw error 90 | } 91 | const buff = Buffer.from(res.data, 'binary'); 92 | return magix(buff, message.mediaKey, message.type, message.size, message.mimetype); 93 | }; 94 | 95 | const processUA = (userAgent: string | undefined) => { 96 | let ua = userAgent || 'WhatsApp/2.16.352 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36'; 97 | if (!ua.includes('WhatsApp')) ua = "WhatsApp/2.16.352 " + ua; 98 | return ua; 99 | } 100 | 101 | const magix = (fileData: any, mediaKeyBase64: any, mediaType: string, expectedSize?: number, mimetype ?: string) => { 102 | var encodedHex = fileData.toString('hex'); 103 | var encodedBytes = hexToBytes(encodedHex); 104 | var mediaKeyBytes: any = base64ToBytes(mediaKeyBase64); 105 | const info = `WhatsApp ${mediaTypes[mediaType.toUpperCase()] || mediaTypes[Object.keys(mediaTypes).filter(type=>mimetype.includes(type.toLowerCase()))[0]]} Keys`; 106 | const hash: string = 'sha256'; 107 | const salt: any = new Uint8Array(32); 108 | const expandedSize = 112; 109 | const mediaKeyExpanded = hkdf(mediaKeyBytes, expandedSize, { 110 | salt, 111 | info, 112 | hash 113 | }); 114 | var iv = mediaKeyExpanded.slice(0, 16); 115 | var cipherKey = mediaKeyExpanded.slice(16, 48); 116 | var decipher = crypto.createDecipheriv('aes-256-cbc', cipherKey, iv); 117 | var decoded: Buffer = decipher.update(encodedBytes); 118 | const mediaDataBuffer = expectedSize ? fixPadding(decoded, expectedSize) : decoded; 119 | return mediaDataBuffer; 120 | }; 121 | 122 | const fixPadding = (data: Buffer, expectedSize: number) => { 123 | let padding = (16 - (expectedSize % 16)) & 0xf; 124 | if (padding > 0) { 125 | if ((expectedSize + padding) == data.length) { 126 | // console.log(`trimmed: ${padding} bytes`); 127 | data = data.slice(0, data.length - padding); 128 | } else if ((data.length + padding) == expectedSize) { 129 | // console.log(`adding: ${padding} bytes`); 130 | let arr = new Uint16Array(padding).map(() => padding); 131 | data = Buffer.concat([data, Buffer.from(arr)]); 132 | } 133 | } 134 | //@ts-ignore 135 | return Buffer.from(data, 'utf-8'); 136 | }; 137 | 138 | 139 | const hexToBytes = (hexStr: any) => { 140 | var intArray = []; 141 | for (var i = 0; i < hexStr.length; i += 2) { 142 | intArray.push(parseInt(hexStr.substr(i, 2), 16)); 143 | } 144 | return new Uint8Array(intArray); 145 | }; 146 | 147 | const base64ToBytes = (base64Str: any) => { 148 | var binaryStr = atob(base64Str); 149 | var byteArray = new Uint8Array(binaryStr.length); 150 | for (var i = 0; i < binaryStr.length; i++) { 151 | byteArray[i] = binaryStr.charCodeAt(i); 152 | } 153 | return byteArray; 154 | }; 155 | 156 | /** 157 | * This removes all but the minimum required data to decrypt media. This can be useful to minimize sensitive data transport. Note, this deletes all information regarding where/who sent the message. 158 | */ 159 | export const bleachMessage = (m : { 160 | [k : string] : unknown 161 | }) => { 162 | var r = { ...m }; 163 | Object.keys(m).map(key => { 164 | if (!["type", "clientUrl", "mimetype", "mediaKey", "size", "filehash", "uploadhash", "deprecatedMms3Url"].includes(key)) delete r[key] 165 | }) 166 | return r; 167 | } 168 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import { DecryptableMessage, decryptMedia } from "../dist/decrypt"; 2 | import mime from "mime-types"; 3 | import fs from "fs"; 4 | import crypto from "crypto"; 5 | 6 | async function test() { 7 | //The absolute minimum data required to decrypt a file. This expires after a while. Add your own data here. 8 | const message : DecryptableMessage = { 9 | clientUrl: undefined, 10 | deprecatedMms3Url: "https://mmg.whatsapp.net/d/f/AlsIPPj-XJ0-HiLnMCyRmZRVIJWqP-l6L5FBKt_ybcad.enc", 11 | filehash: "D7dVGaQfR4lPKdWydw8u1jL/UD5pd/twJN0V6/WhY6w=", 12 | mediaKey: "PSHnGhaGa0wFQT9XxU0YkPFGpnrKYKN6mhe98gWSy2g=", 13 | mimetype: "image/jpeg", 14 | size: 34273, 15 | type: "image" 16 | } 17 | const filename = `${Date.now()}.${mime.extension(message.mimetype)}`; 18 | const mediaData = await decryptMedia(message); 19 | //Now confirm hash 20 | 21 | let output_hash = crypto 22 | .createHash("sha256") 23 | .update(mediaData) 24 | .digest("base64"); 25 | let hashValid = message.filehash == output_hash; 26 | console.log("Hash Validated:", hashValid); 27 | fs.writeFile(filename, mediaData, function (err) { 28 | if (err) { 29 | return console.log(err); 30 | } 31 | console.log("The file was saved!"); 32 | }); 33 | } 34 | 35 | test(); 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist" /* Redirect output structure to the directory. */, 15 | // "rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | "removeComments": true /* Do not emit comments to output. */, 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | // "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | }, 59 | "include": ["src/**/*"], 60 | "exclude": ["src/middleware/middleware.ts"] 61 | } 62 | --------------------------------------------------------------------------------