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;
}
}
}
}