mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
-This is a break from previous versions unfortunately -Connecting is now a promise -Chats, contacts & previously unread messages are supplied on connection -Groups! -Message confirmations are more reliable -Timeout queries & connections
123 lines
5.5 KiB
JavaScript
123 lines
5.5 KiB
JavaScript
const Crypto = require("crypto")
|
|
const HKDF = require("futoin-hkdf")
|
|
const sharp = require("sharp")
|
|
const VideoThumb = require("video-thumb")
|
|
const fs = require("fs")
|
|
|
|
/*
|
|
Basic cryptographic utilities to interact with WhatsApp servers
|
|
*/
|
|
module.exports = {
|
|
// decrypt AES 256 CBC; where the IV is prefixed to the buffer
|
|
aesDecrypt: function (buffer, key) {
|
|
return this.aesDecryptWithIV(buffer.slice(16, buffer.length), key, buffer.slice(0,16))
|
|
},
|
|
// decrypt AES 256 CBC
|
|
aesDecryptWithIV: function (buffer, key, IV) {
|
|
const aes = Crypto.createDecipheriv('aes-256-cbc', key, IV )
|
|
return Buffer.concat( [ aes.update(buffer), aes.final() ] )
|
|
},
|
|
// encrypt AES 256 CBC; where a random IV is prefixed to the buffer
|
|
aesEncrypt: function (buffer, key) {
|
|
const IV = this.randomBytes(16)
|
|
const aes = Crypto.createCipheriv('aes-256-cbc', key, IV)
|
|
return Buffer.concat( [ IV, aes.update(buffer), aes.final() ] ) // prefix IV to the buffer
|
|
},
|
|
// encrypt AES 256 CBC with a given IV
|
|
aesEncrypWithIV: function (buffer, key, IV) {
|
|
const aes = Crypto.createCipheriv('aes-256-cbc', key, IV)
|
|
return Buffer.concat( [ aes.update(buffer), aes.final() ] ) // prefix IV to the buffer
|
|
},
|
|
// sign HMAC using SHA 256
|
|
hmacSign: function (buffer, key) {
|
|
return Crypto.createHmac('sha256', key).update(buffer).digest()
|
|
},
|
|
sha256: function (buffer) {
|
|
return Crypto.createHash('sha256').update(buffer).digest()
|
|
},
|
|
// HKDF key expansion
|
|
hkdf: function (buffer, expandedLength, info) {
|
|
return HKDF(buffer, expandedLength, {salt: Buffer.alloc(32), info: info, hash: 'SHA-256'})
|
|
},
|
|
// generates all the keys required to encrypt/decrypt & sign a media message
|
|
getMediaKeys: function (buffer, mediaType) {
|
|
// info to put into the HKDF key expansion
|
|
const appInfo = {
|
|
'imageMessage': 'WhatsApp Image Keys',
|
|
'videoMessage': 'WhatsApp Video Keys',
|
|
'audioMessage': 'WhatsApp Audio Keys',
|
|
'documentMessage': 'WhatsApp Document Keys',
|
|
'stickerMessage': 'WhatsApp Image Keys'
|
|
}
|
|
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
|
const expandedMediaKey = this.hkdf(buffer, 112, appInfo[mediaType])
|
|
return {
|
|
iv: expandedMediaKey.slice(0, 16),
|
|
cipherKey: expandedMediaKey.slice(16, 48),
|
|
macKey: expandedMediaKey.slice(48, 80)
|
|
}
|
|
},
|
|
// generates a thumbnail for a given media, if required
|
|
generateThumbnail: function (buffer, mediaType, info) {
|
|
let promise
|
|
if (info.thumbnail === null || info.thumbnail) { // don't do anything if the thumbnail is already provided, or is null
|
|
if (mediaType === 'audioMessage') {
|
|
promise = Promise.reject("audio messages cannot have thumbnails")
|
|
} else {
|
|
promise = Promise.resolve()
|
|
}
|
|
} else {
|
|
if (mediaType === 'imageMessage' || mediaType === 'stickerMessage') {
|
|
promise = sharp(buffer) // generate a 48x48 thumb
|
|
.resize(48, 48)
|
|
.jpeg()
|
|
.toBuffer()
|
|
.then (buffer => info.thumbnail = buffer.toString('base64'))
|
|
} else if (mediaType === 'videoMessage') {
|
|
const filename = "./" + this.randomBytes(5).toString("hex") + ".mp4"
|
|
fs.writeFileSync(filename, buffer)
|
|
|
|
promise = new Promise ( (resolve, reject) => {
|
|
VideoThumb.extract (filename, filename + ".png", "00:00:00", "48x48", (err) => {
|
|
if (err) {
|
|
console.log("could not generate video thumb: " + err)
|
|
resolve()
|
|
} else {
|
|
const buff = fs.readFileSync(filename + ".png")
|
|
return sharp(buff)
|
|
.jpeg()
|
|
.toBuffer()
|
|
.then (buffer => info.thumbnail = buffer.toString('base64'))
|
|
.then (() => {
|
|
fs.unlinkSync(filename)
|
|
fs.unlinkSync(filename + ".png")
|
|
resolve()
|
|
})
|
|
}
|
|
})
|
|
})
|
|
} else {
|
|
promise = Promise.resolve()
|
|
}
|
|
}
|
|
return promise
|
|
},
|
|
// generate a buffer with random bytes of the specified length
|
|
randomBytes: function (length) { return Crypto.randomBytes(length) },
|
|
promiseTimeout: function(ms, promise) {
|
|
// Create a promise that rejects in <ms> milliseconds
|
|
let timeout = new Promise((_, reject) => {
|
|
let id = setTimeout(() => {
|
|
clearTimeout(id)
|
|
reject('Timed out')
|
|
}, ms)
|
|
})
|
|
return Promise.race([promise, timeout])
|
|
},
|
|
// whatsapp requires a message tag for every message, we just use the timestamp as one
|
|
generateMessageTag: function () { return new Date().getTime().toString() },
|
|
// generate a random 16 byte client ID
|
|
generateClientID: function () { return this.randomBytes(16).toString('base64') },
|
|
// generate a random 10 byte ID to attach to a message
|
|
generateMessageID: function () { return this.randomBytes(10).toString('hex').toUpperCase() }
|
|
} |