From ea95a7ae7a3a206c7fb9fae29199d2490c62b04e Mon Sep 17 00:00:00 2001 From: Arnaud TAMAILLON Date: Wed, 16 Oct 2024 14:46:13 +0200 Subject: [PATCH] Support decrypting V4/R4 files with AESV2 and no Length property --- .../Integration/EncryptedDocumentTests.cs | 9 +++++++++ .../r4_aesv2_no_length.pdf | Bin 0 -> 3654 bytes .../Encryption/EncryptionHandler.cs | 17 ++++++++++++++--- 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/UglyToad.PdfPig.Tests/Integration/SpecificTestDocuments/r4_aesv2_no_length.pdf diff --git a/src/UglyToad.PdfPig.Tests/Integration/EncryptedDocumentTests.cs b/src/UglyToad.PdfPig.Tests/Integration/EncryptedDocumentTests.cs index 58cd342bb..04eccdd8c 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/EncryptedDocumentTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/EncryptedDocumentTests.cs @@ -63,6 +63,15 @@ public void CanReadDocumentWithEmptyStringEncryptedWithAESEncryptionAndOnlyIV() } } + [Fact] + public void CanReadDocumentWithNoKeyLengthAndRevision4() + { + using (var document = PdfDocument.Open(IntegrationHelpers.GetSpecificTestDocumentPath("r4_aesv2_no_length"))) + { + Assert.Empty(document.Information.Producer); + } + } + private static string GetPath() => IntegrationHelpers.GetSpecificTestDocumentPath(FileName); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Tests/Integration/SpecificTestDocuments/r4_aesv2_no_length.pdf b/src/UglyToad.PdfPig.Tests/Integration/SpecificTestDocuments/r4_aesv2_no_length.pdf new file mode 100644 index 0000000000000000000000000000000000000000..357f7fa72b64650888696e65897c8ebdc4fcee58 GIT binary patch literal 3654 zcmc&%X;c$g78V?6jiP|K97QQX5NIK(R8~YZsiY8vW(fjHL_$(PSdx%b*aa0u1z8%k zjbMu{Ac~FRzTtxHhQc^1Dk_X1ZU}a3gCZ`?yb93L+CS!;`7t?3)#Fm{y?ej=-TS_> z^$}oBurr%td#k&yl>$RFNEsGIado9o{gYHU)khM6Q@~Y?D+q`Q{_97f`r#U-RxQOf z5WC;th0Ej;zA`bG2L5Jpna&7}0dW|xGl#pBLPeDd0?gJx^!}&a)k>{OzXK|w3h;Qj z6!&xIQ>Y@UzgnWusK~vfNfb9X@{22Eq(9`;LBBk4MFbHE!E_FVipgULTusGdBm^$N zrAir2PSOx+ToOx3{OzYTtEi!7>-K5a`#UPO=FB@GX&#k!HYDF@wfCets}_$a;c+~U zDSYS(m0q2MD&B-Hv4jpT=hzg9w#x1$WV|wZ;5vFtlHB)l za`I8rB(Bkg8CpMkcr`twW&&(zz3$h=30seD8s-m?&JVaU5C$?N3_ZL- z-eFO=lmNkN!w3>@k61n6fpQrcB8561mq$br{R^4hrk&2s(^V3_V)y=f%a$efYewH_~n* z{`~7;$Q1^2^8X0NT`ftXfWXMC2Ls|R9g_N2^Ql=&?mEX;Z|o?3+fijmYdl;m85~pv6ldg*GMz8n^z z9l|z5r`p-TX-CX2yMxfegYcc4(ca6%#jenGGv!6CSDhq_NL?}}h3MiVj_GzP-P zm>8Wyqw_H?m&-yCHjfT-c;L$yGTA&Ki^XU2*(^3DKp0Gn!DO)!l#Kusd0HY;skR3j zqo8jP{X+n)Oe@7dn*kOA&}HmThQD^QX}tNix5xWRy7#Wm6A3@OdJ&UkL6nK=_T4F! zrdDoyJH2#uuQ+UZVUC!XYw_4&NB}U|BL7TsfA(k1cT(Rh=|3&glYtrxjoUc zou^)oTVr`Wv`}Qwu#s{<#s0_DzkgF%)!1dM&O+%m6BJA%>n(vMu@~zMb+cAJf3Yez zBdB~%_-e2FzZ%rSycd}sg&uavFp>9RgL}ZFQ@{Noi+{A%Z<%!F6m|C}*JqVaX)J#2 z{_ZomllEU4qZ}WSSR%=3n{K{r#U_cJmQypzxoumJT?jWfeOis>;<&h$?BSK|sC&o&~?`g$i zZ8n-aZ{K9S>8|d?4Z%OBm1`bKGtxv!$Bz3G9mBfKj8m>R7cMxmqKaEKe!<1Gddj&w zS++XiIQFoU5q*9o$M22!C2!v4Cw>aAx@W^#Hc%GXdCy$!gDXM3@_qe;`t)*eocj);L?@}r=!7AQtcY5~Z(Cck|>v03t zz9~~83hH_dGSYT+-uiUyQdxRs$jwFfb*aBxx&D6by7C*VKD|D>V>t4Usf7n#O6oU^ z*jdst;k+fKso+}P-9FD`PGt(Uu_xSc{vpkH-Rb4X`8ml}M^kJvIqeOO-ySlzsm4}C zHOvT4j;vUaS7;_OvC28V{Q9H>_cAv=D!d|KrxqMy>^6xa zn(H}6`70RnM^!gR<&FMV#iF{Ph-eEbi~VHO+wP{)11$Hdj?g+Kui8A$Pkgv__?u5Z zlxVA``_wK8Inul<&SBe=z5jky1DTjx%w|@k6vfX&| zkxt{;FUF0~u0J(Zz#DCe*}Y`Vt(@Ghi+`r6a#T!EPkKgVKXt|?l%KfnRe5sT zjfW#$uCWTxlp}F%3fD!|mp^5n$0{5j!{ZP5A4Yw~3>TzW_;j@w#O=8|-|F1@88XAD ztFb7&Yl!vk%a+!ZJ-BK0QDj$Op7EsTUQK-N={dm}zZ>g{ zN^BN5t%kbQE4#v%xB0XgHB+X|%P(tR)Oz;%gI(d__Vw2rb|09w@{+-Ci!!%E-k=$bc4WqSXITI3JoOra(BlTUAYg5nE{MN^{S0a<5vP@w<6(1g3 z84!CVSTru`6!&7fIAO+vY|NwKyQ9WwwrUnziL|bdD#yx$lbI5(3i|hm5pn zy?1o!6%$`?=0|z4`$pL;-D9d>FZ8IXyDK0PDa!va7rP%YM|h#A$@h?5u|}oyY1|;z zPWQb-DJd7P&r!i=URAZfcZgcWH}mNVI2KeUS#h&=#MLVF5|ZWJS(uSI`Vlee_l33| zT}Ecp&Wy74dbe-unF(#f+X7iK7vEXmPW#dO=Q*Z78cy)!E z^3uDfb{w6T$uJZHkb=E#uv{aT=4$!g^k4>;tQdc$YXGI{}Cxq z#z_pQeo7?}>3Xe36)L3UQ3>gfe<0~R6yZvU6f@*l5bP=xFfl#{<#TyF7GDUo3(P|? z455P`hc7_5Zmx8OfX3$YSTIWC3K0g4BfwA|4Q8Sogva5qP&XjRz`2V?;?ii1HkQhv z^XNbo350YmkIh9fhJZ^KFt}_E2jMd~9K9H|h4g|4mq7_~@VWs+q$X0LRzWOh*qIL5 nquzc(2S}@s;cCeFJE1?Y)4;oxL`~>3jlc{J#nx8njZyvs8J$%R literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs index 1a6fa67be..91ce19498 100644 --- a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs +++ b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs @@ -82,8 +82,8 @@ public EncryptionHandler(EncryptionDictionary encryptionDictionary, TrailerDicti cryptHandler = cryptHandlerLocal; - useAes = cryptHandlerLocal?.StreamDictionary?.Name == CryptDictionary.Method.AesV2 - || cryptHandlerLocal?.StreamDictionary?.Name == CryptDictionary.Method.AesV3; + useAes = cryptHandlerLocal.StreamDictionary.Name == CryptDictionary.Method.AesV2 + || cryptHandlerLocal.StreamDictionary.Name == CryptDictionary.Method.AesV3; } var charset = OtherEncodings.Iso88591; @@ -96,7 +96,18 @@ public EncryptionHandler(EncryptionDictionary encryptionDictionary, TrailerDicti var length = encryptionDictionary.EncryptionAlgorithmCode == EncryptionAlgorithmCode.Rc4OrAes40BitKey ? 5 - : encryptionDictionary.KeyLength.GetValueOrDefault() / 8; + : encryptionDictionary.KeyLength switch + { + // this is as per the PDF specification, (PDF_ISO_32000-2, 7.6.2 Application of encryption, table 20, indicating Length default value is 40) + null when encryptionDictionary.EncryptionAlgorithmCode == EncryptionAlgorithmCode.Rc4OrAesGreaterThan40BitKey => 40, + null when encryptionDictionary.EncryptionAlgorithmCode == EncryptionAlgorithmCode.UnpublishedAlgorithm40To128BitKey => 40, + // this is based on a specific use case encountered and tested, in link with the pdf specification (PDF_ISO_32000-2, 7.6.5.3 Public-key encryption algorithms, table 25, CFM comments) + null when cryptHandler?.StreamDictionary.Name == CryptDictionary.Method.AesV2 => 128, + // this is speculation, in link with the pdf spec, in link with the pdf specification (PDF_ISO_32000-2, 7.6.5.3 Public-key encryption algorithms, table 25, CFM comments) + null when cryptHandler?.StreamDictionary.Name == CryptDictionary.Method.AesV3 => 256, + // other use cases + _ => encryptionDictionary.KeyLength.GetValueOrDefault() + } / 8; var foundPassword = false;