From 53811a7d973aa8047ba4a743a26e785ec8664380 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Tue, 7 May 2019 18:53:51 +0100 Subject: [PATCH] verify password against user password or throw --- .../Encryption/EncryptionDictionary.cs | 7 ++ .../Encryption/EncryptionHandler.cs | 65 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs b/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs index ccc34e1f..8ae32e55 100644 --- a/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs +++ b/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs @@ -19,6 +19,10 @@ public string UserPasswordCheck { get; } + public byte[] OwnerBytes { get; } + + public byte[] UserBytes { get; } + public UserAccessPermissions UserAccessPermissions { get; } public bool IsStandardFilter => string.Equals(Filter, "Standard", StringComparison.OrdinalIgnoreCase); @@ -45,6 +49,9 @@ UserAccessPermissions = userAccessPermissions; Dictionary = dictionary; EncryptMetadata = encryptMetadata; + + OwnerBytes = OtherEncodings.StringAsLatin1Bytes(ownerPasswordCheck); + UserBytes = OtherEncodings.StringAsLatin1Bytes(userPasswordCheck); } } diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs index d34e5dda..1e053e7a 100644 --- a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs +++ b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs @@ -46,6 +46,7 @@ documentIdBytes = trailerDictionary.Identifier != null && trailerDictionary.Identifier.Count == 2 ? OtherEncodings.StringAsLatin1Bytes(trailerDictionary.Identifier[0]) : EmptyArray.Instance; + this.password = password ?? string.Empty; if (encryptionDictionary == null) @@ -70,12 +71,72 @@ ? 5 : encryptionDictionary.KeyLength.GetValueOrDefault() / 8; - encryptionKey = CalculateKeyRevisions2To4(passwordBytes, ownerKey, (int)encryptionDictionary.UserAccessPermissions, encryptionDictionary.StandardSecurityHandlerRevision, - length, documentIdBytes, encryptionDictionary.EncryptMetadata); + if (IsUserPassword(passwordBytes, length)) + { + encryptionKey = CalculateKeyRevisions2To4(passwordBytes, ownerKey, (int) encryptionDictionary.UserAccessPermissions, encryptionDictionary.StandardSecurityHandlerRevision, + length, documentIdBytes, encryptionDictionary.EncryptMetadata); + } + else + { + throw new NotImplementedException("TODO: check owner password."); + } useAes = false; } + private bool IsUserPassword(byte[] passwordBytes, int length) + { + // 1. Create an encryption key based on the user password string. + var password = CalculateKeyRevisions2To4(passwordBytes, encryptionDictionary.OwnerBytes, (int) encryptionDictionary.UserAccessPermissions, + encryptionDictionary.StandardSecurityHandlerRevision, length, documentIdBytes, encryptionDictionary.EncryptMetadata); + + byte[] output; + + if (encryptionDictionary.StandardSecurityHandlerRevision >= 3) + { + using (var md5 = MD5.Create()) + { + // 2. Initialize the MD5 hash function and pass the 32-byte padding string. + UpdateMd5(md5, PaddingBytes); + + // 3. Pass the first element of the file identifier array to the hash function and finish the hash. + UpdateMd5(md5, documentIdBytes); + md5.TransformFinalBlock(EmptyArray.Instance, 0, 0); + + var result = md5.Hash; + + // 4. Encrypt the 16-byte result of the hash, using an RC4 encryption function with the encryption key from step 1. + var temp = RC4.Encrypt(password, result); + + // 5. Do the following 19 times: + for (byte i = 1; i <= 19; i++) + { + // Take the output from the previous invocation of the RC4 function + // and pass it as input to a new invocation of the function + + // Use an encryption key generated by taking each byte of the original encryption key (from step 1) + // and performing an XOR operation between that byte and the single-byte value of the iteration counter. + var key = password.Select(x => (byte)(x ^ i)).ToArray(); + temp = RC4.Encrypt(key, temp); + } + + output = temp; + } + } + else + { + // 2. Encrypt the 32-byte padding string using an RC4 encryption function with the encryption key from the preceding step. + output = RC4.Encrypt(password, PaddingBytes); + } + + if (encryptionDictionary.StandardSecurityHandlerRevision >= 3) + { + return encryptionDictionary.UserBytes.Take(16).SequenceEqual(output.Take(16)); + } + + return encryptionDictionary.UserBytes.SequenceEqual(output); + } + public IToken Decrypt(IndirectReference reference, IToken token) { if (token == null)