fix(core): continuous aes ctr

This commit is contained in:
teidesu 2021-04-15 19:39:41 +03:00
parent afa679cef4
commit 9e681cb13f
8 changed files with 128 additions and 129 deletions

View file

@ -13,5 +13,9 @@
}, },
"dependencies": { "dependencies": {
"@mtcute/tl": "^0.0.0" "@mtcute/tl": "^0.0.0"
},
"devDependencies": {
"@types/ws": "^7.4.1",
"ws": "^7.4.4"
} }
} }

View file

@ -16,17 +16,6 @@ export function typedArrayToBuffer(arr: NodeJS.TypedArray): Buffer {
Buffer.from(arr) Buffer.from(arr)
} }
export function reverseBuffer(buffer: Buffer): Buffer {
for (let i = 0, j = buffer.length - 1; i < j; ++i, --j) {
const t = buffer[j]
buffer[j] = buffer[i]
buffer[i] = t
}
return buffer
}
export function buffersEqual(a: Buffer, b: Buffer): boolean { export function buffersEqual(a: Buffer, b: Buffer): boolean {
if (a.length !== b.length) return false if (a.length !== b.length) return false

View file

@ -29,7 +29,7 @@ export interface ICryptoProvider {
rsaEncrypt(data: Buffer, key: TlPublicKey): MaybeAsync<Buffer> rsaEncrypt(data: Buffer, key: TlPublicKey): MaybeAsync<Buffer>
// in telegram, iv is always either used only once, or is the same for all calls for the key // in telegram, iv is always either used only once, or is the same for all calls for the key
createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme
createAesIge(key: Buffer, iv: Buffer): IEncryptionScheme createAesIge(key: Buffer, iv: Buffer): IEncryptionScheme
createAesEcb(key: Buffer): IEncryptionScheme createAesEcb(key: Buffer): IEncryptionScheme
@ -65,7 +65,7 @@ export abstract class BaseCryptoProvider implements ICryptoProvider {
return bigIntToBuffer(encryptedBigInt) return bigIntToBuffer(encryptedBigInt)
} }
abstract createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme abstract createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme
abstract createAesEcb(key: Buffer): IEncryptionScheme abstract createAesEcb(key: Buffer): IEncryptionScheme

View file

@ -15,41 +15,41 @@ export class ForgeCryptoProvider extends BaseCryptoProvider {
) )
} }
createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme { createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme {
return this._createAes(key, iv, 'CTR') const cipher = forge.cipher[encrypt ? 'createCipher' : 'createDecipher']('AES-CTR', key.toString('binary'))
cipher.start({ iv: iv.toString('binary') })
const update = (data: Buffer): Buffer => {
cipher.output.data = ''
cipher.update(forge.util.createBuffer(data.toString('binary')))
return Buffer.from(cipher.output.data, 'binary')
}
return {
encrypt: update,
decrypt: update
}
} }
createAesEcb(key: Buffer): IEncryptionScheme { createAesEcb(key: Buffer): IEncryptionScheme {
return this._createAes(key, null, 'ECB')
}
private _createAes(
key: Buffer,
iv: Buffer | null,
method: string
): IEncryptionScheme {
const methodName = `AES-${method}`
const keyBuffer = key.toString('binary') const keyBuffer = key.toString('binary')
const ivBuffer = iv ? iv.toString('binary') : undefined
return { return {
encrypt(data: Buffer) { encrypt(data: Buffer) {
const cipher = forge.cipher.createCipher(methodName, keyBuffer) const cipher = forge.cipher.createCipher('AES-ECB', keyBuffer)
if (method === 'ECB') cipher.start({})
cipher.mode.pad = cipher.mode.unpad = false cipher.mode.pad = cipher.mode.unpad = false
cipher.start(method === 'ECB' ? {} : { iv: ivBuffer })
cipher.update(forge.util.createBuffer(data.toString('binary'))) cipher.update(forge.util.createBuffer(data.toString('binary')))
cipher.finish() cipher.finish()
return Buffer.from(cipher.output.data, 'binary') return Buffer.from(cipher.output.data, 'binary')
}, },
decrypt(data: Buffer) { decrypt(data: Buffer) {
const cipher = forge.cipher.createDecipher( const cipher = forge.cipher.createDecipher(
methodName, 'AES-ECB',
keyBuffer keyBuffer
) )
if (method === 'ECB') cipher.start({})
cipher.mode.pad = cipher.mode.unpad = false cipher.mode.pad = cipher.mode.unpad = false
cipher.start(method === 'ECB' ? {} : { iv: ivBuffer })
cipher.update(forge.util.createBuffer(data.toString('binary'))) cipher.update(forge.util.createBuffer(data.toString('binary')))
cipher.finish() cipher.finish()
return Buffer.from(cipher.output.data, 'binary') return Buffer.from(cipher.output.data, 'binary')

View file

@ -9,29 +9,29 @@ export class NodeCryptoProvider extends BaseCryptoProvider {
throw new Error('Cannot use Node crypto functions outside NodeJS!') throw new Error('Cannot use Node crypto functions outside NodeJS!')
} }
createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme { createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme {
return this._createAes(key, iv, 'ctr') const cipher = nodeCrypto[encrypt ? 'createCipheriv' : 'createDecipheriv'](`aes-${key.length * 8}-ctr`, key, iv)
const update = (data: Buffer) => cipher.update(data)
return {
encrypt: update,
decrypt: update,
}
} }
createAesEcb(key: Buffer): IEncryptionScheme { createAesEcb(key: Buffer): IEncryptionScheme {
return this._createAes(key, null, 'ecb') const methodName = `aes-${key.length * 8}-ecb`
}
private _createAes(
key: Buffer,
iv: Buffer | null,
method: string
): IEncryptionScheme {
const methodName = `aes-${key.length * 8}-${method}`
return { return {
encrypt(data: Buffer) { encrypt(data: Buffer) {
const cipher = nodeCrypto.createCipheriv(methodName, key, iv) const cipher = nodeCrypto.createCipheriv(methodName, key, null)
if (method === 'ecb') cipher.setAutoPadding(false) cipher.setAutoPadding(false)
return Buffer.concat([cipher.update(data), cipher.final()]) return Buffer.concat([cipher.update(data), cipher.final()])
}, },
decrypt(data: Buffer) { decrypt(data: Buffer) {
const cipher = nodeCrypto.createDecipheriv(methodName, key, iv) const cipher = nodeCrypto.createDecipheriv(methodName, key, null)
if (method === 'ecb') cipher.setAutoPadding(false) cipher.setAutoPadding(false)
return Buffer.concat([cipher.update(data), cipher.final()]) return Buffer.concat([cipher.update(data), cipher.final()])
}, },
} }

View file

@ -4,30 +4,14 @@ import {
buffersEqual, buffersEqual,
cloneBuffer, cloneBuffer,
encodeUrlSafeBase64, encodeUrlSafeBase64,
isProbablyPlainText,
parseUrlSafeBase64, parseUrlSafeBase64,
randomBytes, randomBytes,
reverseBuffer,
telegramRleDecode, telegramRleDecode,
telegramRleEncode, telegramRleEncode,
xorBuffer, xorBuffer,
xorBufferInPlace, xorBufferInPlace,
} from '../src/utils/buffer-utils' } from '../src/utils/buffer-utils'
describe('reverseBuffer', () => {
it('should reverse even-sized buffers', () => {
const buf = Buffer.from([1, 2, 3, 4])
reverseBuffer(buf)
expect([...buf]).to.eql([4, 3, 2, 1])
})
it('should reverse odd-sized buffers', () => {
const buf = Buffer.from([1, 2, 3])
reverseBuffer(buf)
expect([...buf]).to.eql([3, 2, 1])
})
})
describe('buffersEqual', () => { describe('buffersEqual', () => {
it('should return true for equal buffers', () => { it('should return true for equal buffers', () => {
expect(buffersEqual(Buffer.from([]), Buffer.from([]))).is.true expect(buffersEqual(Buffer.from([]), Buffer.from([]))).is.true
@ -207,60 +191,60 @@ describe('telegramRleDecode', () => {
}) })
}) })
describe('isProbablyPlainText', () => { // describe('isProbablyPlainText', () => {
it('should return true for buffers only containing printable ascii', () => { // it('should return true for buffers only containing printable ascii', () => {
expect( // expect(
isProbablyPlainText(Buffer.from('hello this is some ascii text')) // isProbablyPlainText(Buffer.from('hello this is some ascii text'))
).to.be.true // ).to.be.true
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from( // Buffer.from(
'hello this is some ascii text\nwith unix new lines' // 'hello this is some ascii text\nwith unix new lines'
) // )
) // )
).to.be.true // ).to.be.true
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from( // Buffer.from(
'hello this is some ascii text\r\nwith windows new lines' // 'hello this is some ascii text\r\nwith windows new lines'
) // )
) // )
).to.be.true // ).to.be.true
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from( // Buffer.from(
'hello this is some ascii text\n\twith unix new lines and tabs' // 'hello this is some ascii text\n\twith unix new lines and tabs'
) // )
) // )
).to.be.true // ).to.be.true
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from( // Buffer.from(
'hello this is some ascii text\r\n\twith windows new lines and tabs' // 'hello this is some ascii text\r\n\twith windows new lines and tabs'
) // )
) // )
).to.be.true // ).to.be.true
}) // })
//
it('should return false for buffers containing some binary data', () => { // it('should return false for buffers containing some binary data', () => {
expect(isProbablyPlainText(Buffer.from('hello this is cedilla: ç'))).to // expect(isProbablyPlainText(Buffer.from('hello this is cedilla: ç'))).to
.be.false // .be.false
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from('hello this is some ascii text with emojis 🌸') // Buffer.from('hello this is some ascii text with emojis 🌸')
) // )
).to.be.false // ).to.be.false
//
// random strings of 16 bytes // // random strings of 16 bytes
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from('717f80f08eb9d88c3931712c0e2be32f', 'hex') // Buffer.from('717f80f08eb9d88c3931712c0e2be32f', 'hex')
) // )
).to.be.false // ).to.be.false
expect( // expect(
isProbablyPlainText( // isProbablyPlainText(
Buffer.from('20e8e218e54254c813b261432b0330d7', 'hex') // Buffer.from('20e8e218e54254c813b261432b0330d7', 'hex')
) // )
).to.be.false // ).to.be.false
}) // })
}) // })

