forked from seishun/node-steam-crypto
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathindex.js
More file actions
108 lines (95 loc) · 3.5 KB
/
index.js
File metadata and controls
108 lines (95 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
var Crypto = require('crypto');
var g_PubkeySystem = require('fs').readFileSync(__dirname + '/system.pem');
/**
* Verifies a signature using the Steam "System" public key.
* @param {Buffer} data
* @param {Buffer} signature
* @param {string} algorithm
*/
exports.verifySignature = function(data, signature, algorithm) {
var verify = Crypto.createVerify(algorithm || "RSA-SHA1");
verify.update(data);
verify.end();
return verify.verify(g_PubkeySystem, signature);
};
/**
* Generate a 32-byte symmetric session key and encrypt it with Steam's public "System" key.
* @param {Buffer} [nonce] - If provided, will be appended to the session key when encrypting
* @returns {{plain: Buffer, encrypted: Buffer}}
*/
exports.generateSessionKey = function(nonce) {
var sessionKey = Crypto.randomBytes(32);
var cryptedSessionKey = Crypto.publicEncrypt(g_PubkeySystem, Buffer.concat([sessionKey, nonce || new Buffer(0)]));
return {
plain: sessionKey,
encrypted: cryptedSessionKey
};
};
/**
* AES-encrypt some data with a symmetric key.
* @param {Buffer} input
* @param {Buffer} key
* @param {Buffer} [iv] - If not provided, one will be generated randomly
* @returns {Buffer}
*/
exports.symmetricEncrypt = function(input, key, iv) {
iv = iv || Crypto.randomBytes(16);
var aesIv = Crypto.createCipheriv('aes-256-ecb', key, '');
aesIv.setAutoPadding(false);
aesIv.end(iv);
var aesData = Crypto.createCipheriv('aes-256-cbc', key, iv);
aesData.end(input);
return Buffer.concat([aesIv.read(), aesData.read()]);
};
/**
* AES-encrypt some data with a symmetric key, and add an HMAC.
* @param {Buffer} input
* @param {Buffer} key
*/
exports.symmetricEncryptWithHmacIv = function(input, key) {
// IV is HMAC-SHA1(Random(3) + Plaintext) + Random(3). (Same random values for both)
var random = Crypto.randomBytes(3);
var hmac = Crypto.createHmac("sha1", key.slice(0, 16)); // we only want the first 16 bytes of the key for the hmac
hmac.update(random);
hmac.update(input);
return exports.symmetricEncrypt(input, key, Buffer.concat([hmac.digest().slice(0, 16 - random.length), random])); // the resulting IV must be 16 bytes long, so truncate the hmac to make room for the random
};
/**
* AES-decrypt some data with a symmetric key, and optionally check an HMAC.
* @param {Buffer} input
* @param {Buffer} key
* @param {boolean} [checkHmac=false] - Does this data contain an HMAC for us to check?
* @returns {*}
*/
exports.symmetricDecrypt = function(input, key, checkHmac) {
var aesIv = Crypto.createDecipheriv('aes-256-ecb', key, '');
aesIv.setAutoPadding(false);
aesIv.end(input.slice(0, 16));
var iv = aesIv.read();
var aesData = Crypto.createDecipheriv('aes-256-cbc', key, iv);
aesData.end(input.slice(16));
var plaintext = aesData.read();
if (checkHmac) {
// The last 3 bytes of the IV are a random value, and the remainder are a partial HMAC
var remotePartialHmac = iv.slice(0, iv.length - 3);
var random = iv.slice(iv.length - 3, iv.length);
var hmac = Crypto.createHmac("sha1", key.slice(0, 16));
hmac.update(random);
hmac.update(plaintext);
if (!remotePartialHmac.equals(hmac.digest().slice(0, remotePartialHmac.length))) {
throw new Error("Received invalid HMAC from remote host.");
}
}
return plaintext;
};
/**
* Decrypt something that was encrypted with AES/ECB/PKCS7
* @param {Buffer} input
* @param {Buffer} key
* @returns {Buffer}
*/
exports.symmetricDecryptECB = function(input, key) {
var decipher = Crypto.createDecipheriv('aes-256-ecb', key, '');
decipher.end(input);
return decipher.read();
};