Commit e826a640 authored by TJ Horner's avatar TJ Horner

Initial

parents
This diff is collapsed.
# Telegram Passport
This library lets you decrypt data from Telegram Passport. (It has no dependencies, too!)
## Usage
First, create a `TelegramPassport` object with your private key.
```js
const passport = new TelegramPassport(yourPrivateKey) // The private key must be PEM-encoded
```
After this, you are ready to decrypt payloads. To decrypt a [`PassportData`](https://core.telegram.org/bots/api#passportdata) object from Telegram, use the `decrypt` method.
```js
// Obtain passportData through some means
var decryptedData = passport.decrypt(passportData)
```
Here is an example of what the `decrypt` method will return for the scopes `personal_details`, `email`, `passport`, `identity_card`, and `utility_bill`.
```json
{
"payload": "bot_defined_payload",
"address": {
"data": {
"city": "Somecity",
"country_code": "US",
"post_code": "92069",
"state": "California",
"street_line1": "Address line 1",
"street_line2": ""
}
},
"identity_card": {
"data": {
"document_no": "12345",
"expiry_date": "01.23.2021"
},
"front_side": {
"file": {
"file_id": "SOME_FILE_ID",
"file_date": 1528299109
},
"secret": "BASE64_ENCODED_SECRET",
"hash": "BASE64_ENCODED_HASH"
},
"reverse_side": {
"file": {
"file_id": "SOME_FILE_ID",
"file_date": 1528299109
},
"secret": "BASE64_ENCODED_SECRET",
"hash": "BASE64_ENCODED_HASH"
}
},
"personal_details": {
"data": {
"birth_date": "01.23.2000",
"country_code": "US",
"first_name": "Mark",
"gender": "male",
"last_name": "Zuckerberg",
"residence_country_code": "US"
}
},
"utility_bill": {
"files": [
{
"file": {
"file_id": "SOME_FILE_ID",
"file_date": 1532621515
},
"secret": "BASE64_ENCODED_SECRET",
"hash": "BASE64_ENCODED_HASH"
},
{
"file": {
"file_id": "SOME_FILE_ID",
"file_date": 1532621515
},
"secret": "BASE64_ENCODED_SECRET",
"hash": "BASE64_ENCODED_HASH"
}
]
}
}
```
## Handling Files
Files are returned differently than the [`PassportFile`](https://core.telegram.org/bots/api#passportfile) object. Every file returned by this library is in this format:
```json
{
"file": {
"file_id": "SOME_FILE_ID",
"file_date": 1532621515
},
"secret": "BASE64_ENCODED_SECRET",
"hash": "BASE64_ENCODED_HASH"
}
```
`file` contains the original [`PassportFile`](https://core.telegram.org/bots/api#passportfile) object that was returned by the bot API, and `secret` / `hash` are the `secret` / `file_hash` from the [`FileCredentials`](https://core.telegram.org/passport#filecredentials), accordingly.
To download a file, call `getFile` like normal with the file ID. Once you get the file data, however, you must decrypt it. To do so, you can call the `decryptPassportCredentials` method:
```js
passport.decryptPassportCredentials(
fileData, // Should be a Buffer
Buffer.from(file.hash, "base64"), // It is assumed we are using our example object above
Buffer.from(file.secret, "base64")
)
```
This will return the decrypted JPEG image data.
## License
This library is licensed under the GNU GPL 3.0.
```
node-telegram-passport
Copyright (C) 2018 TJ Horner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
\ No newline at end of file
const crypto = require('crypto')
class TelegramPassport {
constructor(privateKey) {
this.privateKey = privateKey
}
decryptPassportCredentials(data, hash, secret) {
// Get the secret hash.
var hasher = crypto.createHash("sha512")
hasher.update(Buffer.concat([ secret, hash ]))
var secretHash = hasher.digest()
// Obtain key and IV.
var key = secretHash.slice(0, 32) // offset 0, length 32
var iv = secretHash.slice(32, 48) // offset 32, length 16
// Decrypt the data field.
var dataDecipher = crypto.createDecipheriv("aes-256-cbc", key, iv)
dataDecipher.setAutoPadding(false)
var dataCredentialsPadded = Buffer.concat([ dataDecipher.update(data), dataDecipher.final() ])
// Assert the data's integrity, else throw an error.
var integrityHasher = crypto.createHash("sha256")
integrityHasher.update(dataCredentialsPadded)
var integrityHash = integrityHasher.digest()
if(!hash.equals(integrityHash))
throw new Error(`The integrity of the data could not be verified (${hash.toString("hex")} != ${integrityHash.toString("hex")}). Please ensure you are passing the correct hash.`)
return JSON.parse(dataCredentialsPadded.slice(dataCredentialsPadded[0], dataCredentialsPadded.byteLength).toString())
}
decrypt(passport) {
// Decrypt the Passport secret.
var secret = crypto.privateDecrypt({ key: this.privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, Buffer.from(passport.credentials.secret, "base64"))
// Get the data credentials for each data field
var dataCredentials = this.decryptPassportCredentials(
Buffer.from(passport.credentials.data, "base64"),
Buffer.from(passport.credentials.hash, "base64"),
secret
)
var finalData = { payload: dataCredentials.payload }
for(var key in dataCredentials.secure_data) {
var secureData = dataCredentials.secure_data[key]
var dataField = passport.data.filter(field => field.type === key)[0]
finalData[key] = { }
// Decrypt the `data` field
if(secureData.data) {
finalData[key].data = this.decryptPassportCredentials(
Buffer.from(dataField.data, "base64"),
Buffer.from(secureData.data.data_hash, "base64"),
Buffer.from(secureData.data.secret, "base64")
)
}
// Pass the file, secret, and hash for each of these fields
// so they can be decrypted later (via `getFile`)
[ "front_side", "reverse_side", "selfie" ].forEach(field => {
if(secureData[field]) {
finalData[key][field] = {
file: dataField[field],
secret: secureData[field].file_hash,
hash: secureData[field].file_hash
}
}
})
// The same as above, but for file arrays.
if(secureData.files) {
finalData[key].files = [ ]
secureData.files.forEach((file, index) => {
finalData[key].files.push({
file: dataField.files[index],
secret: file.secret,
hash: file.file_hash
})
})
}
}
return finalData
}
}
module.exports = TelegramPassport
\ No newline at end of file
{
"name": "telegram-passport",
"version": "1.0.0",
"description": "Decrypt Telegram Passport data",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "TJ Horner <me@tjhorner.com>",
"license": "GPL-3.0"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment