Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented all crypto utilites. #1

Merged
merged 11 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
package-lock.json
node_modules
4 changes: 4 additions & 0 deletions api/settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
api_id=12345
api_hash=0123456789abcdef0123456789abcdef
user_phone=+34600000000
session_name=anonymous
73 changes: 73 additions & 0 deletions crypto/AES.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const TextEncoder = require("util").TextEncoder;
const TextDecoder = require("util").TextDecoder;
const aesjs = require('aes-js');


class AES {
static decryptIge(cipherText, key, iv) {
let iv1 = iv.slice(0, Math.floor(iv.length / 2));
let iv2 = iv.slice(Math.floor(iv.length / 2));
let plainText = new Array(cipherText.length).fill(0);
let aes = new aesjs.AES(key);
let blocksCount = Math.floor(plainText.length / 16);
let cipherTextBlock = new Array(16).fill(0);

for (let blockIndex = 0; blockIndex < blocksCount; blockIndex++) {
for (let i = 0; i < 16; i++) {
cipherTextBlock[i] = cipherText[blockIndex * 16 + i] ^ iv2[i];
}
let plainTextBlock = aes.decrypt(cipherTextBlock);
for (let i = 0; i < 16; i++) {
plainTextBlock[i] ^= iv1[i];
}

iv1 = cipherText.slice(blockIndex * 16, blockIndex * 16 + 16);
iv2 = plainTextBlock.slice(0, 16);
plainText = new Uint8Array([
...plainText.slice(0, blockIndex * 16),
...plainTextBlock.slice(0, 16),
...plainText.slice(blockIndex * 16 + 16)
]);

}
return plainText;
}

static encryptIge(plainText, key, iv) {
if (plainText.length % 16 !== 0) {
let padding = new Uint8Array(16 - plainText.length % 16);
plainText = new Uint8Array([
...plainText,
...padding,
]);
}
let iv1 = iv.slice(0, Math.floor(iv.length / 2));
let iv2 = iv.slice(Math.floor(iv.length / 2));
let aes = new aesjs.AES(key);
let blocksCount = Math.floor(plainText.length / 16);
let cipherText = new Array(plainText.length).fill(0);
for (let blockIndex = 0; blockIndex < blocksCount; blockIndex++) {
let plainTextBlock = plainText.slice(blockIndex * 16, blockIndex * 16 + 16);

for (let i = 0; i < 16; i++) {
plainTextBlock[i] ^= iv1[i];
}
let cipherTextBlock = aes.encrypt(plainTextBlock);
for (let i = 0; i < 16; i++) {
cipherTextBlock[i] ^= iv2[i];
}

iv1 = cipherTextBlock.slice(0, 16);
iv2 = plainText.slice(blockIndex * 16, blockIndex * 16 + 16);
cipherText = new Uint8Array([
...cipherText.slice(0, blockIndex * 16),
...cipherTextBlock.slice(0, 16),
...cipherText.slice(blockIndex * 16 + 16)
]);
}
return cipherText;
}

}

exports.AES = AES;
20 changes: 20 additions & 0 deletions crypto/AuthKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const helper = require("../utils/Helpers").helpers;

class AuthKey {
constructor(data) {
this.data = data;

let buffer = Buffer.from(helper.sha1(data));

this.aux_hash = buffer.slice(0, 8).readBigUInt64LE();
this.key_id = buffer.slice(12, 20).readBigUInt64LE();

}

calcNewNonceHash(new_nonce, number) {
let buffer = Buffer.concat([new_nonce, number, this.aux_hash]);
return helper.calcMsgKey(buffer);
}

}

90 changes: 90 additions & 0 deletions crypto/Factorizator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const helper = require("../utils/Helpers").helpers;

class Factorizator {

/**
* Finds the small multiplier by using Lopatin's method
* @param what
*/
static findSmallMultiplierLopatin(what) {
let g = 0;
for (let i = 0; i < 3; i++) {
let q = 30 || (helper.getRandomInt(0, 127) & 15) + 17;
let x = 40 || helper.getRandomInt(0, 1000000000) + 1;


let y = x;
let lim = 1 << (i + 18);
for (let j = 1; j < lim; j++) {
let a = x;
let b = x;

let c = q;
while (b !== 0) {
if ((b & 1) !== 0) {
c += a;
if (c >= what) {
c -= what;
}
}
a += a;
if (a >= what) {
a -= what;
}
b >>= 1;
}

x = c;
let z = ((x < y) ? (y - x) : (x - y));

g = this.gcd(z, what);
if (g !== 1) {
break
}

if ((j & (j - 1)) === 0) {
y = x;
}

}
if (g>1){
break;
}
}
let p = Math.floor(what / g);
return Math.min(p, g);
}

/**
* Calculates the greatest common divisor
* @param a
* @param b
* @returns {*}
*/
static gcd(a, b) {
while (((a !== 0) && (b !== 0))) {
while (((b & 1) === 0)) {
b >>= 1;
}
while (((a & 1) === 0)) {
a >>= 1;
}
if ((a > b)) {
a -= b;
} else {
b -= a;
}
}
return ((b === 0) ? a : b);
}

/**
* Factorizes the given number and returns both the divisor and the number divided by the divisor
* @param pq
* @returns {{divisor: *, divided: *}}
*/
static factorize(pq) {
let divisor = this.findSmallMultiplierLopatin(pq);
return {divisor: divisor, divided: Math.floor(pq / divisor)}
}
}
72 changes: 72 additions & 0 deletions crypto/RSA.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const crypto = require('crypto');
const helpers = require("../utils/Helpers").helpers;


