Skip to content

Commit 994b158

Browse files
committed
Fix decryption algorithm
1 parent aa1f4ae commit 994b158

File tree

4 files changed

+26
-29
lines changed

4 files changed

+26
-29
lines changed

apps/rn/src/tests/assets.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { encodeToBase64 } from 'pdf-lib';
33

44
const PDF_PATH = `${RNFetchBlob.fs.dirs.DocumentDir}/out.pdf`;
55

6-
export const writePdf = async (pdfBytes, chunkSize = 100000): Promise<string> =>
6+
export const writePdf = async (pdfBytes, chunkSize = 100000) =>
77
new Promise((resolve) => {
88
const writes = [];
99
RNFetchBlob.fs.writeStream(PDF_PATH, 'base64').then((stream) => {

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cantoo/pdf-lib",
3-
"version": "1.19.0",
3+
"version": "1.19.1",
44
"description": "Create and modify PDF files with JavaScript",
55
"author": "Andrew Dillon <andrew.dillon.j@gmail.com>",
66
"contributors": [
@@ -33,7 +33,7 @@
3333
"François Billioud (https://github.com/Sharcoux)"
3434
],
3535
"scripts": {
36-
"release": "yarn build && yarn release:latest && git push origin master",
36+
"release": "yarn build && yarn release:latest && git add . && git commit -m \"v$(yarn --silent get:version)\" && git push origin master",
3737
"release:latest": "yarn publish --tag latest && yarn pack",
3838
"release:next": "yarn publish --tag next",
3939
"release:prep": "yarn clean && yarn lint && yarn typecheck && yarn test && yarn build",
@@ -56,6 +56,7 @@
5656
"build:umd": "rollup --config rollup.config.js --file dist/pdf-lib.js --environment MODULE_TYPE:umd",
5757
"build:umd:min": "rollup --config rollup.config.js --file dist/pdf-lib.min.js --environment MINIFY,MODULE_TYPE:umd",
5858
"build:downlevel-dts": "rimraf ts3.4 && yarn downlevel-dts . ts3.4 && rimraf ts3.4/scratchpad",
59+
"build:test": "yarn build:es && yarn build:umd",
5960
"scratchpad:start": "tsc --build scratchpad/tsconfig.json --watch",
6061
"scratchpad:run": "node scratchpad/build/scratchpad/index.js",
6162
"scratchpad:flame": "rimraf isolate*.log && node --prof scratchpad/build/scratchpad/index.js && node --prof-process --preprocess -j isolate*.log | flamebearer",
@@ -141,4 +142,4 @@
141142
"javascript",
142143
"library"
143144
]
144-
}
145+
}

src/api/PDFDocument.ts

+11-16
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export default class PDFDocument {
142142
assertIs(ignoreEncryption, 'ignoreEncryption', ['boolean']);
143143
assertIs(parseSpeed, 'parseSpeed', ['number']);
144144
assertIs(throwOnInvalidObject, 'throwOnInvalidObject', ['boolean']);
145+
assertIs(password, 'password', ['string', 'undefined']);
145146

146147
const bytes = toUint8Array(pdf);
147148
const context = await PDFParser.forBytesWithOptions(
@@ -150,20 +151,21 @@ export default class PDFDocument {
150151
throwOnInvalidObject,
151152
capNumbers,
152153
).parseDocument();
153-
const pdfDoc = new PDFDocument(context, ignoreEncryption, updateMetadata, false);
154-
155-
if (pdfDoc.isEncrypted) {
154+
if(!!context.lookup(context.trailerInfo.Encrypt)) {
156155
// Decrypt
157-
const context = await PDFParser.forBytesWithOptions(
156+
const fileIds = context.lookup(context.trailerInfo.ID, PDFArray)
157+
const encryptDict = context.lookup(context.trailerInfo.Encrypt, PDFDict)
158+
const decryptedContext = await PDFParser.forBytesWithOptions(
158159
bytes,
159160
parseSpeed,
160161
throwOnInvalidObject,
161162
capNumbers,
162-
pdfDoc.cryptoFactory
163+
new CipherTransformFactory(encryptDict, (fileIds.get(0) as PDFHexString).asBytes(), password)
163164
).parseDocument();
164-
return new PDFDocument(context, true, updateMetadata, true, password);
165+
return new PDFDocument(decryptedContext, true, updateMetadata, true);
166+
} else {
167+
return new PDFDocument(context, ignoreEncryption, updateMetadata, false);
165168
}
166-
return pdfDoc;
167169
}
168170

169171
/**
@@ -191,8 +193,6 @@ export default class PDFDocument {
191193
/** Whether or not this document is encrypted. */
192194
readonly isEncrypted: boolean;
193195

194-
readonly cryptoFactory?: CipherTransformFactory;
195-
196196
/** The default word breaks used in PDFPage.drawText */
197197
defaultWordBreaks: string[] = [' '];
198198

@@ -212,7 +212,6 @@ export default class PDFDocument {
212212
ignoreEncryption: boolean,
213213
updateMetadata: boolean,
214214
isDecrypted: boolean,
215-
password?: string
216215
) {
217216
assertIs(context, 'context', [[PDFContext, 'PDFContext']]);
218217
assertIs(ignoreEncryption, 'ignoreEncryption', ['boolean']);
@@ -221,13 +220,9 @@ export default class PDFDocument {
221220
this.catalog = context.lookup(context.trailerInfo.Root) as PDFCatalog;
222221
this.isEncrypted = !!context.lookup(context.trailerInfo.Encrypt);
223222

224-
if (this.isEncrypted && !isDecrypted) {
225-
const encryptDict = context.lookup(context.trailerInfo.Encrypt, PDFDict);
226-
const fileIds = context.lookup(context.trailerInfo.ID, PDFArray);
227-
this.cryptoFactory = new CipherTransformFactory(encryptDict, (fileIds.get(0) as PDFHexString).asBytes(), password)
228-
} else if (this.isEncrypted) {
223+
if (this.isEncrypted && isDecrypted) {
229224
// context.delete(context.trailerInfo.Encrypt);
230-
delete context.trailerInfo.Encrypt;
225+
// delete context.trailerInfo.Encrypt;
231226
}
232227

233228
this.pageCache = Cache.populatedBy(this.computePages);

src/core/crypto.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -1385,40 +1385,41 @@ class PDF20 {
13851385
}
13861386
}
13871387

1388+
type Cipher = ARCFourCipher | NullCipher | AES128Cipher | AES256Cipher
13881389
class CipherTransform {
1389-
private StringCipherConstructor: any;
1390-
private StreamCipherConstructor: any;
1390+
private StringCipherConstructor: () => Cipher;
1391+
private StreamCipherConstructor: () => Cipher;
13911392

1392-
constructor(stringCipherConstructor: any, streamCipherConstructor: any) {
1393+
constructor(stringCipherConstructor: () => Cipher, streamCipherConstructor: () => Cipher) {
13931394
this.StringCipherConstructor = stringCipherConstructor;
13941395
this.StreamCipherConstructor = streamCipherConstructor;
13951396
}
13961397

13971398
createStream(stream: StreamType, length: number) {
1398-
const cipher = new this.StreamCipherConstructor();
1399+
const cipher = this.StreamCipherConstructor();
13991400
return new DecryptStream(
14001401
stream,
14011402
function cipherTransformDecryptStream(data, finalize) {
1402-
return cipher.decryptBlock(data, finalize);
1403+
return cipher.decryptBlock(data as Uint8Array, finalize);
14031404
},
14041405
length,
14051406
);
14061407
}
14071408

14081409
decryptString(s: string) {
1409-
const cipher = new this.StringCipherConstructor();
1410+
const cipher = this.StringCipherConstructor();
14101411
let data = stringAsByteArray(s);
14111412
data = cipher.decryptBlock(data, true);
14121413
return arrayAsString(data);
14131414
}
14141415

14151416
decryptBytes(d: Uint8Array) {
1416-
const cipher = new this.StringCipherConstructor();
1417+
const cipher = this.StringCipherConstructor();
14171418
return cipher.decryptBlock(d, true);
14181419
}
14191420

14201421
encryptString(s: string) {
1421-
const cipher = new this.StringCipherConstructor();
1422+
const cipher = this.StringCipherConstructor();
14221423
if (cipher instanceof AESBaseCipher) {
14231424
// Append some chars equal to "16 - (M mod 16)"
14241425
// where M is the string length (see section 7.6.2 in PDF specification)
@@ -1538,7 +1539,7 @@ class CipherTransformFactory {
15381539
// meaningful when V is 4 or 5
15391540
const encryptMetadata =
15401541
(algorithm === 4 || algorithm === 5) &&
1541-
(dict.get(PDFName.of("EncryptMetadata")) as PDFBool).asBoolean() !== false;
1542+
(dict.get(PDFName.of("EncryptMetadata")) as PDFBool)?.asBoolean() !== false;
15421543
this.encryptMetadata = encryptMetadata;
15431544

15441545
let passwordBytes: Uint8Array | undefined;

0 commit comments

Comments
 (0)