zhao
2021-06-04 c7ec496f9e41c2227103b3ef776e4a3f91bce6b2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
namespace HH.WMS.Utils.NPOI.HSSF.Record.Crypto
{
    using System;
    using System.IO;
    using System.Collections.Generic;
 
    using HH.WMS.Utils.NPOI.Util;
 
    
    using System.Security.Cryptography;
 
    public class Biff8EncryptionKey
    {
        // these two constants coincidentally have the same value
        private static int KEY_DIGEST_LENGTH = 5;
        private static int PASSWORD_HASH_NUMBER_OF_BYTES_USED = 5;
 
        private byte[] _keyDigest;
 
        /**
         * Create using the default password and a specified docId
         * @param docId 16 bytes
         */
        public static Biff8EncryptionKey Create(byte[] docId)
        {
            return new Biff8EncryptionKey(CreateKeyDigest("VelvetSweatshop", docId));
        }
        public static Biff8EncryptionKey Create(String password, byte[] docIdData)
        {
            return new Biff8EncryptionKey(CreateKeyDigest(password, docIdData));
        }
 
        internal Biff8EncryptionKey(byte[] keyDigest)
        {
            if (keyDigest.Length != KEY_DIGEST_LENGTH)
            {
                throw new ArgumentException("Expected 5 byte key digest, but got " + HexDump.ToHex(keyDigest));
            }
            _keyDigest = keyDigest;
        }
 
        internal static byte[] CreateKeyDigest(String password, byte[] docIdData)
        {
            Check16Bytes(docIdData, "docId");
            int nChars = Math.Min(password.Length, 16);
            byte[] passwordData = new byte[nChars * 2];
            for (int i = 0; i < nChars; i++)
            {
                char ch = password[i];
                passwordData[i * 2 + 0] = (byte)((ch << 0) & 0xFF);
                passwordData[i * 2 + 1] = (byte)((ch << 8) & 0xFF);
            }
 
            byte[] kd;
            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                byte[] passwordHash = md5.ComputeHash(passwordData);
 
                md5.Initialize();
 
                byte[] data = new byte[PASSWORD_HASH_NUMBER_OF_BYTES_USED * 16 + docIdData.Length * 16];
 
                int offset = 0;
                for (int i = 0; i < 16; i++)
                {
                    Array.Copy(passwordHash, 0, data, offset, PASSWORD_HASH_NUMBER_OF_BYTES_USED);
                    offset += PASSWORD_HASH_NUMBER_OF_BYTES_USED;// passwordHash.Length;
                    Array.Copy(docIdData, 0, data, offset, docIdData.Length);
                    offset += docIdData.Length;
                }
                kd = md5.ComputeHash(data);
                byte[] result = new byte[KEY_DIGEST_LENGTH];
                Array.Copy(kd, 0, result, 0, KEY_DIGEST_LENGTH);
                md5.Clear();
 
                return result;
            }
        }
 
        /**
         * @return <c>true</c> if the keyDigest is compatible with the specified saltData and saltHash
         */
        public bool Validate(byte[] saltData, byte[] saltHash)
        {
            Check16Bytes(saltData, "saltData");
            Check16Bytes(saltHash, "saltHash");
 
            // validation uses the RC4 for block zero
            RC4 rc4 = CreateRC4(0);
            byte[] saltDataPrime = new byte[saltData.Length];
            Array.Copy(saltData, saltDataPrime, saltData.Length);
            rc4.Encrypt(saltDataPrime);
 
            byte[] saltHashPrime = new byte[saltHash.Length];
            Array.Copy(saltHash, saltHashPrime, saltHash.Length);
            rc4.Encrypt(saltHashPrime);
 
            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                byte[] finalSaltResult = md5.ComputeHash(saltDataPrime);
 
                //if (false)
                //{ // set true to see a valid saltHash value
                //    byte[] saltHashThatWouldWork = xor(saltHash, xor(saltHashPrime, finalSaltResult));
                //    Console.WriteLine(HexDump.ToHex(saltHashThatWouldWork));
                //}
 
                return Arrays.Equals(saltHashPrime, finalSaltResult);
            }
        }
 
        private static byte[] xor(byte[] a, byte[] b)
        {
            byte[] c = new byte[a.Length];
            for (int i = 0; i < c.Length; i++)
            {
                c[i] = (byte)(a[i] ^ b[i]);
            }
            return c;
        }
        private static void Check16Bytes(byte[] data, String argName)
        {
            if (data.Length != 16)
            {
                throw new ArgumentException("Expected 16 byte " + argName + ", but got " + HexDump.ToHex(data));
            }
        }
 
        //private static ConcatBytes()
 
        /**
         * The {@link RC4} instance needs to be Changed every 1024 bytes.
         * @param keyBlockNo used to seed the newly Created {@link RC4}
         */
        internal RC4 CreateRC4(int keyBlockNo)
        {
            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                using (MemoryStream baos = new MemoryStream(4))
                {
                    new LittleEndianOutputStream(baos).WriteInt(keyBlockNo);
                    byte[] baosToArray = baos.ToArray();
                    byte[] data = new byte[baosToArray.Length + _keyDigest.Length];
                    Array.Copy(_keyDigest, 0, data, 0, _keyDigest.Length);
                    Array.Copy(baosToArray, 0, data, _keyDigest.Length, baosToArray.Length);
 
                    byte[] digest = md5.ComputeHash(data);
                    return new RC4(digest);
                }
            }
        }
 
 
        /**
         * Stores the BIFF8 encryption/decryption password for the current thread.  This has been done
         * using a {@link ThreadLocal} in order to avoid further overloading the various public APIs
         * (e.g. {@link HSSFWorkbook}) that need this functionality.
         */
        [ThreadStatic]
        private static String _userPasswordTLS = null;
 
        /**
         * @return the BIFF8 encryption/decryption password for the current thread.
         * <code>null</code> if it is currently unSet.
         */
        public static String CurrentUserPassword
        {
            get
            {
                return _userPasswordTLS;
            }
            set 
            {
                _userPasswordTLS = value;
            }
        }
    }
 
}