[Libreoffice-commits] core.git: comphelper/qa comphelper/source include/comphelper

Eike Rathke erack at redhat.com
Sat Feb 24 10:29:16 UTC 2018


 comphelper/qa/unit/test_hash.cxx |   29 ++++++++++
 comphelper/source/misc/hash.cxx  |  112 +++++++++++++++++++++++++++++++++++++++
 include/comphelper/hash.hxx      |   63 +++++++++++++++++++++
 3 files changed, 204 insertions(+)

New commits:
commit 556c2eaffcdb541317ed148d58c6c973fa6fd0e6
Author: Eike Rathke <erack at redhat.com>
Date:   Fri Feb 23 18:23:04 2018 +0100

    Implement OOXML password hashing algorithm, tdf#104250 prep
    
    As per https://msdn.microsoft.com/en-us/library/dd920692
    
    Change-Id: Iebacaf3549dab28fd3033f9c241130fd66782b25
    Reviewed-on: https://gerrit.libreoffice.org/50259
    Tested-by: Jenkins <ci at libreoffice.org>
    Reviewed-by: Eike Rathke <erack at redhat.com>

diff --git a/comphelper/qa/unit/test_hash.cxx b/comphelper/qa/unit/test_hash.cxx
index f83f91d3286a..beec2c537cf4 100644
--- a/comphelper/qa/unit/test_hash.cxx
+++ b/comphelper/qa/unit/test_hash.cxx
@@ -9,6 +9,7 @@
 
 #include <comphelper/hash.hxx>
 
+#include <rtl/ustring.hxx>
 #include <sal/log.hxx>
 #include <iomanip>
 
@@ -22,12 +23,16 @@ public:
     void testSHA1();
     void testSHA256();
     void testSHA512();
+    void testSHA512_NoSaltNoSpin();
+    void testSHA512_saltspin();
 
     CPPUNIT_TEST_SUITE(TestHash);
     CPPUNIT_TEST(testMD5);
     CPPUNIT_TEST(testSHA1);
     CPPUNIT_TEST(testSHA256);
     CPPUNIT_TEST(testSHA512);
+    CPPUNIT_TEST(testSHA512_NoSaltNoSpin);
+    CPPUNIT_TEST(testSHA512_saltspin);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -87,6 +92,30 @@ void TestHash::testSHA512()
     CPPUNIT_ASSERT_EQUAL(aStr, tostring(calculate_hash));
 }
 
