|
| 1 | +/// <reference path='../../../third_party/simple-rc4/simple-rc4.d.ts' /> |
| 2 | +/// <reference path='../../../third_party/typings/browser.d.ts' /> |
| 3 | + |
| 4 | +import crypto = require('crypto'); |
| 5 | +import logging = require('../logging/logging'); |
| 6 | +import rc4 = require('simple-rc4'); |
| 7 | +import transformer = require('./transformer'); |
| 8 | + |
| 9 | +const log = new logging.Log('rc4 transformer'); |
| 10 | + |
| 11 | +// Accepted in serialised form by configure(). |
| 12 | +export interface Config { |
| 13 | + key: string |
| 14 | +} |
| 15 | + |
| 16 | +const KEY_LENGTH_BYTES = 16; |
| 17 | +const R_LENGTH_BYTES = 8; |
| 18 | +const TRUNCATED_IV_LENGTH_BYTES = 8; |
| 19 | + |
| 20 | +// Creates a sample (non-random) config, suitable for testing. |
| 21 | +export function sampleConfig(): Config { |
| 22 | + return { |
| 23 | + key: new Buffer(KEY_LENGTH_BYTES).fill(0).toString('hex') |
| 24 | + }; |
| 25 | +} |
| 26 | + |
| 27 | +// Fast, uniformly random transformer with minimal length expansion, via RC4. |
| 28 | +// From a proposal by kpdyer. |
| 29 | +// |
| 30 | +// Terminology: |
| 31 | +// - K: 128-bit session key, negotiated through the signalling channel |
| 32 | +// - RANDOM(N): a cryptographically secure N-byte string |
| 33 | +// - X[n,...,m]: the (m - n + 1) bytes starting at byte-index n of X |
| 34 | +// - |X|: length, in bytes, of X |
| 35 | +// |
| 36 | +// Pseudo-code: |
| 37 | +// def Encrypt(K, P): |
| 38 | +// R = RANDOM(8) |
| 39 | +// IV = SHA1(K || R) |
| 40 | +// RET R || RC4(IV[0,...,7], P) |
| 41 | +// |
| 42 | +// def Decrypt(K, C): |
| 43 | +// R = C[0,...,7] |
| 44 | +// IV = SHA1(K || R) |
| 45 | +// RET RC4(IV[0,...,7], C[8,...,|C | -1]) |
| 46 | +export class Rc4Transformer implements transformer.Transformer { |
| 47 | + private key_: Buffer; |
| 48 | + |
| 49 | + public constructor() { |
| 50 | + this.configure(JSON.stringify(sampleConfig())); |
| 51 | + } |
| 52 | + |
| 53 | + public configure = (json: string): void => { |
| 54 | + try { |
| 55 | + const config = <Config>JSON.parse(json); |
| 56 | + if (config.key === undefined) { |
| 57 | + throw new Error('must set key parameter'); |
| 58 | + } |
| 59 | + const key = new Buffer(config.key, 'hex'); |
| 60 | + if (key.byteLength !== KEY_LENGTH_BYTES) { |
| 61 | + throw new Error('keys must be ' + KEY_LENGTH_BYTES + ' bytes in length'); |
| 62 | + } |
| 63 | + this.key_ = key; |
| 64 | + } catch (e) { |
| 65 | + throw new Error('could not parse config: ' + e.message); |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + // Applies RC4(IV[0,...,7] to bytes, as described in the pseudocode above. |
| 70 | + private update_ = (r: Buffer, bytes:Buffer): void => { |
| 71 | + const hasher = crypto.createHash('sha1'); |
| 72 | + const iv = hasher.update(Buffer.concat([this.key_, r])); |
| 73 | + const truncatedIv = iv.digest().slice(0, TRUNCATED_IV_LENGTH_BYTES); |
| 74 | + new rc4(truncatedIv).update(bytes); |
| 75 | + } |
| 76 | + |
| 77 | + public transform = (ab: ArrayBuffer): ArrayBuffer[] => { |
| 78 | + const p = new Buffer(ab); |
| 79 | + |
| 80 | + const r = crypto.randomBytes(R_LENGTH_BYTES); |
| 81 | + this.update_(r, p); |
| 82 | + |
| 83 | + return [Buffer.concat([r, p]).buffer]; |
| 84 | + } |
| 85 | + |
| 86 | + public restore = (ab: ArrayBuffer): ArrayBuffer[] => { |
| 87 | + const c = new Buffer(ab); |
| 88 | + |
| 89 | + const r = c.slice(0, R_LENGTH_BYTES); |
| 90 | + const tail = c.slice(R_LENGTH_BYTES); |
| 91 | + this.update_(r, tail); |
| 92 | + |
| 93 | + // Because tail is constructed via Buffer#slice, its buffer field |
| 94 | + // still references ab, which still includes r. |
| 95 | + const slicedResult = tail.buffer.slice(8); |
| 96 | + return [slicedResult]; |
| 97 | + } |
| 98 | +} |
0 commit comments