diff --git a/basics.ts b/basics.ts index 0682618..1ab168e 100644 --- a/basics.ts +++ b/basics.ts @@ -103,3 +103,21 @@ export const detectSingleByteXor: DetectSingleByteXor = (buffs) => { candidates.sort((a, b) => b[0] - a[0]); return candidates[0][1]; }; + +type EncryptRepeatingXOR = (buff: Buffer, key: Buffer) => Buffer; +export const encryptRepeatingXOR: EncryptRepeatingXOR = (buff, key) => { + const resBuff = Buffer.alloc(buff.length); + range(0, buff.length).forEach(i => { + resBuff[i] = buff[i] ^ key[i % key.length]; + }); + return resBuff; +}; + +type DecryptRepeatingXOR = (buff: Buffer, key: Buffer) => Buffer; +export const decryptRepeatingXOR: DecryptRepeatingXOR = (buff, key) => { + const resBuff = Buffer.alloc(buff.length); + range(0, buff.length).forEach(i => { + resBuff[i] = buff[i] ^ key[i % key.length]; + }); + return resBuff; +}; diff --git a/data/repeatingXOR.txt b/data/repeatingXOR.txt new file mode 100644 index 0000000..ea4dafe --- /dev/null +++ b/data/repeatingXOR.txt @@ -0,0 +1,2 @@ +Burning 'em, if you ain't quick and nimble +I go crazy when I hear a cymbal \ No newline at end of file diff --git a/tests/basics.test.ts b/tests/basics.test.ts index 0be5a49..ada60a8 100644 --- a/tests/basics.test.ts +++ b/tests/basics.test.ts @@ -2,10 +2,10 @@ import test from 'node:test'; import * as path from 'node:path'; import * as assert from 'node:assert/strict'; -const DATA_PATH = path.join(__dirname, '..', 'data'); - import { range, fileToBuff } from '../utils'; -import { hexToBuff, buffTo64, xorBuffers, encryptSingleByte, decryptSingleByte, crackSingleByteKeyMsg, detectSingleByteXor } from '../basics'; +import { hexToBuff, buffTo64, xorBuffers, encryptSingleByte, decryptSingleByte, crackSingleByteKeyMsg, detectSingleByteXor, encryptRepeatingXOR, decryptRepeatingXOR } from '../basics'; + +const DATA_PATH = path.join(__dirname, '..', 'data'); test('range', () => { assert.deepEqual(range(0, 10), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -42,3 +42,18 @@ test('detect single byte xor', () => { const expected = 'Now that the party is jumping'; assert.equal(detectSingleByteXor(input), expected); }); + +test('encrypt/decrypt repeating xor is symmetric', () => { + const input = 'random input to test this stuff outeqw e991231293 129083 h1207018 9'; + const key = 'also random'; + assert.equal( + decryptRepeatingXOR(encryptRepeatingXOR(Buffer.from(input, 'utf-8'), Buffer.from(key, 'utf-8')), Buffer.from(key, 'utf-8')).toString('utf-8'), input); +}); + +test('encrypt repeating xor', () => { + const input = fileToBuff(path.join(DATA_PATH, 'repeatingXOR.txt'), 'utf-8'); + const parsedInput = Buffer.from(input.map(buff => buff.toString('utf-8')).join('\n')); + const key = 'ICE'; + const expected = '0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f'; + assert.equal(encryptRepeatingXOR(parsedInput, Buffer.from(key, 'utf-8')).toString('hex'), expected); +});