diff --git a/src/ssn/decryption/decryptChunk.ts b/src/ssn/decryption/decryptChunk.ts new file mode 100644 index 0000000..7966e81 --- /dev/null +++ b/src/ssn/decryption/decryptChunk.ts @@ -0,0 +1,25 @@ +import updateKeys from './lib/updateKeys'; + +export default function decryptChunk(encryptedChunk: Buffer, [key0, key1, key2]: [number, number, number]): Buffer { + const dv = new DataView(encryptedChunk.buffer); + + const decryptedChunk = new Buffer(encryptedChunk.length); + const dvOut = new DataView(decryptedChunk.buffer); + + for (let i = 0; i < length; i += 1) { + //read and decrypt byte + let curChar = dv.getUint8(i); + const keyPart = (key2 | 2) & 0xFFFF; + const decryptedByte = (keyPart * (keyPart ^ 1)) >>> 8; + curChar ^= decryptedByte & 0xFF; + //Skip the first 12 bytes (random encryption header) + if (i >= 12) { + dvOut.setUint8(i - 12, curChar); + } + + //update keys + [key0, key1, key2] = updateKeys([key0, key1, key2], curChar); + } + + return decryptedChunk; +} diff --git a/src/ssn/extractFileStream.ts b/src/ssn/extractFileStream.ts index 0f6a37f..a074647 100644 --- a/src/ssn/extractFileStream.ts +++ b/src/ssn/extractFileStream.ts @@ -3,6 +3,7 @@ import * as stream from 'stream'; import * as zlib from 'zlib'; import { ISsnFileEntry } from '../interfaces/ISsnFileEntry'; +import decryptStream from './streams/decryptStream'; import streamSetMaxLength from './streams/streamSetMaxLength'; /** Extracts the file with the given metadata from the stream. @@ -25,22 +26,28 @@ export default function extractFileStream(file: ISsnFileEntry, inputStream: stre //skip local file name and extra field inputStream.read(localFilenameSize + localExtraSize); - //TODO: pipe into decryption if file is encrypted - const decryptTransform = new stream.Transform({ - read() { - //... - }, - }); - inputStream.pipe(decryptTransform); + //------------------------------------------------- - //TODO: pipe into decompression if file is compressed + let curStream = inputStream; + + //set max length + const maxLength = streamSetMaxLength(curStream, file.compressedSize); + curStream = maxLength; + + //pipe into decryption if file is encrypted + if (file.decryptionKeys !== undefined) { + const decryptTransform = decryptStream(curStream, file.decryptionKeys); + curStream = decryptTransform; + } + + //pipe into decompression const decompressTransform = zlib.createInflateRaw(); - decryptTransform.pipe(decompressTransform); + curStream.pipe(decompressTransform); + curStream = decompressTransform; - //TODO: output file - return streamSetMaxLength(decompressTransform, file.size); + //set max length + const maxLength2 = streamSetMaxLength(curStream, file.size); + curStream = maxLength2; - /*const out = new stream.Readable(); - - return out;*/ + return curStream; } diff --git a/src/ssn/streams/decryptStream.ts b/src/ssn/streams/decryptStream.ts new file mode 100644 index 0000000..a805e66 --- /dev/null +++ b/src/ssn/streams/decryptStream.ts @@ -0,0 +1,30 @@ +import * as stream from 'stream'; +import decryptChunk from '../decryption/decryptChunk'; + +export default function decryptStream(inputStream: stream.Readable, decryptionKeys: [number, number, number]): stream.Readable { + let skippedRandomHeader = false; + + const outStream = new stream.Readable({ + encoding: 'binary', + read(size) { + //There are 12 random bytes at the beginning, we need to use them to initialize the decryption keys, but we can ignore the decrypted bytes. + if (!skippedRandomHeader) { + const encryptedHeader = inputStream.read(12); + decryptChunk(encryptedHeader, decryptionKeys); + skippedRandomHeader = true; + } + + //Decrypt chunk + const encryptedChunk = inputStream.read(size); + const decryptedChunk = decryptChunk(encryptedChunk, decryptionKeys); + + this.push(decryptedChunk); + }, + }); + + inputStream.on('end', () => { + outStream.emit('end'); + }); + + return outStream; +}