+// Must be identical to testSHA512()
+void TestHash::testSHA512_NoSaltNoSpin()
+{
+    const char* const pInput = "";
+    std::vector<unsigned char> calculate_hash =
+        comphelper::Hash::calculateHash( reinterpret_cast<const unsigned char*>(pInput), 0,
+                nullptr, 0, 0, comphelper::HashType::SHA512);
+    CPPUNIT_ASSERT_EQUAL(size_t(64), calculate_hash.size());
+    std::string aStr("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
+    CPPUNIT_ASSERT_EQUAL(aStr, tostring(calculate_hash));
+}
+
+// Password, salt, hash and spin count taken from OOXML sheetProtection of
+// tdf#104250 https://bugs.documentfoundation.org/attachment.cgi?id=129104
+void TestHash::testSHA512_saltspin()
+{
+    const OUString aPass("pwd");
+    const OUString aAlgo("SHA-512");
+    const OUString aSalt("876MLoKTq42+/DLp415iZQ==");
+    const OUString aHash = comphelper::Hash::calculateHash( aPass, aSalt, 100000, aAlgo);
+    OUString aStr("5l3mgNHXpWiFaBPv5Yso1Xd/UifWvQWmlDnl/hsCYbFT2sJCzorjRmBCQ/3qeDu6Q/4+GIE8a1DsdaTwYh1q2g==");
+    CPPUNIT_ASSERT_EQUAL(aStr, aHash);
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(TestHash);
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/comphelper/source/misc/hash.cxx b/comphelper/source/misc/hash.cxx
index 8da77a792e6a..7a8c3fecd7e9 100644
--- a/comphelper/source/misc/hash.cxx
+++ b/comphelper/source/misc/hash.cxx
@@ -8,6 +8,11 @@
  */
 
 #include <comphelper/hash.hxx>
+#include <comphelper/base64.hxx>
+#include <comphelper/sequence.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/alloc.h>
+#include <osl/endian.h>
 #include <config_oox.h>
 
 #if USE_TLS_NSS
@@ -150,6 +155,113 @@ std::vector<unsigned char> Hash::calculateHash(const unsigned char* pInput, size
     return aHash.finalize();
 }
 
+std::vector<unsigned char> Hash::calculateHash(
+        const unsigned char* pInput, size_t nLength,
+        const unsigned char* pSalt, size_t nSaltLen,
+        sal_uInt32 nSpinCount,
+        HashType eType)
+{
+    if (!pSalt)
+        nSaltLen = 0;
+
+    if (!nSaltLen && !nSpinCount)
+        return calculateHash( pInput, nLength, eType);
+
+    Hash aHash(eType);
+    std::vector<unsigned char> hash;
+    if (nSaltLen)
+    {
+        std::vector<unsigned char> initialData( nSaltLen + nLength);
+        std::copy( pSalt, pSalt + nSaltLen, initialData.begin());
+        std::copy( pInput, pInput + nLength, initialData.begin() + nSaltLen);
+        aHash.update( initialData.data(), initialData.size());
+        rtl_secureZeroMemory( initialData.data(), initialData.size());
+    }
+    else
+    {
+        aHash.update( pInput, nLength);
+    }
+    hash = aHash.finalize();
+
+    if (nSpinCount)
+    {
+        // https://msdn.microsoft.com/en-us/library/dd920692
+        // says the iteration is concatenated after the hash.
+        // XXX NOTE: oox/source/crypto/AgileEngine.cxx
+        // AgileEngine::calculateHashFinal() prepends the iteration value, they
+        // do things differently for write protection and encryption passwords.
+        // https://msdn.microsoft.com/en-us/library/dd924776
+        /* TODO: maybe pass a flag whether to prepend or append, and then let
+         * AgileEngine::calculateHashFinal() call this function. */
+        const size_t nIterPos = hash.size();
+        const size_t nHashPos = 0;
+        //const size_t nIterPos = 0;
+        //const size_t nHashPos = 4;
+        std::vector<unsigned char> data( hash.size() + 4, 0);
+        for (sal_uInt32 i = 0; i < nSpinCount; ++i)
+        {
+            std::copy( hash.begin(), hash.end(), data.begin() + nHashPos);
+#ifdef OSL_BIGENDIAN
+            sal_uInt32 be = i;
+            sal_uInt8* p = reinterpret_cast<sal_uInt8*>(&be);
+            std::swap( p[0], p[3] );
+            std::swap( p[1], p[2] );
+            memcpy( data.data() + nIterPos, &be, 4);
+#else
+            memcpy( data.data() + nIterPos, &i, 4);
+#endif
+            /* TODO: isn't there something better than
+             * creating/finalizing/destroying on each iteration? */
+            Hash aReHash(eType);
+            aReHash.update( data.data(), data.size());
+            hash = aReHash.finalize();
+        }
+    }
+
+    return hash;
+}
+
+std::vector<unsigned char> Hash::calculateHash(
+        const OUString& rPassword,
+        const std::vector<unsigned char>& rSaltValue,
+        sal_uInt32 nSpinCount,
+        HashType eType)
+{
+    const unsigned char* pPassBytes = reinterpret_cast<const unsigned char*>(rPassword.getStr());
+    const size_t nPassBytesLen = rPassword.getLength() * 2;
+    return calculateHash( pPassBytes, nPassBytesLen, rSaltValue.data(), rSaltValue.size(), nSpinCount, eType);
+}
+
+OUString Hash::calculateHash(
+        const rtl::OUString& rPassword,
+        const rtl::OUString& rSaltValue,
+        sal_uInt32 nSpinCount,
+        const rtl::OUString& rAlgorithmName)
+{
+    HashType eType;
+    if (rAlgorithmName == "SHA-512")
+        eType = HashType::SHA512;
+    else if (rAlgorithmName == "SHA-256")
+        eType = HashType::SHA256;
+    else if (rAlgorithmName == "SHA-1")
+        eType = HashType::SHA1;
+    else if (rAlgorithmName == "MD5")
+        eType = HashType::MD5;
+    else
+        return OUString();
+
+    css::uno::Sequence<sal_Int8> aSaltSeq;
+    comphelper::Base64::decode( aSaltSeq, rSaltValue);
+
+    std::vector<unsigned char> hash = calculateHash( rPassword,
+            comphelper::sequenceToContainer<std::vector<unsigned char>>( aSaltSeq),
+            nSpinCount, eType);
+
+    OUStringBuffer aBuf;
+    comphelper::Base64::encode( aBuf, comphelper::containerToSequence<sal_Int8>( hash));
+    return aBuf.makeStringAndClear();
+}
+
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/comphelper/hash.hxx b/include/comphelper/hash.hxx
index df70757f4042..07998ad02736 100644
--- a/include/comphelper/hash.hxx
+++ b/include/comphelper/hash.hxx
@@ -15,6 +15,10 @@
 #include <memory>
 #include <vector>
 
+namespace rtl {
+    class OUString;
+}
+
 namespace comphelper {
 
 enum class HashType
@@ -43,6 +47,65 @@ public:
 
     static std::vector<unsigned char> calculateHash(const unsigned char* pInput, size_t length, HashType eType);
 
+    /** Calculate hash value with salt (pSalt,nSaltLen) prepended to password
+        (pInput,nLength) and repeated iterations run if nSpinCount>0.
+
+        For repeated iterations, each iteration's result plus a 4 byte value
+        (0-based, little endian) containing the number of the iteration
+        appended to the hash value is the input for the next iteration.
+
+        This implements the algorithm as specified in
+        https://msdn.microsoft.com/en-us/library/dd920692
+
+        @param  pSalt
+                may be nullptr thus no salt prepended
+
+        @return the raw hash value
+     */
+    static std::vector<unsigned char> calculateHash(
+            const unsigned char* pInput, size_t nLength,
+            const unsigned char* pSalt, size_t nSaltLen,
+            sal_uInt32 nSpinCount,
+            HashType eType);
+
+    /** Convenience function to calculate a salted hash with iterations.
+
+        @param  rPassword
+                UTF-16LE encoded string without leading BOM character
+
+        @param  rSaltValue
+                Salt that will be prepended to password data.
+     */
+    static std::vector<unsigned char> calculateHash(
+            const rtl::OUString& rPassword,
+            const std::vector<unsigned char>& rSaltValue,
+            sal_uInt32 nSpinCount,
+            HashType eType);
+
+    /** Convenience function to calculate a salted hash with iterations.
+
+        @param  rPassword
+                UTF-16LE encoded string without leading BOM character
+
+        @param  rSaltValue
+                Base64 encoded salt that will be decoded and prepended to password
+                data.
+
+        @param  rAlgorithmName
+                One of "SHA-512", "SHA-256", ... as listed in
+                https://msdn.microsoft.com/en-us/library/dd920692
+                that have a valid match in HashType. If not, an empty string is
+                returned. Not all algorithm names are supported.
+
+        @return the base64 encoded string of the hash value, that can be
+                compared against a stored base64 encoded hash value.
+     */
+    static rtl::OUString calculateHash(
+            const rtl::OUString& rPassword,
+            const rtl::OUString& rSaltValue,
+            sal_uInt32 nSpinCount,
+            const rtl::OUString& rAlgorithmName);
+
     size_t getLength() const;
 };
 


More information about the Libreoffice-commits mailing list