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 true 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. * null if it is currently unSet. */ public static String CurrentUserPassword { get { return _userPasswordTLS; } set { _userPasswordTLS = value; } } } }