Word文档保护,如何计算密码哈希? [英] Word document protection, how to calculate the password hash?

查看:308
本文介绍了Word文档保护,如何计算密码哈希?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述




我正在尝试保护Word文档的C#代码,以防止对我创建的文档进行无意的更改。



我没有在Open XML Format SDK 1.0和2.0中找不到"Protect()"方法(我想避免Interop.Word),所以我自己实现了哈希码计算,如下所述:



Office Open XML第4部分 - 标记语言参考.pdf


2.15.1.28 documentProtection(文档编辑限制)



如t中给出的示例他的规格,我可以从: password ="Example"to to derivedKey ="7EEDCE64"。


然后我用SHA-1哈希。


< p class = MsoNormal style ="margin:0cm 0cm 0pt 30.05pt"align = left>



但是当我尝试暂停文档保护时Word不会验证密码。我的代码一定有错,但我找不到它。



环顾四周,我还没有



有人能给我一个详细的例子(pseodocode,C#代码)吗? / font>


或告诉我我的错误?


还是指向一些API或SDK来处理支持文档保护的Open XML格式?



提前致谢。



C#; VSNet2008; .Net 3.5; Open XML Format SDK 2.0; Word 2007




私人 static byte [] concatByteArrays( byte [] array1, byte [] array2)


{


byte [] result = new 字节 [array1.Length + array2.Length];



Buffer .BlockCopy(array1,0,result,0,array1.Length);


缓冲区 .BlockCopy(array2, 0,result,array1.Length,array2.Length);



返回 result;


}



private static byte [] generateRandomSalt()


{


byte [] salt = new byte [16];


RandomNumberGenerator rand = new RNGCryptoServiceProvider ();


rand.GetNonZeroBytes(salt);


返回 salt;


}



私人 static byte [] calculateKey( string 密码)


< p class = MsoNormal style ="margin:0cm 0cm 0pt"> {


字节 [] key = new byte [4];



//如果密码为空,则返回0.


if (! String .IsNullOrEmpty(password))


{


//将密码截断为15个字符。


password = password.Substring(0,数学 .Min(password.L ength,maxPasswordLength));



//构造一个由单字节字符组成的新的以NULL结尾的字符串:


//通过迭代截断密码的Unicode字符来获取单字节值。


//对于每个字符,如果低字节不等于0,请接受它。否则,取高字节。


byte [] bytes = new byte [password.Length];


for int i = 0; i< password.Length; i ++)


{


int v = Conve rt .ToInt32(密码[i]);


bytes [i] = 转换 .ToByte(v& 0x00FF);


如果(bytes [i] == 0)


bytes [i] = 转换 .ToByte((v& 0xFF00)>> 8);


}



< span style ="color:green"> //计算n的高位字新密钥:


// - 从初始代码数组(见下文)初始化,具体取决于密码的长度。对于每个


> //密码中的字符:


// - 对于角色中的每一位,从最低有效位开始,进展到(但


//排除最重要的,如果设置了该位,则用


// c来自加密矩阵的相应单词


int high = InitialCodeArray [bytes.Length - 1];


for int i = 0;我< bytes.Length; i ++)


{


int c = maxPasswordLength - bytes.Length + i;


for int b = 0; b <7; b ++)


{


if ((bytes [i]&(0x0001<< b))!= 0)


{


high ^ = EncryptionMatrix [c,b];


}


}


}



//计算新密钥的低位字:


// - 初始化为0


// - 对于每个角色密码,倒退,


// 低位字=(((低-order word SHR 14)AND 0x0001 )OR(低位字SHL 1)和0x7FFF))异或字符


// - 最后,


// 低位字=(((低位字SHR 14)和0x0001)或(低位字SHL 1)和0x7FFF))XOR密码长度XOR 0xCE4B。


int low = 0;


for int i = bytes.Length - 1; i> = 0;我 - )


{


low =(((low>>> 14)& 0x0001)|((low << 1)& ; 0x7FFF))^ bytes [i];


}


低=(((低>>>>>  0x0001)|((低< ;< 1)& 0x7FFF))^ bytes.Length ^ 0xCE4B;



int key2 =(高<< 16)+ low;



//结果的字节顺序应反转[例如:0x64CEED7E变为7EEDCE64。结束示例],


//该值应按属性值定义进行哈希处理。


for int i = 0; i< 4; i ++)


{


key [i] = 转换 .ToByte((( uint )(key2&(0x000000FF<<(i * 8) )))>>(i * 8));


}


}



// TODO:可能不是byte [] {0x7E,0xED,0xCE,0x64}但是"7EEDCE64"。


StringBuilder sb = new StringBuilder ();


int i = 0;我< 4; i ++)


{


sb.Append(转换 .ToString(key [i],16 ));


}


