mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Implementation of rev. 6 hash
This commit is contained in:
@ -272,11 +272,21 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||||||
// SECURITY - handle encrypted documents
|
// SECURITY - handle encrypted documents
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
const PDFObject& trailerDictionaryObject = xrefTable.getTrailerDictionary();
|
const PDFObject& trailerDictionaryObject = xrefTable.getTrailerDictionary();
|
||||||
if (!trailerDictionaryObject.isDictionary())
|
|
||||||
|
const PDFDictionary* trailerDictionary = nullptr;
|
||||||
|
if (trailerDictionaryObject.isDictionary())
|
||||||
|
{
|
||||||
|
trailerDictionary = trailerDictionaryObject.getDictionary();
|
||||||
|
}
|
||||||
|
else if (trailerDictionaryObject.isStream())
|
||||||
|
{
|
||||||
|
const PDFStream* stream = trailerDictionaryObject.getStream();
|
||||||
|
trailerDictionary = stream->getDictionary();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
throw PDFParserException(tr("Invalid trailer dictionary."));
|
throw PDFParserException(tr("Invalid trailer dictionary."));
|
||||||
}
|
}
|
||||||
const PDFDictionary* trailerDictionary = trailerDictionaryObject.getDictionary();
|
|
||||||
|
|
||||||
// Read the document ID
|
// Read the document ID
|
||||||
QByteArray id;
|
QByteArray id;
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
#include <openssl/rc4.h>
|
#include <openssl/rc4.h>
|
||||||
#include <openssl/md5.h>
|
#include <openssl/md5.h>
|
||||||
|
#include <openssl/aes.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
@ -283,6 +285,9 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||||||
QByteArray password;
|
QByteArray password;
|
||||||
bool passwordObtained = true;
|
bool passwordObtained = true;
|
||||||
|
|
||||||
|
// Clear the authorization data
|
||||||
|
m_authorizationData = AuthorizationData();
|
||||||
|
|
||||||
while (passwordObtained)
|
while (passwordObtained)
|
||||||
{
|
{
|
||||||
switch (m_R)
|
switch (m_R)
|
||||||
@ -300,6 +305,8 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||||||
if (U == m_U)
|
if (U == m_U)
|
||||||
{
|
{
|
||||||
// We have authorized owner access
|
// We have authorized owner access
|
||||||
|
m_authorizationData.authorizationResult = AuthorizationResult::OwnerAuthorized;
|
||||||
|
m_authorizationData.fileEncryptionKey = fileEncryptionKey;
|
||||||
return AuthorizationResult::OwnerAuthorized;
|
return AuthorizationResult::OwnerAuthorized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,6 +318,8 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||||||
if (U == m_U)
|
if (U == m_U)
|
||||||
{
|
{
|
||||||
// We have authorized owner access
|
// We have authorized owner access
|
||||||
|
m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized;
|
||||||
|
m_authorizationData.fileEncryptionKey = fileEncryptionKey;
|
||||||
return AuthorizationResult::UserAuthorized;
|
return AuthorizationResult::UserAuthorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,15 +328,68 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
{
|
{
|
||||||
// TODO: Implement revision 6 encryption
|
UserOwnerData_r6 userData = parseParts(m_U);
|
||||||
return AuthorizationResult::Failed;
|
UserOwnerData_r6 ownerData = parseParts(m_O);
|
||||||
|
|
||||||
|
// Try to authorize owner password
|
||||||
|
{
|
||||||
|
QByteArray inputData = password + ownerData.validationSalt + m_U;
|
||||||
|
QByteArray hash = createHash_r6(inputData, password, true);
|
||||||
|
|
||||||
|
if (hash == ownerData.hash)
|
||||||
|
{
|
||||||
|
// We have authorized owner access. Now we must calculate the owner encryption key
|
||||||
|
|
||||||
|
QByteArray fileEncryptionKeyInputData = password + ownerData.keySalt + m_U;
|
||||||
|
QByteArray fileEncryptionDecryptionKey = createHash_r6(fileEncryptionKeyInputData, password, true);
|
||||||
|
|
||||||
|
Q_ASSERT(fileEncryptionDecryptionKey.size() == 32);
|
||||||
|
AES_KEY key = { };
|
||||||
|
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key);
|
||||||
|
unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { };
|
||||||
|
m_authorizationData.fileEncryptionKey.resize(m_OE.size());
|
||||||
|
AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_OE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT);
|
||||||
|
|
||||||
|
m_authorizationData.authorizationResult = AuthorizationResult::OwnerAuthorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to authorize user password
|
||||||
|
if (!m_authorizationData.isAuthorized())
|
||||||
|
{
|
||||||
|
QByteArray inputData = password + userData.validationSalt;
|
||||||
|
QByteArray hash = createHash_r6(inputData, password, false);
|
||||||
|
|
||||||
|
if (hash == userData.hash)
|
||||||
|
{
|
||||||
|
QByteArray fileEncryptionKeyInputData = password + userData.keySalt;
|
||||||
|
QByteArray fileEncryptionDecryptionKey = createHash_r6(fileEncryptionKeyInputData, password, false);
|
||||||
|
|
||||||
|
Q_ASSERT(fileEncryptionDecryptionKey.size() == 32);
|
||||||
|
AES_KEY key = { };
|
||||||
|
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key);
|
||||||
|
unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { };
|
||||||
|
m_authorizationData.fileEncryptionKey.resize(m_OE.size());
|
||||||
|
AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_OE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT);
|
||||||
|
|
||||||
|
// We have authorized owner access
|
||||||
|
m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop, if we authorized the document usage
|
||||||
|
if (m_authorizationData.isAuthorized())
|
||||||
|
{
|
||||||
|
return m_authorizationData.authorizationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return AuthorizationResult::Failed;
|
return AuthorizationResult::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
|
// TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
|
||||||
password = getPasswordCallback(&passwordObtained).toUtf8();
|
password = getPasswordCallback(&passwordObtained).toUtf8();
|
||||||
}
|
}
|
||||||
@ -388,7 +450,8 @@ QByteArray PDFStandardSecurityHandler::createFileEncryptionKey(const QByteArray&
|
|||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
{
|
{
|
||||||
// TODO: Implement revision 6 key
|
// This function must not be called with revision 6
|
||||||
|
Q_ASSERT(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,4 +622,149 @@ std::array<uint8_t, 32> PDFStandardSecurityHandler::createPaddedPassword32(const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray PDFStandardSecurityHandler::createHash_r6(const QByteArray& input, const QByteArray& inputPassword, bool useUserKey) const
|
||||||
|
{
|
||||||
|
QByteArray result;
|
||||||
|
|
||||||
|
// First compute sha-256 digest of the input
|
||||||
|
std::array<uint8_t, SHA256_DIGEST_LENGTH> inputDigest = { };
|
||||||
|
SHA256(reinterpret_cast<const unsigned char*>(input.data()), input.size(), inputDigest.data());
|
||||||
|
std::vector<uint8_t> K(inputDigest.cbegin(), inputDigest.cend());
|
||||||
|
|
||||||
|
// Fill the user key, if we use it
|
||||||
|
std::vector<uint8_t> userKey;
|
||||||
|
if (useUserKey)
|
||||||
|
{
|
||||||
|
userKey.resize(m_U.size());
|
||||||
|
std::copy_n(m_U.constData(), m_U.size(), userKey.begin());
|
||||||
|
}
|
||||||
|
const size_t userKeySize = userKey.size();
|
||||||
|
|
||||||
|
// Fill the input password
|
||||||
|
std::vector<uint8_t> password(inputPassword.constData(), inputPassword.constData() + inputPassword.size());
|
||||||
|
const size_t passwordSize = password.size();
|
||||||
|
|
||||||
|
std::vector<uint8_t> K1;
|
||||||
|
std::vector<uint8_t> E;
|
||||||
|
|
||||||
|
int round = 0;
|
||||||
|
while (round < 64 || round < E.back() + 32)
|
||||||
|
{
|
||||||
|
const size_t blockCount = 64;
|
||||||
|
const size_t KSize = K.size();
|
||||||
|
const size_t sequenceSize = passwordSize + KSize + userKeySize;
|
||||||
|
const size_t totalSize = blockCount * sequenceSize;
|
||||||
|
|
||||||
|
// Resize the arrays
|
||||||
|
K1.resize(totalSize);
|
||||||
|
E.resize(totalSize);
|
||||||
|
|
||||||
|
// a) fill the input array K1 with data
|
||||||
|
auto it = K1.begin();
|
||||||
|
for (size_t i = 0; i < blockCount; ++i)
|
||||||
|
{
|
||||||
|
std::copy_n(password.cbegin(), passwordSize, it);
|
||||||
|
std::advance(it, passwordSize);
|
||||||
|
|
||||||
|
std::copy_n(K.cbegin(), KSize, it);
|
||||||
|
std::advance(it, KSize);
|
||||||
|
|
||||||
|
std::copy_n(userKey.cbegin(), userKeySize, it);
|
||||||
|
std::advance(it, userKeySize);
|
||||||
|
}
|
||||||
|
Q_ASSERT(it == K1.cend());
|
||||||
|
Q_ASSERT(K.size() >= 32);
|
||||||
|
|
||||||
|
// b) encrypt K1 with AES-128 in CBC mode, first 16 bytes of K is key,
|
||||||
|
// second 16 bytes in K is initialization vector for AES algorithm.
|
||||||
|
AES_KEY key = { };
|
||||||
|
AES_set_encrypt_key(K.data(), 128, &key);
|
||||||
|
AES_cbc_encrypt(K1.data(), E.data(), K1.size(), &key, K.data() + 16, AES_ENCRYPT);
|
||||||
|
|
||||||
|
// c) we take first 16 bytes from E as unsigned 128 bit big-endian integer and compute
|
||||||
|
// remainder modulo 3. Then we decide which SHA function we will use.
|
||||||
|
|
||||||
|
// We can't directly modulo 128 bit unsigned number, because we do not have 128 bit arithmetic (yet).
|
||||||
|
// We will use following trick from https://math.stackexchange.com/questions/2727954/bit-representation-and-divisibility-by-3
|
||||||
|
//
|
||||||
|
// 2^n mod 3 = 2 for n = 1, 3, 5, 7, 9, ...
|
||||||
|
// 2^n mod 3 = 1 for n = 0, 2, 4, 6, 8, ...
|
||||||
|
//
|
||||||
|
// Also, it doesn't matter the endianity of the numbers, becase for example, when we change endianity of 16 bit
|
||||||
|
// numbers, then bits 0-7 became 8-15, so even/odd bits become also even/odd.
|
||||||
|
|
||||||
|
int remainderAccumulator = 0;
|
||||||
|
for (size_t i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
uint8_t byte = E[i];
|
||||||
|
|
||||||
|
int currentRemainder = 1;
|
||||||
|
for (uint8_t i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
if ((byte >> i) & 1)
|
||||||
|
{
|
||||||
|
remainderAccumulator += currentRemainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We alternate the remainder 1, 2, 1, 2, 1, 2, ...
|
||||||
|
currentRemainder = 3 - currentRemainder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remainderAccumulator = remainderAccumulator % 3;
|
||||||
|
|
||||||
|
// d) according to the remainder, decide, which function we will use
|
||||||
|
switch (remainderAccumulator)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
K.resize(SHA256_DIGEST_LENGTH);
|
||||||
|
SHA256(E.data(), E.size(), K.data());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
K.resize(SHA384_DIGEST_LENGTH);
|
||||||
|
SHA384(E.data(), E.size(), K.data());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
K.resize(SHA512_DIGEST_LENGTH);
|
||||||
|
SHA512(E.data(), E.size(), K.data());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// Invalid value, can't occur
|
||||||
|
Q_ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++round;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(K.size() >= 32);
|
||||||
|
|
||||||
|
// Clamp result to 32 bytes
|
||||||
|
result.resize(32);
|
||||||
|
std::copy_n(K.data(), 32, reinterpret_cast<unsigned char*>(result.data()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFStandardSecurityHandler::UserOwnerData_r6 PDFStandardSecurityHandler::parseParts(const QByteArray& data) const
|
||||||
|
{
|
||||||
|
UserOwnerData_r6 result;
|
||||||
|
Q_ASSERT(data.size() == 48);
|
||||||
|
|
||||||
|
result.hash = data.left(32);
|
||||||
|
result.validationSalt = data.mid(32, 8);
|
||||||
|
result.keySalt = data.mid(40, 8);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -131,12 +131,22 @@ public:
|
|||||||
|
|
||||||
struct AuthorizationData
|
struct AuthorizationData
|
||||||
{
|
{
|
||||||
|
bool isAuthorized() const { return authorizationResult == AuthorizationResult::UserAuthorized || authorizationResult == AuthorizationResult::OwnerAuthorized; }
|
||||||
|
|
||||||
|
AuthorizationResult authorizationResult = AuthorizationResult::Failed;
|
||||||
|
QByteArray fileEncryptionKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend static PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
friend static PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||||
|
|
||||||
|
struct UserOwnerData_r6
|
||||||
|
{
|
||||||
|
QByteArray hash;
|
||||||
|
QByteArray validationSalt;
|
||||||
|
QByteArray keySalt;
|
||||||
|
};
|
||||||
|
|
||||||
/// Creates file encryption key from passed password, based on the revision
|
/// Creates file encryption key from passed password, based on the revision
|
||||||
/// \param password Password to be used to create file encryption key
|
/// \param password Password to be used to create file encryption key
|
||||||
/// \note Password must be in PDFDocEncoding for revision 4 or earlier,
|
/// \note Password must be in PDFDocEncoding for revision 4 or earlier,
|
||||||
@ -156,6 +166,16 @@ private:
|
|||||||
/// then padding password is returned.
|
/// then padding password is returned.
|
||||||
std::array<uint8_t, 32> createPaddedPassword32(const QByteArray& password) const;
|
std::array<uint8_t, 32> createPaddedPassword32(const QByteArray& password) const;
|
||||||
|
|
||||||
|
/// Creates hash using algorithm 2.B for revision 6. Input is input of the hash,
|
||||||
|
/// and if \p useUserKey is used, user key is considered in the hash.
|
||||||
|
/// \param input Input of the hash
|
||||||
|
/// \param inputPassword Input password
|
||||||
|
/// \param useUserKey Use user key in the hash computation
|
||||||
|
QByteArray createHash_r6(const QByteArray& input, const QByteArray& inputPassword, bool useUserKey) const;
|
||||||
|
|
||||||
|
/// Parses parts of the user/owner data (U/O values of the encryption dictionary)
|
||||||
|
UserOwnerData_r6 parseParts(const QByteArray& data) const;
|
||||||
|
|
||||||
/// Revision number of standard security number
|
/// Revision number of standard security number
|
||||||
int m_R = 0;
|
int m_R = 0;
|
||||||
|
|
||||||
@ -188,6 +208,9 @@ private:
|
|||||||
|
|
||||||
/// First part of the id of the document
|
/// First part of the id of the document
|
||||||
QByteArray m_ID;
|
QByteArray m_ID;
|
||||||
|
|
||||||
|
/// Authorization data
|
||||||
|
AuthorizationData m_authorizationData;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
Reference in New Issue
Block a user