diff --git a/src/utils/tea.test.ts b/src/utils/tea.test.ts new file mode 100644 index 0000000..fc5741c --- /dev/null +++ b/src/utils/tea.test.ts @@ -0,0 +1,69 @@ +// Copyright 2021 MengYX. All rights reserved. +// +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in https://go.dev/LICENSE. + +import {TeaCipher} from "@/utils/tea"; + +test("key size", () => { + const testKey = new Uint8Array([ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x00 + ]) + expect(() => new TeaCipher(testKey.slice(0, 16))) + .not.toThrow() + + expect(() => new TeaCipher(testKey)) + .toThrow() + + expect(() => new TeaCipher(testKey.slice(0, 15))) + .toThrow() + +}) + + +const teaTests = [ + // These were sourced from https://github.com/froydnj/ironclad/blob/master/testing/test-vectors/tea.testvec + { + rounds: TeaCipher.numRounds, + key: new Uint8Array([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + plainText: new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + cipherText: new Uint8Array([0x41, 0xea, 0x3a, 0x0a, 0x94, 0xba, 0xa9, 0x40]), + }, + { + rounds: TeaCipher.numRounds, + key: new Uint8Array([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), + plainText: new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), + cipherText: new Uint8Array([0x31, 0x9b, 0xbe, 0xfb, 0x01, 0x6a, 0xbd, 0xb2]), + }, + { + rounds: 16, + key: new Uint8Array([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + plainText: new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + cipherText: new Uint8Array([0xed, 0x28, 0x5d, 0xa1, 0x45, 0x5b, 0x33, 0xc1]), + }, +] + + +test("encrypt & decrypt", () => { + for (const tt of teaTests) { + const c = new TeaCipher(tt.key, tt.rounds) + + const buf = new Uint8Array(8) + const bufView = new DataView(buf.buffer) + + c.encrypt(bufView, new DataView(tt.plainText.buffer)) + expect(buf).toStrictEqual(tt.cipherText) + + c.decrypt(bufView, new DataView(tt.cipherText.buffer)) + expect(buf).toStrictEqual(tt.plainText) + } +}) diff --git a/src/utils/tea.ts b/src/utils/tea.ts new file mode 100644 index 0000000..22366c3 --- /dev/null +++ b/src/utils/tea.ts @@ -0,0 +1,85 @@ +// Copyright 2021 MengYX. All rights reserved. +// +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in https://go.dev/LICENSE. + +// TeaCipher is a typescript port to golang.org/x/crypto/tea + +// Package tea implements the TEA algorithm, as defined in Needham and +// Wheeler's 1994 technical report, “TEA, a Tiny Encryption Algorithm”. See +// http://www.cix.co.uk/~klockstone/tea.pdf for details. +// +// TEA is a legacy cipher and its short block size makes it vulnerable to +// birthday bound attacks (see https://sweet32.info). It should only be used +// where compatibility with legacy systems, not security, is the goal. + +export class TeaCipher { + // BlockSize is the size of a TEA block, in bytes. + static readonly BlockSize = 8; + + // KeySize is the size of a TEA key, in bytes. + static readonly KeySize = 16; + + // delta is the TEA key schedule constant. + static readonly delta = 0x9e3779b9; + + // numRounds 64 is the standard number of rounds in TEA. + static readonly numRounds = 64; + + k0: number + k1: number + k2: number + k3: number + rounds: number + + constructor(key: Uint8Array, rounds: number = TeaCipher.numRounds) { + if (key.length != 16) { + throw Error("incorrect key size") + } + if ((rounds & 1) != 0) { + throw Error("odd number of rounds specified") + } + + const k = new DataView(key.buffer) + this.k0 = k.getUint32(0, false) + this.k1 = k.getUint32(4, false) + this.k2 = k.getUint32(8, false) + this.k3 = k.getUint32(12, false) + this.rounds = rounds + } + + + encrypt(dst: DataView, src: DataView) { + if (src.byteLength != 8) { + throw Error("src.byteLength != 8") + } + + let v0 = src.getUint32(0, false) + let v1 = src.getUint32(4, false) + + let sum = 0 + for (let i = 0; i < this.rounds / 2; i++) { + sum = sum + TeaCipher.delta + v0 += ((v1 << 4) + this.k0) ^ (v1 + sum) ^ ((v1 >>> 5) + this.k1) + v1 += ((v0 << 4) + this.k2) ^ (v0 + sum) ^ ((v0 >>> 5) + this.k3) + } + + dst.setUint32(0, v0, false) + dst.setUint32(4, v1, false) + } + + decrypt(dst: DataView, src: DataView) { + let v0 = src.getUint32(0, false) + let v1 = src.getUint32(4, false) + + let sum = TeaCipher.delta * this.rounds / 2 + for (let i = 0; i < this.rounds / 2; i++) { + v1 -= ((v0 << 4) + this.k2) ^ (v0 + sum) ^ ((v0 >>> 5) + this.k3) + v0 -= ((v1 << 4) + this.k0) ^ (v1 + sum) ^ ((v1 >>> 5) + this.k1) + sum -= TeaCipher.delta + } + dst.setUint32(0, v0, false) + dst.setUint32(4, v1, false) + } +}