https://issues.apache.org/bugzilla/show_bug.cgi?id=56076

Stefan Kopf <[email protected]> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEEDINFO                    |NEW

--- Comment #4 from Stefan Kopf <[email protected]> ---
Andreas,

The algorithm is different from the one you referenced here :-(.

It is defined in "Office Open XML Part 4 - Markup Language Reference" chapter
2.15.1.28 on page 1158.

At first, the password is hashed with the legacy algortithm used in .doc files.
I have provided an implementation of this algorithm in bug 56077. I can tell
that this algorithm is correct by comparing my results to the example given in
the spec.

Next, the result of this is hashed by the hash algorithm (SHA-1 in the example
above) with the salt prependen. The output of this is used as input  for the
next round of hashing (without a round key prepended). This is repeated for
spin-count rounds.

But the result of this does not match the hash calculated by MS Office.

Here is the implementation I used to test it:

package com.alfresco.sparta.research.office.changetracking;

import java.security.MessageDigest;

import org.apache.commons.codec.binary.Base64;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;

public class TestSha1
{

    public static void main(String[] args) throws Exception
    {

        String password = "Example";
        byte[] salt = Base64.decodeBase64("2Z+i7o/0EZyUNakVeWzU/w==");
        byte[] expectedHash =
Base64.decodeBase64("MUHbcmpC9AnlLsd9v3lW0j30y6E=");
        int rounds = 100000;

        System.out.println("Salt: "  +  hexBytes(salt));
        System.out.println("ExpectedHash: "  +  hexBytes(expectedHash));


        int wordHash = wordPasswordHash(password);
        System.out.print("WordHash: 0x");
        System.out.format("%H",wordHash);
        System.out.println();

        byte[] reversedWordHash = new byte[4];
        reversedWordHash[0] = (byte) (wordHash & 0x000000FF);
        reversedWordHash[1] = (byte) ( (wordHash & 0x0000FF00) >> 8);
        reversedWordHash[2] = (byte) ( (wordHash & 0x00FF0000) >> 16);
        reversedWordHash[3] = (byte) ( (wordHash & 0xFF000000) >> 24);
        System.out.println("ReversedWordHash: "  + 
hexBytes(reversedWordHash));

        MessageDigest md = MessageDigest.getInstance("SHA-1");

        md.update(salt);
        byte[] hash = md.digest(reversedWordHash);

        byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
        for(int i = 0; i < rounds; i++)
        {
            LittleEndian.putInt(iterator, 0, i);
            md.reset();
            //md.update(iterator);
            md.update(hash);
            md.digest(hash, 0, hash.length);
        }
        System.out.println("GeneratedHash: "  +  hexBytes(hash));
    }

    static String hexBytes(byte[] b)
    {
        StringBuilder result = new StringBuilder("0x");
        for(int i = 0; i < b.length;  i++)
        {            
            String s = String.format("%02X", b[i]);
            result.append(s);
        }
        return result.toString();
    }


    private static char[] initialValue = { 0xE1F0, 0x1D0F, 0xCC9C, 0x84C0,
0x110C, 0x0E10, 0xF1CE, 0x313E,
            0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3 };

    private static char[][] encryptionMatrix =
      { { 0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09 },
        { 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF },
        { 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0 },
        { 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40 },
        { 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5 },
        { 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A },
        { 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9 },
        { 0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0 },
        { 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC },
        { 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10 },
        { 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168 },
        { 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C },
        { 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD },
        { 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC },
        { 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4 } };

    public static int wordPasswordHash(String password)
    {
        if( (password == null) || (password.length() == 0) )
        {
            return 0x00000000;
        }
        // prepare password bytes from unicode string
        if(password.length() > 15)
        {
            password = password.substring(0, 15);
        }
        byte[] passwordBytes = new byte[password.length()];
        for(int i = 0; i < password.length(); i++)
        {
            char c = password.charAt(i);
            byte lowByte = (byte)(c & 0x00FF);
            if(lowByte != 0)
            {
                passwordBytes[i] = lowByte;
            }
            else
            {
                byte highByte = (byte)((c & 0xFF00) >> 8);
                passwordBytes[i] = highByte;
            }
        }
        // Compute the high-order word
        char highOrderWord = initialValue[passwordBytes.length-1];
        for(int pos = 0; pos < passwordBytes.length; pos++)
        {
            int matrixRow = 14 - (passwordBytes.length - 1 - pos);
            char[] encryptionVector = encryptionMatrix[matrixRow];
            for(int bitPos = 0; bitPos  < 7; bitPos++)
            {
                if((passwordBytes[pos] & (1 << bitPos)) != 0)
                {
                    highOrderWord = (char)(highOrderWord ^
encryptionVector[bitPos]);
                }
            }
        }
        // compute low-order word
        char lowOrderWord = 0;
        for(int pos = passwordBytes.length-1; pos >= 0; pos--)
        {
            lowOrderWord = (char) ((((lowOrderWord >> 14) & 0x0001) |
((lowOrderWord << 1) & 0x7FFF)) ^ passwordBytes[pos]);
        }
        lowOrderWord = (char) ((((lowOrderWord >> 14) & 0x0001) |
((lowOrderWord << 1) & 0x7FFF)) ^ passwordBytes.length ^ 0xCE4B);
        return (highOrderWord << 16) | lowOrderWord;
    }

}

-- 
You are receiving this mail because:
You are the assignee for the bug.

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to