2019-08-04 18:26:15 +02:00
// Copyright (C) 2019 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
# include "pdfsecurityhandler.h"
# include "pdfexception.h"
# include <openssl/rc4.h>
2019-08-10 17:24:12 +02:00
# include <openssl/md5.h>
2019-08-11 15:46:26 +02:00
# include <openssl/aes.h>
# include <openssl/sha.h>
2019-08-10 17:24:12 +02:00
# include <array>
2019-08-04 18:26:15 +02:00
namespace pdf
{
2019-08-10 17:24:12 +02:00
// Padding password
static constexpr std : : array < uint8_t , 32 > PDFPasswordPadding = {
0x28 , 0xBF , 0x4E , 0x5E , 0x4E , 0x75 , 0x8A , 0x41 ,
0x64 , 0x00 , 0x4E , 0x56 , 0xFF , 0xFA , 0x01 , 0x08 ,
0x2E , 0x2E , 0x00 , 0xB6 , 0xD0 , 0x68 , 0x3E , 0x80 ,
0x2F , 0x0C , 0xA9 , 0xFE , 0x64 , 0x53 , 0x69 , 0x7A
} ;
PDFSecurityHandlerPointer PDFSecurityHandler : : createSecurityHandler ( const PDFObject & encryptionDictionaryObject , const QByteArray & id )
2019-08-04 18:26:15 +02:00
{
if ( encryptionDictionaryObject . isNull ( ) )
{
return PDFSecurityHandlerPointer ( new PDFNoneSecurityHandler ( ) ) ;
}
if ( ! encryptionDictionaryObject . isDictionary ( ) )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Invalid encryption dictionary. " ) ) ;
}
const PDFDictionary * dictionary = encryptionDictionaryObject . getDictionary ( ) ;
auto getName = [ ] ( const PDFDictionary * dictionary , const char * key , bool required , const char * defaultValue = nullptr ) - > QByteArray
{
const PDFObject & nameObject = dictionary - > get ( key ) ;
if ( nameObject . isNull ( ) )
{
return defaultValue ? QByteArray ( defaultValue ) : QByteArray ( ) ;
}
if ( ! nameObject . isName ( ) )
{
if ( required )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Invalid value for entry '%1' in encryption dictionary. Name expected. " ) . arg ( QString : : fromLatin1 ( key ) ) ) ;
}
return defaultValue ? QByteArray ( defaultValue ) : QByteArray ( ) ;
}
return nameObject . getString ( ) ;
} ;
auto getInt = [ ] ( const PDFDictionary * dictionary , const char * key , bool required , PDFInteger defaultValue = - 1 ) - > PDFInteger
{
const PDFObject & intObject = dictionary - > get ( key ) ;
if ( ! intObject . isInt ( ) )
{
if ( required )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Invalid value for entry '%1' in encryption dictionary. Integer expected. " ) . arg ( QString : : fromLatin1 ( key ) ) ) ;
}
return defaultValue ;
}
return intObject . getInteger ( ) ;
} ;
QByteArray filterName = getName ( dictionary , " Filter " , true ) ;
if ( filterName ! = " Standard " )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Unknown security handler. " ) ) ;
}
const int V = getInt ( dictionary , " V " , true ) ;
// Check V
if ( V < 1 | | V > 5 )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Unsupported version of document encryption (V = %1) . " ).arg(V)) ;
}
// Only valid for V == 2 or V == 3, otherwise we set file encryption key length manually
int Length = 40 ;
switch ( V )
{
case 1 :
Length = 40 ;
break ;
case 2 :
case 3 :
Length = getInt ( dictionary , " Length " , false , 40 ) ;
break ;
case 4 :
Length = 128 ;
break ;
case 5 :
Length = 256 ;
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
// Create standard security handler
PDFStandardSecurityHandler handler ;
handler . m_V = V ;
handler . m_keyLength = Length ;
// Add "Identity" filter to the filters
CryptFilter identityFilter ;
identityFilter . type = CryptFilterType : : Identity ;
handler . m_cryptFilters [ " Identity " ] = identityFilter ;
if ( V = = 4 | | V = = 5 )
{
const PDFObject & cryptFilterObjects = dictionary - > get ( " CF " ) ;
if ( cryptFilterObjects . isDictionary ( ) )
{
auto parseCryptFilter = [ & getName ] ( const PDFObject & object ) - > CryptFilter
{
if ( ! object . isDictionary ( ) )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Crypt filter is not a dictionary! " ) ) ;
}
const PDFDictionary * cryptFilterDictionary = object . getDictionary ( ) ;
CryptFilter filter ;
QByteArray CFMName = getName ( cryptFilterDictionary , " CFM " , false , " None " ) ;
if ( CFMName = = " None " )
{
filter . type = CryptFilterType : : None ;
}
else if ( CFMName = = " V2 " )
{
filter . type = CryptFilterType : : V2 ;
}
else if ( CFMName = = " AESV2 " )
{
filter . type = CryptFilterType : : AESV2 ;
}
else if ( CFMName = = " AESV3 " )
{
filter . type = CryptFilterType : : AESV3 ;
}
else
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Unsupported encryption algorithm '%1'. " ) . arg ( QString : : fromLatin1 ( CFMName ) ) ) ;
}
QByteArray authEventName = getName ( cryptFilterDictionary , " AuthEvent " , false , " DocOpen " ) ;
if ( authEventName = = " DocOpen " )
{
filter . authEvent = AuthEvent : : DocOpen ;
}
else if ( authEventName = = " EFOpen " )
{
filter . authEvent = AuthEvent : : EFOpen ;
}
else
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Unsupported authorization event '%1'. " ) . arg ( QString : : fromLatin1 ( authEventName ) ) ) ;
}
return filter ;
} ;
const PDFDictionary * cryptFilters = cryptFilterObjects . getDictionary ( ) ;
for ( size_t i = 0 , cryptFilterCount = cryptFilters - > getCount ( ) ; i < cryptFilterCount ; + + i )
{
handler . m_cryptFilters [ cryptFilters - > getKey ( i ) ] = parseCryptFilter ( cryptFilters - > getValue ( i ) ) ;
}
}
// Now, add standard filters
auto resolveFilter = [ & handler ] ( const QByteArray & name )
{
auto it = handler . m_cryptFilters . find ( name ) ;
if ( it = = handler . m_cryptFilters . cend ( ) )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Uknown crypt filter '%1'. " ) . arg ( QString : : fromLatin1 ( name ) ) ) ;
}
return it - > second ;
} ;
handler . m_filterStreams = resolveFilter ( getName ( dictionary , " StmF " , false , " Identity " ) ) ;
handler . m_filterStrings = resolveFilter ( getName ( dictionary , " StrF " , false , " Identity " ) ) ;
if ( dictionary - > hasKey ( " EFF " ) )
{
handler . m_filterEmbeddedFiles = resolveFilter ( getName ( dictionary , " EFF " , true ) ) ;
}
else
{
// According to the PDF specification, if 'EFF' entry is omitted, then filter
// for streams is used.
handler . m_filterEmbeddedFiles = handler . m_filterStreams ;
}
}
2019-08-10 17:24:12 +02:00
int R = getInt ( dictionary , " R " , true ) ;
if ( R < 2 | | R > 6 | | R = = 5 )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( R ) ) ;
}
handler . m_R = R ;
auto readByteArray = [ dictionary ] ( const char * key , int size )
{
QByteArray result ;
const PDFObject & object = dictionary - > get ( key ) ;
if ( object . isString ( ) )
{
result = object . getString ( ) ;
if ( result . size ( ) ! = size )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Expected %1 characters long string in entry '%2'. Provided length is %3. " ) . arg ( size ) . arg ( QString : : fromLatin1 ( key ) ) . arg ( result . size ( ) ) ) ;
}
}
else
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Expected %1 characters long string in entry '%2'. " ) . arg ( size ) . arg ( QString : : fromLatin1 ( key ) ) ) ;
}
return result ;
} ;
handler . m_O = readByteArray ( " O " , ( R ! = 6 ) ? 32 : 48 ) ;
handler . m_U = readByteArray ( " U " , ( R ! = 6 ) ? 32 : 48 ) ;
handler . m_permissions = static_cast < uint32_t > ( static_cast < int > ( getInt ( dictionary , " P " , true ) ) ) ;
if ( R = = 6 )
{
handler . m_OE = readByteArray ( " OE " , 32 ) ;
handler . m_UE = readByteArray ( " UE " , 32 ) ;
handler . m_Perms = readByteArray ( " Perms " , 16 ) ;
}
const PDFObject & encryptMetadataObject = dictionary - > get ( " EncryptMetadata " ) ;
if ( encryptMetadataObject . isBool ( ) )
{
handler . m_encryptMetadata = encryptMetadataObject . getBool ( ) ;
}
handler . m_ID = id ;
2019-08-04 18:26:15 +02:00
return PDFSecurityHandlerPointer ( new PDFStandardSecurityHandler ( qMove ( handler ) ) ) ;
}
2019-08-10 17:24:12 +02:00
PDFSecurityHandler : : AuthorizationResult PDFStandardSecurityHandler : : authenticate ( const std : : function < QString ( bool * ) > & getPasswordCallback )
{
QByteArray password ;
bool passwordObtained = true ;
2019-08-11 15:46:26 +02:00
// Clear the authorization data
m_authorizationData = AuthorizationData ( ) ;
2019-08-10 17:24:12 +02:00
while ( passwordObtained )
{
switch ( m_R )
{
case 2 :
case 3 :
case 4 :
{
// Try to authorize by owner password
{
QByteArray userPassword = createUserPasswordFromOwnerPassword ( password ) ;
QByteArray fileEncryptionKey = createFileEncryptionKey ( userPassword ) ;
QByteArray U = createEntryValueU_r234 ( fileEncryptionKey ) ;
if ( U = = m_U )
{
// We have authorized owner access
2019-08-11 15:46:26 +02:00
m_authorizationData . authorizationResult = AuthorizationResult : : OwnerAuthorized ;
m_authorizationData . fileEncryptionKey = fileEncryptionKey ;
2019-08-10 17:24:12 +02:00
return AuthorizationResult : : OwnerAuthorized ;
}
}
// Try to authorize user password
QByteArray fileEncryptionKey = createFileEncryptionKey ( password ) ;
QByteArray U = createEntryValueU_r234 ( fileEncryptionKey ) ;
if ( U = = m_U )
{
// We have authorized owner access
2019-08-11 15:46:26 +02:00
m_authorizationData . authorizationResult = AuthorizationResult : : UserAuthorized ;
m_authorizationData . fileEncryptionKey = fileEncryptionKey ;
2019-08-10 17:24:12 +02:00
return AuthorizationResult : : UserAuthorized ;
}
break ;
}
case 6 :
{
2019-08-11 15:46:26 +02:00
UserOwnerData_r6 userData = parseParts ( m_U ) ;
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 ;
2019-08-10 17:24:12 +02:00
}
default :
return AuthorizationResult : : Failed ;
}
// TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
password = getPasswordCallback ( & passwordObtained ) . toUtf8 ( ) ;
}
return AuthorizationResult : : Cancelled ;
}
QByteArray PDFStandardSecurityHandler : : createFileEncryptionKey ( const QByteArray & password ) const
{
QByteArray result ;
switch ( m_R )
{
case 2 :
case 3 :
case 4 :
{
std : : array < uint8_t , 32 > paddedPassword = createPaddedPassword32 ( password ) ;
uint32_t transformedPermissions = qToLittleEndian ( m_permissions ) ;
MD5_CTX context = { } ;
MD5_Init ( & context ) ;
MD5_Update ( & context , paddedPassword . data ( ) , paddedPassword . size ( ) ) ;
MD5_Update ( & context , m_O . constData ( ) , m_O . size ( ) ) ;
MD5_Update ( & context , & transformedPermissions , sizeof ( transformedPermissions ) ) ;
MD5_Update ( & context , m_ID . constData ( ) , m_ID . size ( ) ) ;
if ( ! m_encryptMetadata )
{
constexpr uint32_t value = 0xFFFFFFFF ;
MD5_Update ( & context , & value , sizeof ( value ) ) ;
}
std : : array < uint8_t , MD5_DIGEST_LENGTH > fileEncryptionKey ;
MD5_Final ( fileEncryptionKey . data ( ) , & context ) ;
const int keyByteLength = m_keyLength / 8 ;
if ( keyByteLength > MD5_DIGEST_LENGTH )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Encryption key length (%1) exceeded maximal value of . " ).arg(keyByteLength).arg(MD5_DIGEST_LENGTH)) ;
}
if ( m_R > = 3 )
{
for ( int i = 0 ; i < 50 ; + + i )
{
MD5_Init ( & context ) ;
MD5_Update ( & context , fileEncryptionKey . data ( ) , keyByteLength ) ;
MD5_Final ( fileEncryptionKey . data ( ) , & context ) ;
}
}
result . resize ( keyByteLength ) ;
std : : copy_n ( fileEncryptionKey . cbegin ( ) , keyByteLength , result . begin ( ) ) ;
break ;
}
case 6 :
{
2019-08-11 15:46:26 +02:00
// This function must not be called with revision 6
Q_ASSERT ( false ) ;
2019-08-10 17:24:12 +02:00
break ;
}
default :
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( m_R ) ) ;
}
}
return result ;
}
QByteArray PDFStandardSecurityHandler : : createEntryValueU_r234 ( const QByteArray & fileEncryptionKey ) const
{
QByteArray result ;
switch ( m_R )
{
case 2 :
{
RC4_KEY key = { } ;
RC4_set_key ( & key , fileEncryptionKey . size ( ) , reinterpret_cast < const unsigned char * > ( fileEncryptionKey . data ( ) ) ) ;
result . resize ( static_cast < int > ( PDFPasswordPadding . size ( ) ) ) ;
RC4 ( & key , PDFPasswordPadding . size ( ) , PDFPasswordPadding . data ( ) , reinterpret_cast < unsigned char * > ( result . data ( ) ) ) ;
break ;
}
case 3 :
case 4 :
{
std : : array < uint8_t , MD5_DIGEST_LENGTH > hash ;
MD5_CTX context = { } ;
MD5_Init ( & context ) ;
MD5_Update ( & context , PDFPasswordPadding . data ( ) , PDFPasswordPadding . size ( ) ) ;
MD5_Update ( & context , m_ID . data ( ) , m_ID . size ( ) ) ;
MD5_Final ( hash . data ( ) , & context ) ;
RC4_KEY key = { } ;
RC4_set_key ( & key , fileEncryptionKey . size ( ) , reinterpret_cast < const unsigned char * > ( fileEncryptionKey . data ( ) ) ) ;
std : : array < uint8_t , MD5_DIGEST_LENGTH > encryptedHash ;
RC4 ( & key , hash . size ( ) , hash . data ( ) , reinterpret_cast < unsigned char * > ( encryptedHash . data ( ) ) ) ;
QByteArray transformedKey = fileEncryptionKey ;
for ( int i = 1 ; i < = 19 ; + + i )
{
for ( int j = 0 , keySize = fileEncryptionKey . size ( ) ; j < keySize ; + + j )
{
transformedKey [ j ] = static_cast < uint8_t > ( fileEncryptionKey [ j ] ) ^ static_cast < uint8_t > ( i ) ;
}
RC4_set_key ( & key , transformedKey . size ( ) , reinterpret_cast < const unsigned char * > ( transformedKey . data ( ) ) ) ;
RC4 ( & key , encryptedHash . size ( ) , encryptedHash . data ( ) , reinterpret_cast < unsigned char * > ( encryptedHash . data ( ) ) ) ;
}
// We do a hack here. In the PDF's specification, it is written, that arbitrary 16 bytes
// are appended to the 16 bytes result. We use the last 16 bytes of the U entry, because we
// want to compare byte arrays entirely (otherwise we must compare only 16 bytes to authenticate
// user password).
result = m_U ;
std : : copy_n ( encryptedHash . begin ( ) , encryptedHash . size ( ) , result . begin ( ) ) ;
break ;
}
default :
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( m_R ) ) ;
}
}
return result ;
}
QByteArray PDFStandardSecurityHandler : : createUserPasswordFromOwnerPassword ( const QByteArray & password ) const
{
QByteArray result ;
std : : array < uint8_t , 32 > paddedPassword = createPaddedPassword32 ( password ) ;
std : : array < uint8_t , MD5_DIGEST_LENGTH > hash ;
MD5_CTX context = { } ;
MD5_Init ( & context ) ;
MD5_Update ( & context , paddedPassword . data ( ) , paddedPassword . size ( ) ) ;
MD5_Final ( hash . data ( ) , & context ) ;
const int keyByteLength = m_keyLength / 8 ;
if ( keyByteLength > MD5_DIGEST_LENGTH )
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Encryption key length (%1) exceeded maximal value of . " ).arg(keyByteLength).arg(MD5_DIGEST_LENGTH)) ;
}
if ( m_R > = 3 )
{
for ( int i = 0 ; i < 50 ; + + i )
{
MD5_Init ( & context ) ;
MD5_Update ( & context , hash . data ( ) , keyByteLength ) ;
MD5_Final ( hash . data ( ) , & context ) ;
}
}
switch ( m_R )
{
case 2 :
{
RC4_KEY key = { } ;
RC4_set_key ( & key , keyByteLength , reinterpret_cast < const unsigned char * > ( hash . data ( ) ) ) ;
result . resize ( m_O . size ( ) ) ;
RC4 ( & key , m_O . size ( ) , reinterpret_cast < const unsigned char * > ( m_O . data ( ) ) , reinterpret_cast < unsigned char * > ( result . data ( ) ) ) ;
break ;
}
case 3 :
case 4 :
{
QByteArray buffer = m_O ;
QByteArray transformedKey ;
transformedKey . resize ( keyByteLength ) ;
std : : copy_n ( hash . data ( ) , keyByteLength , transformedKey . data ( ) ) ;
for ( int i = 19 ; i > = 0 ; - - i )
{
for ( int j = 0 , keySize = transformedKey . size ( ) ; j < keySize ; + + j )
{
transformedKey [ j ] = static_cast < uint8_t > ( hash [ j ] ) ^ static_cast < uint8_t > ( i ) ;
}
RC4_KEY key = { } ;
RC4_set_key ( & key , transformedKey . size ( ) , reinterpret_cast < const unsigned char * > ( transformedKey . data ( ) ) ) ;
RC4 ( & key , buffer . size ( ) , reinterpret_cast < const unsigned char * > ( buffer . data ( ) ) , reinterpret_cast < unsigned char * > ( buffer . data ( ) ) ) ;
}
result = buffer ;
break ;
}
default :
{
throw PDFParserException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( m_R ) ) ;
}
}
return result ;
}
std : : array < uint8_t , 32 > PDFStandardSecurityHandler : : createPaddedPassword32 ( const QByteArray & password ) const
{
std : : array < uint8_t , 32 > result = { } ;
int copiedBytes = qMin < int > ( static_cast < int > ( result . size ( ) ) , password . size ( ) ) ;
auto it = result . begin ( ) ;
for ( int i = 0 ; i < copiedBytes ; + + i )
{
* it + + = static_cast < uint8_t > ( password [ i ] ) ;
}
auto itPadding = PDFPasswordPadding . cbegin ( ) ;
for ( ; it ! = result . cend ( ) ; )
{
Q_ASSERT ( itPadding ! = PDFPasswordPadding . cend ( ) ) ;
* it + + = * itPadding + + ;
}
return result ;
}
2019-08-11 15:46:26 +02:00
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 ;
}
2019-08-04 18:26:15 +02:00
} // namespace pdf