    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     *
     */

    #include <oox/crypto/AgileEngine.hxx>

    #include <oox/helper/binaryinputstream.hxx>
    #include <oox/helper/binaryoutputstream.hxx>

    #include <sax/tools/converter.hxx>

    #include <comphelper/hash.hxx>
    #include <comphelper/docpasswordhelper.hxx>
    #include <comphelper/random.hxx>
    #include <comphelper/processfactory.hxx>
    #include <comphelper/base64.hxx>
    #include <comphelper/sequence.hxx>

    #include <filter/msfilter/mscodec.hxx>
    #include <tools/XmlWriter.hxx>

    #include <com/sun/star/io/XSeekable.hpp>
    #include <com/sun/star/io/XStream.hpp>
    #include <com/sun/star/uno/XComponentContext.hpp>
    #include <com/sun/star/xml/sax/XFastParser.hpp>
    #include <com/sun/star/xml/sax/XFastTokenHandler.hpp>
    #include <com/sun/star/xml/sax/FastParser.hpp>
    #include <com/sun/star/xml/sax/FastToken.hpp>

    using namespace css;
    using namespace css::beans;
    using namespace css::io;
    using namespace css::lang;
    using namespace css::uno;
    using namespace css::xml::sax;
    using namespace css::xml;

    namespace oox {
    namespace core {

    namespace {

    OUString stripNamespacePrefix(OUString const & rsInputName)
    {
        return rsInputName.copy(rsInputName.indexOf(":") + 1);
    }

    class AgileTokenHandler : public cppu::WeakImplHelper<XFastTokenHandler>
    {
    public:
        virtual sal_Int32 SAL_CALL getTokenFromUTF8(const Sequence< sal_Int8 >& /*nIdentifier*/) override
        {
            return FastToken::DONTKNOW;
        }

        virtual Sequence<sal_Int8> SAL_CALL getUTF8Identifier(sal_Int32 /*nToken*/) override
        {
            return Sequence<sal_Int8>();
        }
    };

    class AgileDocumentHandler : public ::cppu::WeakImplHelper<XFastDocumentHandler>
    {
        AgileEncryptionInfo& mInfo;

    public:
        explicit AgileDocumentHandler(AgileEncryptionInfo& rInfo) :
            mInfo(rInfo)
        {}

        void SAL_CALL startDocument() override {}
        void SAL_CALL endDocument() override {}
        void SAL_CALL processingInstruction( const OUString& /*rTarget*/, const OUString& /*rData*/ ) override {}
        void SAL_CALL setDocumentLocator( const Reference< XLocator >& /*xLocator*/ ) override {}
        void SAL_CALL startFastElement( sal_Int32 /*Element*/, const Reference< XFastAttributeList >& /*Attribs*/ ) override {}

        void SAL_CALL startUnknownElement( const OUString& /*aNamespace*/, const OUString& rName, const Reference< XFastAttributeList >& aAttributeList ) override
        {
            const OUString& rLocalName = stripNamespacePrefix(rName);

            for (const Attribute& rAttribute : aAttributeList->getUnknownAttributes())
            {
                const OUString& rAttrLocalName = stripNamespacePrefix(rAttribute.Name);

                if (rAttrLocalName == "spinCount")
                {
                    ::sax::Converter::convertNumber(mInfo.spinCount, rAttribute.Value);
                }
                else if (rAttrLocalName == "saltSize")
                {
                    ::sax::Converter::convertNumber(mInfo.saltSize, rAttribute.Value);
                }
                else if (rAttrLocalName == "blockSize")
                {
                    ::sax::Converter::convertNumber(mInfo.blockSize, rAttribute.Value);
                }
                else if (rAttrLocalName == "keyBits")
                {
                    ::sax::Converter::convertNumber(mInfo.keyBits, rAttribute.Value);
                }
                else if (rAttrLocalName == "hashSize")
                {
                    ::sax::Converter::convertNumber(mInfo.hashSize, rAttribute.Value);
                }
                else if (rAttrLocalName == "cipherAlgorithm")
                {
                    mInfo.cipherAlgorithm = rAttribute.Value;
                }
                else if (rAttrLocalName == "cipherChaining")
                {
                    mInfo.cipherChaining = rAttribute.Value;
                }
                else if (rAttrLocalName == "hashAlgorithm")
                {
                    mInfo.hashAlgorithm = rAttribute.Value;
                }
                else if (rAttrLocalName == "saltValue")
                {
                    Sequence<sal_Int8> saltValue;
                    comphelper::Base64::decode(saltValue, rAttribute.Value);
                    if (rLocalName == "encryptedKey")
                        mInfo.saltValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(saltValue);
                    else if (rLocalName == "keyData")
                        mInfo.keyDataSalt = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(saltValue);
                }
                else if (rAttrLocalName == "encryptedVerifierHashInput")
                {
                    Sequence<sal_Int8> encryptedVerifierHashInput;
                    comphelper::Base64::decode(encryptedVerifierHashInput, rAttribute.Value);
                    mInfo.encryptedVerifierHashInput = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedVerifierHashInput);
                }
                else if (rAttrLocalName == "encryptedVerifierHashValue")
                {
                    Sequence<sal_Int8> encryptedVerifierHashValue;
                    comphelper::Base64::decode(encryptedVerifierHashValue, rAttribute.Value);
                    mInfo.encryptedVerifierHashValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedVerifierHashValue);
                }
                else if (rAttrLocalName == "encryptedKeyValue")
                {
                    Sequence<sal_Int8> encryptedKeyValue;
                    comphelper::Base64::decode(encryptedKeyValue, rAttribute.Value);
                    mInfo.encryptedKeyValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedKeyValue);
                }
                if (rAttrLocalName == "encryptedHmacKey")
                {
                    Sequence<sal_Int8> aValue;
                    comphelper::Base64::decode(aValue, rAttribute.Value);
                    mInfo.hmacEncryptedKey = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
                }
                if (rAttrLocalName == "encryptedHmacValue")
                {
                    Sequence<sal_Int8> aValue;
                    comphelper::Base64::decode(aValue, rAttribute.Value);
                    mInfo.hmacEncryptedValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
                }
            }
        }

        void SAL_CALL endFastElement( sal_Int32 /*aElement*/ ) override
        {}
        void SAL_CALL endUnknownElement( const OUString& /*aNamespace*/, const OUString& /*aName*/ ) override
        {}

        Reference< XFastContextHandler > SAL_CALL createFastChildContext( sal_Int32 /*aElement*/, const Reference< XFastAttributeList >& /*aAttribs*/ ) override
        {
            return nullptr;
        }

        Reference< XFastContextHandler > SAL_CALL createUnknownChildContext( const OUString& /*aNamespace*/, const OUString& /*aName*/, const Reference< XFastAttributeList >& /*aAttribs*/ ) override
        {
            return this;
        }

        void SAL_CALL characters( const OUString& /*aChars*/ ) override
        {}
    };

    constexpr const sal_uInt32 constSegmentLength = 4096;

    static const std::vector<sal_uInt8> constBlock1 { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
    static const std::vector<sal_uInt8> constBlock2 { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
    static const std::vector<sal_uInt8> constBlock3 { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
    static const std::vector<sal_uInt8> constBlockHmac1 { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };
    static const std::vector<sal_uInt8> constBlockHmac2 { 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33 };

    bool hashCalc(std::vector<sal_uInt8>& output,
                  std::vector<sal_uInt8>& input,
                  const OUString& sAlgorithm )
    {
        if (sAlgorithm == "SHA1")
        {
            std::vector<unsigned char> out = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA1);
            output = out;
            return true;
        }
        else if (sAlgorithm == "SHA512")
        {
            std::vector<unsigned char> out = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA512);
            output = out;
            return true;
        }
        return false;
    }

    CryptoHashType cryptoHashTypeFromString(OUString const & sAlgorithm)
    {
        if (sAlgorithm == "SHA512")
            return CryptoHashType::SHA512;
        return CryptoHashType::SHA1;
    }

    } // namespace

    AgileEngine::AgileEngine()
        : meEncryptionPreset(AgileEncryptionPreset::AES_256_SHA512)
    {}

    Crypto::CryptoType AgileEngine::cryptoType(const AgileEncryptionInfo& rInfo)
    {
        if (rInfo.keyBits == 128 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
            return Crypto::AES_128_CBC;
        else if (rInfo.keyBits == 256 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
            return Crypto::AES_256_CBC;
        return Crypto::UNKNOWN;
    }

    std::vector<sal_uInt8> calculateIV(comphelper::HashType eType,
                                 std::vector<sal_uInt8> const & rSalt,
                                 std::vector<sal_uInt8> const & rBlock,
                                 sal_Int32 nCipherBlockSize)
    {
        comphelper::Hash aHasher(eType);
        aHasher.update(rSalt.data(), rSalt.size());
        aHasher.update(rBlock.data(), rBlock.size());
        std::vector<sal_uInt8> aIV = aHasher.finalize();
        aIV.resize(roundUp(sal_Int32(aIV.size()), nCipherBlockSize), 0x36);
        return aIV;
    }

    void AgileEngine::calculateBlock(
        std::vector<sal_uInt8> const & rBlock,
        std::vector<sal_uInt8>& rHashFinal,
        std::vector<sal_uInt8>& rInput,
        std::vector<sal_uInt8>& rOutput)
    {
        std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
        std::vector<sal_uInt8> dataFinal(mInfo.hashSize + rBlock.size(), 0);
        std::copy(rHashFinal.begin(), rHashFinal.end(), dataFinal.begin());
        std::copy(rBlock.begin(), rBlock.end(), dataFinal.begin() + mInfo.hashSize);

        hashCalc(hash, dataFinal, mInfo.hashAlgorithm);

        sal_Int32 keySize = mInfo.keyBits / 8;
        std::vector<sal_uInt8> key(keySize, 0);

        std::copy(hash.begin(), hash.begin() + keySize, key.begin());

        Decrypt aDecryptor(key, mInfo.saltValue, cryptoType(mInfo));
        aDecryptor.update(rOutput, rInput);
    }

    void AgileEngine::encryptBlock(
        std::vector<sal_uInt8> const & rBlock,
        std::vector<sal_uInt8> & rHashFinal,
        std::vector<sal_uInt8> & rInput,
        std::vector<sal_uInt8> & rOutput)
    {
        std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
        std::vector<sal_uInt8> dataFinal(mInfo.hashSize + rBlock.size(), 0);
        std::copy(rHashFinal.begin(), rHashFinal.end(), dataFinal.begin());
        std::copy(rBlock.begin(), rBlock.end(), dataFinal.begin() + mInfo.hashSize);

        hashCalc(hash, dataFinal, mInfo.hashAlgorithm);

        sal_Int32 keySize = mInfo.keyBits / 8;
        std::vector<sal_uInt8> key(keySize, 0);

        std::copy(hash.begin(), hash.begin() + keySize, key.begin());

        Encrypt aEncryptor(key, mInfo.saltValue, cryptoType(mInfo));

        aEncryptor.update(rOutput, rInput);
    }

    void AgileEngine::calculateHashFinal(const OUString& rPassword, std::vector<sal_uInt8>& aHashFinal)
    {
        aHashFinal = comphelper::DocPasswordHelper::GetOoxHashAsVector(
                        rPassword, mInfo.saltValue, mInfo.spinCount,
                        comphelper::Hash::IterCount::PREPEND, mInfo.hashAlgorithm);
    }

    namespace
    {

    bool generateBytes(std::vector<sal_uInt8> & rBytes, sal_Int32 nSize)
    {
        size_t nMax = std::min(rBytes.size(), size_t(nSize));

        for (size_t i = 0; i < nMax; ++i)
        {
            rBytes[i] = sal_uInt8(comphelper::rng::uniform_uint_distribution(0, 0xFF));
        }

        return true;
    }

    } // end anonymous namespace

    bool AgileEngine::decryptAndCheckVerifierHash(OUString const & rPassword)
    {
        std::vector<sal_uInt8> hashFinal(mInfo.hashSize, 0);
        calculateHashFinal(rPassword, hashFinal);

        std::vector<sal_uInt8>& encryptedHashInput = mInfo.encryptedVerifierHashInput;
        std::vector<sal_uInt8> hashInput(mInfo.saltSize, 0);
        calculateBlock(constBlock1, hashFinal, encryptedHashInput, hashInput);

        std::vector<sal_uInt8>& encryptedHashValue = mInfo.encryptedVerifierHashValue;
        std::vector<sal_uInt8> hashValue(encryptedHashValue.size(), 0);
        calculateBlock(constBlock2, hashFinal, encryptedHashValue, hashValue);

        std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
        hashCalc(hash, hashInput, mInfo.hashAlgorithm);

        return (hash.size() <= hashValue.size() && std::equal(hash.begin(), hash.end(), hashValue.begin()));
    }

    void AgileEngine::decryptEncryptionKey(OUString const & rPassword)
    {
        sal_Int32 nKeySize = mInfo.keyBits / 8;

        mKey.clear();
        mKey.resize(nKeySize, 0);

        std::vector<sal_uInt8> aPasswordHash(mInfo.hashSize, 0);
        calculateHashFinal(rPassword, aPasswordHash);

        calculateBlock(constBlock3, aPasswordHash, mInfo.encryptedKeyValue, mKey);
    }

    // TODO: Rename
    bool AgileEngine::generateEncryptionKey(OUString const & rPassword)
    {
        bool bResult = decryptAndCheckVerifierHash(rPassword);

        if (bResult)
        {
            decryptEncryptionKey(rPassword);
            decryptHmacKey();
            decryptHmacValue();
        }
        return bResult;
    }

    bool AgileEngine::decryptHmacKey()
    {
        // Initialize hmacKey
        mInfo.hmacKey.clear();
        mInfo.hmacKey.resize(mInfo.hmacEncryptedKey.size(), 0);

        // Calculate IV
        comphelper::HashType eType;
        if (mInfo.hashAlgorithm == "SHA1")
            eType = comphelper::HashType::SHA1;
        else if (mInfo.hashAlgorithm == "SHA512")
            eType = comphelper::HashType::SHA512;
        else
            return false;

        std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac1, mInfo.blockSize);

        // Decrypt with out key, calculated iv
        Decrypt aDecrypt(mKey, iv, cryptoType(mInfo));
        aDecrypt.update(mInfo.hmacKey, mInfo.hmacEncryptedKey);

        mInfo.hmacKey.resize(mInfo.hashSize, 0);

        return true;
    }

    bool AgileEngine::decryptHmacValue()
    {
        // Initialize hmacHash
        mInfo.hmacHash.clear();
        mInfo.hmacHash.resize(mInfo.hmacEncryptedValue.size(), 0);

        // Calculate IV
        comphelper::HashType eType;
        if (mInfo.hashAlgorithm == "SHA1")
            eType = comphelper::HashType::SHA1;
        else if (mInfo.hashAlgorithm == "SHA512")
            eType = comphelper::HashType::SHA512;
        else
            return false;
        std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac2, mInfo.blockSize);

        // Decrypt with out key, calculated iv
        Decrypt aDecrypt(mKey, iv, cryptoType(mInfo));
        aDecrypt.update(mInfo.hmacHash, mInfo.hmacEncryptedValue);

        mInfo.hmacHash.resize(mInfo.hashSize, 0);

        return true;
    }

    bool AgileEngine::checkDataIntegrity()
    {
        bool bResult = (mInfo.hmacHash.size() == mInfo.hmacCalculatedHash.size() &&
                   std::equal(mInfo.hmacHash.begin(), mInfo.hmacHash.end(), mInfo.hmacCalculatedHash.begin()));

        return bResult;
    }

    bool AgileEngine::decrypt(BinaryXInputStream& aInputStream,
                              BinaryXOutputStream& aOutputStream)
    {
        CryptoHash aCryptoHash(mInfo.hmacKey, cryptoHashTypeFromString(mInfo.hashAlgorithm));

        sal_uInt32 totalSize = aInputStream.readuInt32(); // Document unencrypted size - 4 bytes
        // account for size in HMAC
        std::vector<sal_uInt8> aSizeBytes(sizeof(sal_uInt32));
        ByteOrderConverter::writeLittleEndian(aSizeBytes.data(), totalSize);
        aCryptoHash.update(aSizeBytes);

        aInputStream.skip(4);  // Reserved 4 Bytes
        // account for reserved 4 bytes (must be 0)
        std::vector<sal_uInt8> aReserved{0,0,0,0};
        aCryptoHash.update(aReserved);

        // setup decryption
        std::vector<sal_uInt8>& keyDataSalt = mInfo.keyDataSalt;

        sal_uInt32 saltSize = mInfo.saltSize;
        sal_uInt32 keySize = mInfo.keyBits / 8;

        sal_uInt32 segment = 0;

        std::vector<sal_uInt8> saltWithBlockKey(saltSize + sizeof(segment), 0);
        std::copy(keyDataSalt.begin(), keyDataSalt.end(), saltWithBlockKey.begin());

        std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
        std::vector<sal_uInt8> iv(keySize, 0);

        std::vector<sal_uInt8> inputBuffer(constSegmentLength);
        std::vector<sal_uInt8> outputBuffer(constSegmentLength);
        sal_uInt32 inputLength;
        sal_uInt32 outputLength;
        sal_uInt32 remaining = totalSize;

        while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
        {
            sal_uInt8* segmentBegin = reinterpret_cast<sal_uInt8*>(&segment);
            sal_uInt8* segmentEnd   = segmentBegin + sizeof(segment);
            std::copy(segmentBegin, segmentEnd, saltWithBlockKey.begin() + saltSize);

            hashCalc(hash, saltWithBlockKey, mInfo.hashAlgorithm);

            // Only if hash > keySize
            std::copy(hash.begin(), hash.begin() + keySize, iv.begin());

            Decrypt aDecryptor(mKey, iv, AgileEngine::cryptoType(mInfo));
            outputLength = aDecryptor.update(outputBuffer, inputBuffer, inputLength);

            sal_uInt32 writeLength = std::min(outputLength, remaining);

            aCryptoHash.update(inputBuffer, inputLength);

            aOutputStream.writeMemory(outputBuffer.data(), writeLength);

            remaining -= outputLength;
            segment++;
        }

        mInfo.hmacCalculatedHash = aCryptoHash.finalize();

        return true;
    }

    bool AgileEngine::readEncryptionInfo(uno::Reference<io::XInputStream> & rxInputStream)
    {
        // Check reserved value
        std::vector<sal_uInt8> aExpectedReservedBytes(sizeof(sal_uInt32));
        ByteOrderConverter::writeLittleEndian(aExpectedReservedBytes.data(), msfilter::AGILE_ENCRYPTION_RESERVED);

        uno::Sequence<sal_Int8> aReadReservedBytes(sizeof(sal_uInt32));
        rxInputStream->readBytes(aReadReservedBytes, aReadReservedBytes.getLength());

        if (!std::equal(aReadReservedBytes.begin(), aReadReservedBytes.end(), aExpectedReservedBytes.begin()))
            return false;

        mInfo.spinCount = 0;
        mInfo.saltSize = 0;
        mInfo.keyBits = 0;
        mInfo.hashSize = 0;
        mInfo.blockSize = 0;

        Reference<XFastDocumentHandler> xFastDocumentHandler(new AgileDocumentHandler(mInfo));
        Reference<XFastTokenHandler> xFastTokenHandler(new AgileTokenHandler);

        Reference<XFastParser> xParser(css::xml::sax::FastParser::create(comphelper::getProcessComponentContext()));

        xParser->setFastDocumentHandler(xFastDocumentHandler);
        xParser->setTokenHandler(xFastTokenHandler);

        InputSource aInputSource;
        aInputSource.aInputStream = rxInputStream;
        xParser->parseStream(aInputSource);

        // CHECK info data
        if (2 > mInfo.blockSize || mInfo.blockSize > 4096)
            return false;

        if (0 > mInfo.spinCount || mInfo.spinCount > 10000000)
            return false;

        if (1 > mInfo.saltSize|| mInfo.saltSize > 65536) // Check
            return false;

        // AES 128 CBC with SHA1
        if (mInfo.keyBits         == 128 &&
            mInfo.cipherAlgorithm == "AES" &&
            mInfo.cipherChaining  == "ChainingModeCBC" &&
            mInfo.hashAlgorithm   == "SHA1" &&
            mInfo.hashSize        == msfilter::SHA1_HASH_LENGTH)
        {
            return true;
        }

        // AES 256 CBC with SHA512
        if (mInfo.keyBits         == 256 &&
            mInfo.cipherAlgorithm == "AES" &&
            mInfo.cipherChaining  == "ChainingModeCBC" &&
            mInfo.hashAlgorithm   == "SHA512" &&
            mInfo.hashSize        == msfilter::SHA512_HASH_LENGTH)
        {
            return true;
        }

        return false;
    }

    bool AgileEngine::generateAndEncryptVerifierHash(OUString const & rPassword)
    {
        if (!generateBytes(mInfo.saltValue, mInfo.saltSize))
            return false;

        std::vector<sal_uInt8> unencryptedVerifierHashInput(mInfo.saltSize);
        if (!generateBytes(unencryptedVerifierHashInput, mInfo.saltSize))
            return false;

        // HASH - needs to be modified to be multiple of block size
        sal_Int32 nVerifierHash = roundUp(mInfo.hashSize, mInfo.blockSize);
        std::vector<sal_uInt8> unencryptedVerifierHashValue;
        if (!hashCalc(unencryptedVerifierHashValue, unencryptedVerifierHashInput, mInfo.hashAlgorithm))
            return false;
        unencryptedVerifierHashValue.resize(nVerifierHash, 0);

        std::vector<sal_uInt8> hashFinal(mInfo.hashSize, 0);
        calculateHashFinal(rPassword, hashFinal);

        encryptBlock(constBlock1, hashFinal, unencryptedVerifierHashInput, mInfo.encryptedVerifierHashInput);

        encryptBlock(constBlock2, hashFinal, unencryptedVerifierHashValue, mInfo.encryptedVerifierHashValue);

        return true;
    }

    bool AgileEngine::encryptHmacKey()
    {
        // Initialize hmacKey
        mInfo.hmacKey.clear();
        mInfo.hmacKey.resize(mInfo.hashSize, 0);

        if (!generateBytes(mInfo.hmacKey, mInfo.hashSize))
            return false;

        // Encrypted salt must be multiple of block size
        sal_Int32 nEncryptedSaltSize = oox::core::roundUp(mInfo.hashSize, mInfo.blockSize);

        // We need to extend hmacSalt to multiple of block size, padding with 0x36
        std::vector<sal_uInt8> extendedSalt(mInfo.hmacKey);
        extendedSalt.resize(nEncryptedSaltSize, 0x36);

        // Initialize hmacEncryptedKey
        mInfo.hmacEncryptedKey.clear();
        mInfo.hmacEncryptedKey.resize(nEncryptedSaltSize, 0);

        // Calculate IV
        comphelper::HashType eType;
        if (mInfo.hashAlgorithm == "SHA1")
            eType = comphelper::HashType::SHA1;
        else if (mInfo.hashAlgorithm == "SHA512")
            eType = comphelper::HashType::SHA512;
        else
            return false;

        std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac1, mInfo.blockSize);

        // Encrypt with out key, calculated iv
        Encrypt aEncryptor(mKey, iv, cryptoType(mInfo));
        aEncryptor.update(mInfo.hmacEncryptedKey, extendedSalt);

        return true;
    }

    bool AgileEngine::encryptHmacValue()
    {
        sal_Int32 nEncryptedValueSize = roundUp(mInfo.hashSize, mInfo.blockSize);
        mInfo.hmacEncryptedValue.clear();
        mInfo.hmacEncryptedValue.resize(nEncryptedValueSize, 0);

        std::vector<sal_uInt8> extendedHash(mInfo.hmacHash);
        extendedHash.resize(nEncryptedValueSize, 0x36);

        // Calculate IV
        comphelper::HashType eType;
        if (mInfo.hashAlgorithm == "SHA1")
            eType = comphelper::HashType::SHA1;
        else if (mInfo.hashAlgorithm == "SHA512")
            eType = comphelper::HashType::SHA512;
        else
            return false;

        std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac2, mInfo.blockSize);

        // Encrypt with out key, calculated iv
        Encrypt aEncryptor(mKey, iv, cryptoType(mInfo));
        aEncryptor.update(mInfo.hmacEncryptedValue, extendedHash);

        return true;
    }

    bool AgileEngine::encryptEncryptionKey(OUString const & rPassword)
    {
        sal_Int32 nKeySize = mInfo.keyBits / 8;

        mKey.clear();
        mKey.resize(nKeySize, 0);

        mInfo.encryptedKeyValue.clear();
        mInfo.encryptedKeyValue.resize(nKeySize, 0);

        if (!generateBytes(mKey, nKeySize))
            return false;

        std::vector<sal_uInt8> aPasswordHash(mInfo.hashSize, 0);
        calculateHashFinal(rPassword, aPasswordHash);

        encryptBlock(constBlock3, aPasswordHash, mKey, mInfo.encryptedKeyValue);

        return true;
    }

    bool AgileEngine::setupEncryption(OUString const & rPassword)
    {
        if (meEncryptionPreset == AgileEncryptionPreset::AES_128_SHA1)
            setupEncryptionParameters({ 100000, 16, 128, 20, 16, OUString("AES"), OUString("ChainingModeCBC"), OUString("SHA1") });
        else
            setupEncryptionParameters({ 100000, 16, 256, 64, 16, OUString("AES"), OUString("ChainingModeCBC"), OUString("SHA512") });

        if (!setupEncryptionKey(rPassword))
            return false;
        return true;
    }

    void AgileEngine::setupEncryptionParameters(AgileEncryptionParameters const & rAgileEncryptionParameters)
    {
        mInfo.spinCount = rAgileEncryptionParameters.spinCount;
        mInfo.saltSize = rAgileEncryptionParameters.saltSize;
        mInfo.keyBits = rAgileEncryptionParameters.keyBits;
        mInfo.hashSize = rAgileEncryptionParameters.hashSize;
        mInfo.blockSize = rAgileEncryptionParameters.blockSize;

        mInfo.cipherAlgorithm = rAgileEncryptionParameters.cipherAlgorithm;
        mInfo.cipherChaining = rAgileEncryptionParameters.cipherChaining;
        mInfo.hashAlgorithm = rAgileEncryptionParameters.hashAlgorithm;

        mInfo.keyDataSalt.resize(mInfo.saltSize);
        mInfo.saltValue.resize(mInfo.saltSize);
        mInfo.encryptedVerifierHashInput.resize(mInfo.saltSize);
        mInfo.encryptedVerifierHashValue.resize(roundUp(mInfo.hashSize, mInfo.blockSize), 0);
    }

    bool AgileEngine::setupEncryptionKey(OUString const & rPassword)
    {
        if (!generateAndEncryptVerifierHash(rPassword))
            return false;
        if (!encryptEncryptionKey(rPassword))
            return false;
        if (!generateBytes(mInfo.keyDataSalt, mInfo.saltSize))
            return false;
        if (!encryptHmacKey())
            return false;
        return true;
    }

    void AgileEngine::writeEncryptionInfo(BinaryXOutputStream & rStream)
    {
        rStream.WriteUInt32(msfilter::VERSION_INFO_AGILE);
        rStream.WriteUInt32(msfilter::AGILE_ENCRYPTION_RESERVED);

        SvMemoryStream aMemStream;
        tools::XmlWriter aXmlWriter(&aMemStream);

        if (aXmlWriter.startDocument(0/*nIndent*/))
        {
            aXmlWriter.startElement("", "encryption", "http://schemas.microsoft.com/office/2006/encryption");
            aXmlWriter.attribute("xmlns:p", OString("http://schemas.microsoft.com/office/2006/keyEncryptor/password"));

            aXmlWriter.startElement("keyData");
            aXmlWriter.attribute("saltSize", mInfo.saltSize);
            aXmlWriter.attribute("blockSize", mInfo.blockSize);
            aXmlWriter.attribute("keyBits", mInfo.keyBits);
            aXmlWriter.attribute("hashSize", mInfo.hashSize);
            aXmlWriter.attribute("cipherAlgorithm", mInfo.cipherAlgorithm);
            aXmlWriter.attribute("cipherChaining", mInfo.cipherChaining);
            aXmlWriter.attribute("hashAlgorithm", mInfo.hashAlgorithm);
            aXmlWriter.attributeBase64("saltValue", mInfo.keyDataSalt);
            aXmlWriter.endElement();

            aXmlWriter.startElement("dataIntegrity");
            aXmlWriter.attributeBase64("encryptedHmacKey", mInfo.hmacEncryptedKey);
            aXmlWriter.attributeBase64("encryptedHmacValue", mInfo.hmacEncryptedValue);
            aXmlWriter.endElement();

            aXmlWriter.startElement("keyEncryptors");
            aXmlWriter.startElement("keyEncryptor");
            aXmlWriter.attribute("uri", OString("http://schemas.microsoft.com/office/2006/keyEncryptor/password"));

            aXmlWriter.startElement("p", "encryptedKey", "");
            aXmlWriter.attribute("spinCount", mInfo.spinCount);
            aXmlWriter.attribute("saltSize", mInfo.saltSize);
            aXmlWriter.attribute("blockSize", mInfo.blockSize);
            aXmlWriter.attribute("keyBits", mInfo.keyBits);
            aXmlWriter.attribute("hashSize", mInfo.hashSize);
            aXmlWriter.attribute("cipherAlgorithm", mInfo.cipherAlgorithm);
            aXmlWriter.attribute("cipherChaining", mInfo.cipherChaining);
            aXmlWriter.attribute("hashAlgorithm", mInfo.hashAlgorithm);
            aXmlWriter.attributeBase64("saltValue", mInfo.saltValue);
            aXmlWriter.attributeBase64("encryptedVerifierHashInput", mInfo.encryptedVerifierHashInput);
            aXmlWriter.attributeBase64("encryptedVerifierHashValue", mInfo.encryptedVerifierHashValue);
            aXmlWriter.attributeBase64("encryptedKeyValue", mInfo.encryptedKeyValue);
            aXmlWriter.endElement();

            aXmlWriter.endElement();
            aXmlWriter.endElement();

            aXmlWriter.endElement();
            aXmlWriter.endDocument();
        }
        rStream.writeMemory(aMemStream.GetData(), aMemStream.GetSize());
    }

    void AgileEngine::encrypt(css::uno::Reference<css::io::XInputStream> &  rxInputStream,
                              css::uno::Reference<css::io::XOutputStream> & rxOutputStream,
                              sal_uInt32 nSize)
    {
        CryptoHash aCryptoHash(mInfo.hmacKey, cryptoHashTypeFromString(mInfo.hashAlgorithm));

        BinaryXOutputStream aBinaryOutputStream(rxOutputStream, false);
        BinaryXInputStream aBinaryInputStream(rxInputStream, false);

        std::vector<sal_uInt8> aSizeBytes(sizeof(sal_uInt32));
        ByteOrderConverter::writeLittleEndian(aSizeBytes.data(), nSize);
        aBinaryOutputStream.writeMemory(aSizeBytes.data(), aSizeBytes.size()); // size
        aCryptoHash.update(aSizeBytes, aSizeBytes.size());

        std::vector<sal_uInt8> aNull{0,0,0,0};
        aBinaryOutputStream.writeMemory(aNull.data(), aNull.size()); // reserved
        aCryptoHash.update(aNull, aNull.size());

        std::vector<sal_uInt8>& keyDataSalt = mInfo.keyDataSalt;

        sal_uInt32 saltSize = mInfo.saltSize;
        sal_uInt32 keySize = mInfo.keyBits / 8;

        sal_uInt32 nSegment = 0;
        sal_uInt32 nSegmentByteSize = sizeof(nSegment);

        std::vector<sal_uInt8> saltWithBlockKey(saltSize + nSegmentByteSize, 0);
        std::copy(keyDataSalt.begin(), keyDataSalt.end(), saltWithBlockKey.begin());

        std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
        std::vector<sal_uInt8> iv(keySize, 0);

        std::vector<sal_uInt8> inputBuffer(constSegmentLength);
        std::vector<sal_uInt8> outputBuffer(constSegmentLength);
        sal_uInt32 inputLength;
        sal_uInt32 outputLength;

        while ((inputLength = aBinaryInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
        {
            sal_uInt32 correctedInputLength = inputLength % mInfo.blockSize == 0 ?
                            inputLength : oox::core::roundUp(inputLength, sal_uInt32(mInfo.blockSize));

            // Update Key
            sal_uInt8* segmentBegin = reinterpret_cast<sal_uInt8*>(&nSegment);
            sal_uInt8* segmentEnd   = segmentBegin + nSegmentByteSize;
            std::copy(segmentBegin, segmentEnd, saltWithBlockKey.begin() + saltSize);

            hashCalc(hash, saltWithBlockKey, mInfo.hashAlgorithm);

            // Only if hash > keySize
            std::copy(hash.begin(), hash.begin() + keySize, iv.begin());

            Encrypt aEncryptor(mKey, iv, AgileEngine::cryptoType(mInfo));
            outputLength = aEncryptor.update(outputBuffer, inputBuffer, correctedInputLength);
            aBinaryOutputStream.writeMemory(outputBuffer.data(), outputLength);
            aCryptoHash.update(outputBuffer, outputLength);

            nSegment++;
        }
        mInfo.hmacHash = aCryptoHash.finalize();
        encryptHmacValue();
    }

    } // namespace core
    } // namespace oox

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: mInfo.