console.log();

class RSA {
_server_keys = {
'216be86c022bb4c3': new RSAServerKey("216be86c022bb4c3", parseInt('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
'1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
'580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
'9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934' +
'EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F' +
'81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F' +
'6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
'5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 16), parseInt('010001', 16))
};

/**
* Encrypts the given data given a fingerprint
* @param fingerprint
* @param data
* @param offset
* @param length
*/
static encrypt(fingerprint, data, offset, length) {
if (!(fingerprint.toLowerCase() in RSA._server_keys)) {
return;
}
let key = RSA._server_keys[fingerprint.toLowerCase()];
return key.encrypt(data, offset, length);

}
}

class RSAServerKey {
constructor(fingerprint, m, e) {
this.fingerprint = fingerprint;
this.m = m;
this.e = e;

}

/**
* Encrypts the given data with the current key
* @param data
* @param offset
* @param length
*/
encrypt(data, offset, length) {
if (offset === undefined) {
offset = 0;
}
if (length === undefined) {
length = data.length;
}
let dataToWrite = data.split(offset, offset + length);
let sha1Data = helpers.sha1(dataToWrite);
let writer = Buffer.concat([sha1Data, dataToWrite]);

if (length < 235) {
writer = Buffer.concat([writer, helpers.generateRandomBytes(235 - length)]);

}
let result = writer.readBigInt64BE();
result = (result ** this.e) % this.m;
let buffer = Buffer.alloc(256);
buffer.writeBigInt64BE(result);
}
}

exports.RSA = RSA;
130 changes: 130 additions & 0 deletions utils/Helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const crypto = require('crypto');
const fs = require("fs").promises;

class Helpers {


/**
* Generates a random long integer (8 bytes), which is optionally signed
* @returns {number}
*/
static generateRandomLong() {
let buf = Buffer.from(this.generateRandomBytes(8)); // 0x12345678 = 305419896
return buf.readUInt32BE(0);
}

/**
* Generates a random bytes array
* @param count
* @returns {Buffer}
*/
static generateRandomBytes(count) {
return crypto.randomBytes(count);
}

/**
* Loads the user settings located under `api/`
* @param path
* @returns {Promise<void>}
*/
static async loadSettings(path = "../api/settings") {
let settings = {};
let left, right, value_pair;

let data = await fs.readFile(path, 'utf-8');

for (let line of data.toString().split('\n')) {
value_pair = line.split("=");
if (value_pair.length !== 2) {
break;
}
left = value_pair[0].replace(/ \r?\n|\r/g, '');
right = value_pair[1].replace(/ \r?\n|\r/g, '');
if (!isNaN(right)) {
settings[left] = Number.parseInt(right);
} else {
settings[left] = right;
}
}


return settings;


}

/**
* Calculate the key based on Telegram guidelines, specifying whether it's the client or not
* @param shared_key
* @param msg_key
* @param client
* @returns {[*, *]}
*/

static calcKey(shared_key, msg_key, client) {
let x = client !== null ? 0 : 8;
let iv, key, sha1a, sha1b, sha1c, sha1d;
sha1a = this.sha1((msg_key + shared_key.slice(x, (x + 32))));
sha1b = this.sha1(((shared_key.slice((x + 32), (x + 48)) + msg_key) + shared_key.slice((x + 48), (x + 64))));
sha1c = this.sha1((shared_key.slice((x + 64), (x + 96)) + msg_key));
sha1d = this.sha1((msg_key + shared_key.slice((x + 96), (x + 128))));
key = ((sha1a.slice(0, 8) + sha1b.slice(8, 20)) + sha1c.slice(4, 16));
iv = (((sha1a.slice(8, 20) + sha1b.slice(0, 8)) + sha1c.slice(16, 20)) + sha1d.slice(0, 8));
return [key, iv];


}

/**
* Calculates the message key from the given data
* @param data
* @returns {Buffer}
*/
static calcMsgKey(data) {
return this.sha1(data).slice(4, 20);


}

/**
* Generates the key data corresponding to the given nonces
* @param serverNonce
* @param newNonce
* @returns {{ivBuffer: Buffer, keyBuffer: Buffer}}
*/
static generateKeyDataFromNonces(serverNonce, newNonce) {
let hash1 = this.sha1(Buffer.concat([newNonce, serverNonce]));
let hash2 = this.sha1(Buffer.concat([serverNonce, newNonce]));
let hash3 = this.sha1(Buffer.concat([newNonce, newNonce]));
let keyBuffer = Buffer.concat([hash1, hash1.slice(0, 12)]);
let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
return {keyBuffer: keyBuffer, ivBuffer: ivBuffer}
}

/**
* Calculates the SHA1 digest for the given data
* @param data
* @returns {Buffer}
*/
static sha1(data) {
let shaSum = crypto.createHash('sha1');
shaSum.update(data);
return shaSum.digest();

}

/**
* returns a random int from min (inclusive) and max (inclusive)
* @param min
* @param max
* @returns {number}
*/
static getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}

}

exports.helpers = Helpers;