View file

@ -46,16 +46,31 @@ export function testCryptoProvider(c: ICryptoProvider): void {
}) })
it('should encrypt and decrypt aes-ctr', async () => { it('should encrypt and decrypt aes-ctr', async () => {
const aes = c.createAesCtr( let aes = c.createAesCtr(
Buffer.from('8ddcf593fd74ec251038e459c165461f', 'hex'), Buffer.from('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b', 'hex'),
Buffer.from('0fea3601c60e770ac57ffe6b33ca8be1', 'hex') Buffer.from('0182de2bd789c295c3c6c875c5e9e190', 'hex'),
true
) )
expect((await aes.encrypt(Buffer.from('hello'))).toString('hex')).to.eq(
'4f6d702526' expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('a5fea1')
expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('ab51ca')
expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('365e5c')
expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('4b94a9')
expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('776387')
expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('c940be')
aes = c.createAesCtr(
Buffer.from('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b', 'hex'),
Buffer.from('0182de2bd789c295c3c6c875c5e9e190', 'hex'),
false
) )
expect(
(await aes.decrypt(Buffer.from('4f6d702526', 'hex'))).toString() expect((await aes.decrypt(Buffer.from('a5fea1', 'hex'))).toString('hex')).eq('010203')
).to.eq('hello') expect((await aes.decrypt(Buffer.from('ab51ca', 'hex'))).toString('hex')).eq('010203')
expect((await aes.decrypt(Buffer.from('365e5c', 'hex'))).toString('hex')).eq('010203')
expect((await aes.decrypt(Buffer.from('4b94a9', 'hex'))).toString('hex')).eq('010203')
expect((await aes.decrypt(Buffer.from('776387', 'hex'))).toString('hex')).eq('010203')
expect((await aes.decrypt(Buffer.from('c940be', 'hex'))).toString('hex')).eq('010203')
}) })
it('should encrypt and decrypt aes-ige', async () => { it('should encrypt and decrypt aes-ige', async () => {

View file

@ -2722,6 +2722,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/ws@^7.4.1":
version "7.4.1"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.1.tgz#49eacb15a0534663d53a36fbf5b4d98f5ae9a73a"
integrity sha512-ISCK1iFnR+jYv7+jLNX0wDqesZ/5RAeY3wUx6QaphmocphU61h+b+PHjS18TF4WIPTu/MMzxIq2PHr32o2TS5Q==
dependencies:
"@types/node" "*"
"@types/yargs-parser@*": "@types/yargs-parser@*":
version "20.2.0" version "20.2.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9"
@ -15016,7 +15023,7 @@ write-pkg@^4.0.0:
type-fest "^0.4.1" type-fest "^0.4.1"
write-json-file "^3.2.0" write-json-file "^3.2.0"
ws@7.4.4, ws@^7.3.0, ws@~7.4.2: ws@7.4.4, ws@^7.3.0, ws@^7.4.4, ws@~7.4.2:
version "7.4.4" version "7.4.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59"
integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==