steam-appticket-2
Version:
Decrypts and parses Steam app tickets
113 lines (100 loc) • 3.81 kB
JavaScript
var Crypto = require('crypto');
var g_PubkeySystem = `-----BEGIN PUBLIC KEY-----
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDf7BrWLBBmLBc1OhSwfFkRf53T
2Ct64+AVzRkeRuh7h3SiGEYxqQMUeYKO6UWiSRKpI2hzic9pobFhRr3Bvr/WARvY
gdTckPv+T1JzZsuVcNfFjrocejN1oWI0Rrtgt4Bo+hOneoo3S57G9F1fOpn5nsQ6
6WOiu4gZKODnFMBCiQIBEQ==
-----END PUBLIC KEY-----`;
/**
* 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();
};