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]
