瀏覽代碼

Implemented all crypto utilities.

painor 5 年之前
父節點
當前提交
4133f0da35
共有 7 個文件被更改,包括 392 次插入0 次删除
  1. 3 0
      .gitignore
  2. 4 0
      api/settings
  3. 73 0
      crypto/AES.js
  4. 20 0
      crypto/AuthKey.js
  5. 90 0
      crypto/Factorizator.js
  6. 72 0
      crypto/RSA.js
  7. 130 0
      utils/Helpers.js

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.idea/
+package-lock.json
+node_modules

+ 4 - 0
api/settings

@@ -0,0 +1,4 @@
+api_id=12345
+api_hash=0123456789abcdef0123456789abcdef
+user_phone=+34600000000
+session_name=anonymous

+ 73 - 0
crypto/AES.js

@@ -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 - 0
crypto/AuthKey.js

@@ -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 - 0
crypto/Factorizator.js

@@ -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 - 0
crypto/RSA.js

@@ -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 - 0
utils/Helpers.js

@@ -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;