key = 编码 .Unicode.GetBytes(sb.ToString() .ToUpper());



返回键;


}



private static byte [] keyDerivationFunction( string 密码, byte [] salt, int 次迭代)


{


byte [] key = calculateKey(password) ;


< span style =""> key = concatByteArrays(salt,key);



//迭代次数指定散列函数迭代次数run(使用每个


//迭代的结果作为下一次迭代的输入。)


HashAlgorithm sha1 = new SHA1Managed ();


for int i = 0;我<迭代; i ++)


{


key = sha1.ComputeHash(key);


}



返回键;


}



public static void 保护( WordprocessingDocument wd, DocumentProtectionValues protectionType, string 密码)


{


int iterations = 50000;


byte [] salt = generateRandomSalt();


byte [] hash = keyDerivationFunction(密码,盐,迭代);



DocumentProtection documentProtection = new DocumentProtection ();


documentProtection.Edit = d ocumentProtectionValues .ReadOnly;


documentProtection.Enforcement = BooleanValues .One;


documentProtection .CryptographicAlgorithmClass = CryptAlgorithmClassValues .Hash;


< span style ="font-size:10pt; font-family:'Courier New'"> documentProtection.CryptographicProviderType = CryptProviderValues .RsaFull;


    documentProtection.CryptographicAlgorithmType = CryptAlgorithmValues.TypeAny;


    documentProtection.CryptographicAlgorithmSid = 4; // SHA1


    documentProtection.CryptographicSpinCount = iterations;


    documentProtection.Hash = Convert.ToBase64String(hash);


    documentProtection.Salt = Convert.ToBase64String(salt);


 


    wd.MainDocumentPart.DocumentSettingsPart.Settings.AppendChild(documentProtection);


    wd.MainDocumentPart.DocumentSettingsPart.Settings.Save();


}


 

解决方案


Hi,


 


Reading though the specs, I have seen some tiny mistakes.


 


Office Open XML Part 4 - Markup Language Reference.pdf


2.15.1.28 documentProtection (Document Editing Restrictions)


 


[Example: Consider a WordprocessingML document with the following information stored in one of its protection elements:


 


<w:… w:cry ptAlgorithmClass="hash"


  w:cryptAlgorithmType="typeAny"


  w:cryptAlgorithmSid="1"


  w:hash="9oN7nWkCAyEZib1RomSJTjmPpCY=" />


 


The cryptAlgorithmSid attribute value of 1 specifies that the SHA-1 hashing algorithm shall be used to generate a hash from the user-defined password. end example]


 


While just above it states:


 
























Value


Algorithm


1


MD2


2


MD4


3


MD5


4


SHA-1


Etc.


 


 


I have also seen there were mistakes on the Hashing password for use in SpreadsheetML.


 


Therefore I am wondering if there couldn’t be errors in the algorithm described in section 2.15.1.28 documentProtection (Document Editing Restrictions) too.


 



Th ings that aren’t crystal clear in the specs about Word password hashing:


·         "Construct a new NULL-terminated string consisting of single-byte characters:" NULL-terminated strings are a C or C++ representation of a character string, but not in C#. Should the ending NULL be taken into account? As far as I can tell, no, it shouldn’t.


·         "If the password is empty, return 0." and later "Second, the byte order of the result shall be reversed [Example: 0x64CEED7E becomes 7EEDCE64. end example], and that value shall be hashed as defined by the attribute values." The returned data type is unclear. Is it an integer or a character string representation of the hexadecimal value? It must be converted into a byte array before computing the hash, but with which encoding? Unicode (UTF-16), single-byte chars (UTF-8 or ASCII)? Big-endian, Little-endian?


·         "salt (Salt for Password Verifier) Specifies the salt which was prepended to the user-supplied password before it was hashed using the hashing algorithm defined by the preceding attribute values to generate the hash attribute […] A salt is a random string which is added to a user-supplied password before it is hashed […]" In general, salt is a random generated not-null byte array, not a string. How many chars or bytes in length should it be? Is the salt prepended or apended to the user-supplied password or to the derived key returned by the first step of the algorithm? If the salt is longer than 15 chars and it is prepended to the user-supplied password, the derived key will truncate it down to 15 chars, removing the tail where the user-supplied password lies, making it useless.


·         "If this attribute is omitted, then no salt shall be prepended to the user-supplied password before it is hashed for comparison with the stored hash value." If the salt attribute is omitted, Word simply doesn’t ask for the password when suspending the protection, as if hash attribute was omitted too. Reading the specs I would have said that the password hash should have been calculated without prepending any salt, making it a weaker hashing, but not an inoperative one.


 


By the way, document protection is not a security feature,


