Skip to content

Commit

Permalink
feat: support Office Agile & RC4 Crypto API encrypted databases (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
andipaetzold authored Jan 14, 2022
1 parent 74eb8ca commit 6f017b0
Show file tree
Hide file tree
Showing 36 changed files with 903 additions and 65 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ yarn add mdb-reader
- Access 2016 (ACE16)
- Access 2019 (ACE17)

### Encryption

- Jet
- Office Agile
- Office RC4 Crypto API

## Usage

```javascript
Expand All @@ -61,7 +67,12 @@ class MDBReader {
/**
* @param buffer Buffer of the database.
*/
constructor(buffer: Buffer);
constructor(
buffer: Buffer,
options: {
password?: string;
}
);

/**
* Date when the database was created
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"url": "https://github.com/andipaetzold/mdb-reader/issues"
},
"homepage": "https://github.com/andipaetzold/mdb-reader#readme",
"dependencies": {
"fast-xml-parser": "^4.0.0"
},
"devDependencies": {
"@semantic-release/changelog": "6.0.1",
"@semantic-release/git": "10.0.1",
Expand Down
18 changes: 10 additions & 8 deletions src/Database.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { decryptRC4 } from "./crypto-util";
import { CodecHandler, createCodecHandler } from "./codec-handler";
import { decryptRC4 } from "./crypto";
import { readDateTime } from "./data/datetime";
import { getJetFormat, JetFormat } from "./JetFormat";
import { createPageDecrypter } from "./PageDecrypter";
import { PageDecrypter } from "./PageDecrypter/types";
import PageType, { assertPageType } from "./PageType";
import { SortOrder } from "./types";
import { uncompressText } from "./unicodeCompression";
Expand All @@ -13,19 +12,22 @@ const PASSWORD_OFFSET = 0x42;
export default class Database {
public readonly format: JetFormat;

private readonly pageDecrypter: PageDecrypter;
private readonly codecHandler: CodecHandler;
private readonly databaseDefinitionPage: Buffer;

public constructor(private readonly buffer: Buffer) {
public constructor(private readonly buffer: Buffer, readonly password: string) {
assertPageType(this.buffer, PageType.DatabaseDefinitionPage);

this.format = getJetFormat(this.buffer);

this.databaseDefinitionPage = Buffer.alloc(this.format.pageSize);
this.buffer.copy(this.databaseDefinitionPage, 0, 0, this.format.pageSize);
decryptHeader(this.databaseDefinitionPage, this.format);
this.codecHandler = createCodecHandler(this.databaseDefinitionPage, password);

this.pageDecrypter = createPageDecrypter(this.databaseDefinitionPage, "");
if (!this.codecHandler.verifyPassword()) {
throw new Error("Wrong password");
}
}

public getPassword(): string | null {
Expand Down Expand Up @@ -106,7 +108,7 @@ export default class Database {
}

const pageBuffer = this.buffer.slice(offset, offset + this.format.pageSize);
return this.pageDecrypter(pageBuffer, page);
return this.codecHandler.decryptPage(pageBuffer, page);
}

/**
Expand Down Expand Up @@ -146,7 +148,7 @@ const ENCRYPTION_KEY = Buffer.from([0xc7, 0xda, 0x39, 0x6b]);
function decryptHeader(buffer: Buffer, format: JetFormat): void {
const decryptedBuffer = decryptRC4(
ENCRYPTION_KEY,
buffer.slice(ENCRYPTION_START, ENCRYPTION_START + format.databaseDefinitionPage.encryptedSize),
buffer.slice(ENCRYPTION_START, ENCRYPTION_START + format.databaseDefinitionPage.encryptedSize)
);
decryptedBuffer.copy(buffer, ENCRYPTION_START);
}
8 changes: 6 additions & 2 deletions src/MDBReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import { SortOrder } from "./types";
const MSYS_OBJECTS_TABLE = "MSysObjects";
const MSYS_OBJECTS_PAGE = 2;

interface Options {
password?: string;
}

export default class MDBReader {
private readonly sysObjects: SysObject[];
private readonly db: Database;

/**
* @param buffer Buffer of the database.
*/
public constructor(private readonly buffer: Buffer) {
public constructor(private readonly buffer: Buffer, { password }: Options = {}) {
assertPageType(this.buffer, PageType.DatabaseDefinitionPage);

this.db = new Database(this.buffer);
this.db = new Database(this.buffer, password ?? "");

const mSysObjectsTable = new Table(MSYS_OBJECTS_TABLE, this.db, MSYS_OBJECTS_PAGE).getData<{
Id: number;
Expand Down
5 changes: 0 additions & 5 deletions src/PageDecrypter/IdentityDecrypter.ts

This file was deleted.

21 changes: 0 additions & 21 deletions src/PageDecrypter/JetPageDecrypter.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/PageDecrypter/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/PageDecrypter/types.ts

This file was deleted.

20 changes: 20 additions & 0 deletions src/codec-handler/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CodecHandler } from ".";
import { getJetFormat } from "../JetFormat";
import { CodecType } from "../JetFormat/types";
import { createIdentityHandler } from "./handlers/identity";
import { createJetCodecHandler } from "./handlers/jet";
import { createOfficeCodecHandler } from "./handlers/office";

export function createCodecHandler(databaseDefinitionPage: Buffer, password: string): CodecHandler {
const format = getJetFormat(databaseDefinitionPage);
switch (format.codecType) {
case CodecType.JET:
return createJetCodecHandler(databaseDefinitionPage);

case CodecType.OFFICE:
return createOfficeCodecHandler(databaseDefinitionPage, password);

default:
return createIdentityHandler();
}
}
8 changes: 8 additions & 0 deletions src/codec-handler/handlers/identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { CodecHandler } from "../types";

export function createIdentityHandler(): CodecHandler {
return {
decryptPage: (b) => b,
verifyPassword: () => true
};
}
27 changes: 27 additions & 0 deletions src/codec-handler/handlers/jet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { CodecHandler } from "..";
import { decryptRC4 } from "../../crypto";
import { isEmptyBuffer } from "../../util";
import { createIdentityHandler } from "./identity";
import { DecryptPage } from "../types";
import { getPageEncodingKey } from "../util";

const KEY_OFFSET = 0x3e; // 62
const KEY_SIZE = 4;

export function createJetCodecHandler(databaseDefinitionPage: Buffer): CodecHandler {
const encodingKey = databaseDefinitionPage.slice(KEY_OFFSET, KEY_OFFSET + KEY_SIZE);

if (isEmptyBuffer(encodingKey)) {
return createIdentityHandler();
}

const decryptPage: DecryptPage = (pageBuffer, pageIndex) => {
const pagekey = getPageEncodingKey(encodingKey, pageIndex);
return decryptRC4(pagekey, pageBuffer);
};

return {
decryptPage,
verifyPassword: () => true, // TODO
};
}
43 changes: 43 additions & 0 deletions src/codec-handler/handlers/office/CryptoAlgorithm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export interface CryptoAlgorithm {
readonly id: number;
readonly encryptionVerifierHashLength: number;
readonly keySizeMin: number;
readonly keySizeMax: number;
}

const EXTERNAL: CryptoAlgorithm = {
id: 0,
encryptionVerifierHashLength: 0,
keySizeMin: 0,
keySizeMax: 0,
};

const RC4: CryptoAlgorithm = {
id: 0x6801,
encryptionVerifierHashLength: 20,
keySizeMin: 0x28,
keySizeMax: 0x200,
};

const AES_128: CryptoAlgorithm = {
id: 0x6801,
encryptionVerifierHashLength: 32,
keySizeMin: 0x80,
keySizeMax: 0x80,
};

const AES_192: CryptoAlgorithm = {
id: 0x660f,
encryptionVerifierHashLength: 32,
keySizeMin: 0xc0,
keySizeMax: 0xc0,
};

const AES_256: CryptoAlgorithm = {
id: 0x6610,
encryptionVerifierHashLength: 32,
keySizeMin: 0x100,
keySizeMax: 0x100,
};

export const CRYPTO_ALGORITHMS = { EXTERNAL, RC4, AES_128, AES_192, AES_256 };
Loading

0 comments on commit 6f017b0

Please sign in to comment.