Funny: How the new powerful cryptography implemented in Word 2007 turns it into a perfect tool for document password removal.


 


Is there any workaround to this issue converting document files from Word 2007 into Word 2003?


 


Regards,


 


 

Hi,

 

I am trying protect a Word document form C# code, to prevent unintentional changes on a document I create.

 

I haven’t found a "Protect()" method in the Open XML Format SDK 1.0 nor 2.0 (I want to avoid Interop.Word), so I have implemented the hash code calculation myself, as described in:

 

Office Open XML Part 4 - Markup Language Reference.pdf

2.15.1.28 documentProtection (Document Editing Restrictions)

 

As the example given in the specs, I can go from: password="Example" to derivedKey="7EEDCE64".

Then I hash it with SHA-1.

 

 

But Word doesn’t validate the password when I try to suspend the document’s protection. There must be a mistake in my code, but I can’t find it.

 

Looking around on the Internet, I haven’t  found any code example.

 

Can someone give me a detailed example (pseodocode, C# code)?

Or tell me what’s wrong in mine?

Or point me to some API or SDK to handle Open XML Format that enables document protection?

 

Thanks in advance.

 

C#; VSNet2008; .Net 3.5; Open XML Format SDK 2.0; Word 2007

 

 

private static byte[] concatByteArrays(byte[] array1, byte[] array2)

{

    byte[] result = new byte[array1.Length + array2.Length];

   

    Buffer.BlockCopy(array1, 0, result, 0, array1.Length);

    Buffer.BlockCopy(array2, 0, result, array1.Length, array2.Length);

 

    return result;

}

 

private static byte[] generateRandomSalt()

{

    byte[] salt = new byte[16];

    RandomNumberGenerator rand = new RNGCryptoServiceProvider();

    rand.GetNonZeroBytes(salt);

    return salt;

}

 

private static byte[] calculateKey(string password)

{

    byte[] key = new byte[4];

 

    // If the password is empty, return 0.

    if (!String.IsNullOrEmpty(password))

    {

        // Truncate the password to 15 characters.

        password = password.Substring(0, Math.Min(password.Length, maxPasswordLength));

 

        // Construct a new NULL-terminated string consisting of single-byte characters:

        // Get the single-byte values by iterating through the Unicode characters of the truncated password.

        // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte.

        byte[] bytes = new byte[password.Length];

        for (int i = 0; i < password.Length; i++)

        {

            int v = Convert.ToInt32(password[i ]);

            bytes[i ] = Convert.ToByte(v & 0x00FF);

            if (bytes[i ] == 0)

                bytes[i ] = Convert.ToByte((v & 0xFF00) >> 8);

        }

 

        // Compute the high-order word of the new key:

        // - Initialize from the initial code array (see below), depending on the password’s length. For each

        // character in the password:

        // - For every bit in the character, starting with the least significant and progressing to (but

        // excluding) the most significant, if the bit is set, XOR the key’s high-order word with the

        // corresponding word from the encryption matrix

        int high = InitialCodeArray[bytes.Length - 1];

        for (int i = 0; i < bytes.Length; i++)

        {

            int c = maxPasswordLength - bytes.Length + i;

            for (int b = 0; b < 7; b++)

            {

                if ((bytes[i ] & (0x0001 << b)) != 0)

                {

                    high ^= EncryptionMatrix[c, b];

                }

            }

        }

 

        // Compute the low-order word of the new key:

        // - Initialize with 0

        // - For each character in the password, going backwards,

        //      low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character

        // - Lastly,

        //      low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR password length XOR 0xCE4B.

        int low = 0;

        for (int i = bytes.Length - 1; i >= 0; i--)

        {

            low = (((low >> 14) & 0x0001) | ((low << 1) & 0x7FFF)) ^ bytes[i ];

        }

        low = (((low >> 14) & 0x0001) | ((low << 1) & 0x7FFF)) ^ bytes.Length ^ 0xCE4B;

 

        int key2 = (high << 16) + low;

 

        // The byte order of the result shall be reversed [Example: 0x64CEED7E becomes 7EEDCE64. end example],

        // and that value shall be hashed as defined by the attribute values.

        for (int i = 0; i < 4; i++)

        {

            key[i ] = Convert.ToByte(((uint)(key2 & (0x000000FF << (i * 8)))) >> (i * 8));

        }

    }

 

    //TODO: May be not byte[]{0x7E, 0xED, 0xCE, 0x64} but "7EEDCE64".

    StringBuilder sb = new StringBuilder();

    for (int i = 0; i < 4; i++)

    {

        sb.Append(Convert.ToString(key[i ], 16));

    }

    key = Encoding.Unicode.GetBytes(sb.ToString().ToUpper());

 

    return key;

}

 

private static byte[] keyDerivationFunction(string password, byte[] salt, int iterations)

{

    byte[] key = calculateKey(password);

    key = concatByteArrays(salt, key);

 

    // Iterations specifies the number of times the hashing function shall be iteratively run (using each

    // iteration's result as the input for the next iteration).

    HashAlgorithm sha1 = new SHA1Managed();

    for (int i = 0; i < iterations; i++)

    {

        key = sha1.ComputeHash(key);

    }

 

    return key;

}

 

public static void Protect(WordprocessingDocument wd, DocumentProtectionValues protectionType, string password)

{

    int iterations = 50000;

    byte[] salt = generateRandomSalt();

    byte[] hash = keyDerivationFunction(password, salt, iterations);

 

    DocumentProtection documentProtection = new DocumentProtection();

    documentProtection.Edit = DocumentProtectionValues.ReadOnly;

    documentProtection.Enforcement = BooleanValues.One;

    documentProtection.CryptographicAlgorithmClass = CryptAlgorithmClassValues.Hash;

    documentProtection.CryptographicProviderType = CryptProviderValues.RsaFull;

    documentProtection.CryptographicAlgorithmType = CryptAlgorithmValues.TypeAny;

    documentProtection.CryptographicAlgorithmSid = 4; // SHA1

    documentProtection.CryptographicSpinCount = iterations;

    documentProtection.Hash = Convert.ToBase64String(hash);

    documentProtection.Salt = Convert.ToBase64String(salt);

 

    wd.MainDocumentPart.DocumentSettingsPart.Settings.AppendChild(documentProtection);

    wd.MainDocumentPart.DocumentSettingsPart.Settings.Save();

}

 

解决方案

Hi,

 

Reading though the specs, I have seen some tiny mistakes.

 

Office Open XML Part 4 - Markup Language Reference.pdf

2.15.1.28 documentProtection (Document Editing Restrictions)

 

[Example: Consider a WordprocessingML document with the following information stored in one of its protection elements:

 

<w:… w:cryptAlgorithmClass="hash"

  w:cryptAlgorithmType="typeAny"

  w:cryptAlgorithmSid="1"

  w:hash="9oN7nWkCAyEZib1RomSJTjmPpCY=" />

 

The cryptAlgorithmSid attribute value of 1 specifies that the SHA-1 hashing algorithm shall be used to generate a hash from the user-defined password. end example]

 

While just above it states:

 

Value

Algorithm

1

MD2

2

MD4

3

MD5

4

SHA-1

Etc.

 

 

I have also seen there were mistakes on the Hashing password for use in SpreadsheetML.

 

Therefore I am wondering if there couldn’t be errors in the algorithm described in section 2.15.1.28 documentProtection (Document Editing Restrictions) too.

 

Things that aren’t crystal clear in the specs about Word password hashing:

·         "Construct a new NULL-terminated string consisting of single-byte characters:" NULL-terminated strings are a C or C++ representation of a character string, but not in C#. Should the ending NULL be taken into account? As far as I can tell, no, it shouldn’t.

·         "If the password is empty, return 0." and later "Second, the byte order of the result shall be reversed [Example: 0x64CEED7E becomes 7EEDCE64. end example], and that value shall be hashed as defined by the attribute values." The returned data type is unclear. Is it an integer or a character string representation of the hexadecimal value? It must be converted into a byte array before computing the hash, but with which encoding? Unicode (UTF-16), single-byte chars (UTF-8 or ASCII)? Big-endian, Little-endian?

·         "salt (Salt for Password Verifier) Specifies the salt which was prepended to the user-supplied password before it was hashed using the hashing algorithm defined by the preceding attribute values to generate the hash attribute […] A salt is a random string which is added to a user-supplied password before it is hashed […]" In general, salt is a random generated not-null byte array, not a string. How many chars or bytes in length should it be? Is the salt prepended or apended to the user-supplied password or to the derived key returned by the first step of the algorithm? If the salt is longer than 15 chars and it is prepended to the user-supplied password, the derived key will truncate it down to 15 chars, removing the tail where the user-supplied password lies, making it useless.

·         "If this attribute is omitted, then no salt shall be prepended to the user-supplied password before it is hashed for comparison with the stored hash value." If the salt attribute is omitted, Word simply doesn’t ask for the password when suspending the protection, as if hash attribute was omitted too. Reading the specs I would have said that the password hash should have been calculated without prepending any salt, making it a weaker hashing, but not an inoperative one.

 

By the way, document protection is not a security feature,

Funny: How the new powerful cryptography implemented in Word 2007 turns it into a perfect tool for document password removal.

 

Is there any workaround to this issue converting document files from Word 2007 into Word 2003?

 

Regards,

 


这篇关于Word文档保护,如何计算密码哈希?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