mirror of
https://github.com/MichaelGrafnetter/DSInternals
synced 2025-02-19 20:46:51 +00:00
Improvements in secret encryption
This commit is contained in:
parent
cb59397d68
commit
34cc68aae0
@ -76,6 +76,27 @@ namespace DSInternals.Common.Cryptography
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] EncryptHashHistory(byte[][] hashHistory, int rid)
|
||||
{
|
||||
Validator.AssertNotNull(hashHistory, "hashHistory");
|
||||
|
||||
int expectedBufferLength = hashHistory.Length * NTHash.HashSize;
|
||||
|
||||
using (var buffer = new MemoryStream(expectedBufferLength))
|
||||
{
|
||||
// Encryption layer 1: Encrypt the individual hashes
|
||||
foreach(byte[] hash in hashHistory)
|
||||
{
|
||||
byte[] encryptedHash = EncryptUsingDES(hash, rid);
|
||||
buffer.Write(encryptedHash, 0, encryptedHash.Length);
|
||||
}
|
||||
|
||||
// Encryption layer 2: Encrypt the entire hash history
|
||||
byte[] encryptedHashHistory = this.EncryptSecret(buffer.ToArray());
|
||||
return encryptedHashHistory;
|
||||
}
|
||||
}
|
||||
|
||||
protected static byte[] DecryptUsingDES(byte[] encryptedHash, int rid)
|
||||
{
|
||||
byte[] decryptedHash;
|
||||
@ -104,12 +125,12 @@ namespace DSInternals.Common.Cryptography
|
||||
return DecryptUsingRC4(data, salt, encryptionKey, saltHashRounds);
|
||||
}
|
||||
|
||||
protected static byte[] DecryptUsingAES(byte[] data, byte[] iv, byte[] key)
|
||||
protected static byte[] DecryptUsingAES(byte[] data, byte[] iv, byte[] key, PaddingMode padding)
|
||||
{
|
||||
using(var aes = AesManaged.Create())
|
||||
using(var aes = Aes.Create())
|
||||
{
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.Zeros;
|
||||
aes.Padding = padding;
|
||||
using(var decryptor = aes.CreateDecryptor(key, iv))
|
||||
{
|
||||
using (var inputStream = new MemoryStream(data, false))
|
||||
@ -127,12 +148,12 @@ namespace DSInternals.Common.Cryptography
|
||||
}
|
||||
}
|
||||
|
||||
protected static byte[] EncryptUsingAES(byte[] data, byte[] iv, byte[] key)
|
||||
protected static byte[] EncryptUsingAES(byte[] data, byte[] iv, byte[] key, PaddingMode padding)
|
||||
{
|
||||
using (var aes = AesManaged.Create())
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.Zeros;
|
||||
aes.Padding = padding;
|
||||
using (var encryptor = aes.CreateEncryptor(key, iv))
|
||||
{
|
||||
using (var inputStream = new MemoryStream(data, false))
|
||||
@ -153,7 +174,7 @@ namespace DSInternals.Common.Cryptography
|
||||
protected static byte[] ComputeMD5(byte[] key, byte[] salt, int saltHashRounds = DefaultSaltHashRounds)
|
||||
{
|
||||
// TODO: Test that saltHashRounds >= 1
|
||||
using (var md5 = new MD5CryptoServiceProvider())
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
// Hash key
|
||||
md5.TransformBlock(key, 0, key.Length, null, 0);
|
||||
|
@ -18,6 +18,32 @@ namespace DSInternals.DataStore.Test
|
||||
string expected = "04b7b3fd6df689af9d6837e840abdc8c";
|
||||
Assert.AreEqual(expected, pek.CurrentKey.ToHex());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreEncryptPEK_W2k()
|
||||
{
|
||||
// Win 2000 - Win 2012 R2
|
||||
byte[] encryptedPEK = "020000000100000042b1f49dbb723edff3b865a4d28e3afbf215961695225991e991d429a02ad382bd89214319f61e7eb4620e89b42ddba3d0de84c0603d6e34ae2fccf79eb9374a9a08d3b1".HexToBinary();
|
||||
byte[] bootKey = "41e34661faa0d182182f6ddf0f0ca0d1".HexToBinary();
|
||||
byte[] bootKey2 = "abcdef0123456789abcdef0123456780".HexToBinary();
|
||||
|
||||
// Decrypt
|
||||
var pek = new DataStoreSecretDecryptor(encryptedPEK, bootKey);
|
||||
|
||||
// Re-encrypt with a different boot key
|
||||
byte[] encryptedPEK2 = pek.ToByteArray(bootKey2);
|
||||
|
||||
// And decrypt again with the new boot key
|
||||
var pek2 = new DataStoreSecretDecryptor(encryptedPEK2, bootKey2);
|
||||
|
||||
// Check if the new PEK looks like the original one
|
||||
Assert.AreEqual(pek.Version, pek2.Version);
|
||||
Assert.AreEqual(pek.LastGenerated, pek2.LastGenerated);
|
||||
Assert.AreEqual(pek.EncryptionType, pek2.EncryptionType);
|
||||
Assert.AreEqual(pek.CurrentKeyIndex, pek2.CurrentKeyIndex);
|
||||
Assert.AreEqual(pek.CurrentKey.ToHex(), pek2.CurrentKey.ToHex());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreDecryptPEK_W2016()
|
||||
{
|
||||
@ -29,6 +55,31 @@ namespace DSInternals.DataStore.Test
|
||||
Assert.AreEqual(expected, pek.CurrentKey.ToHex(true));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreEncryptPEK_W2016()
|
||||
{
|
||||
// Win 2016 TP4+
|
||||
byte[] encryptedPEK = "03000000010000008ACED06423573C329BECD77936128FD61FD3892FAC724D4D24B2F4A5DA48A72B5472BDCB7FB6EEFA4884CDC9B2D2A835931A3E67B434DC766051A28B73DE385285B19961E0DC0CF661BA0AC3B3DD185D00000000000000000000000000000000".HexToBinary();
|
||||
byte[] bootKey = "c0f2efe014aeda56da739a22ae9e9893".HexToBinary();
|
||||
byte[] bootKey2 = "abcdef0123456789abcdef0123456780".HexToBinary();
|
||||
|
||||
// Decrypt
|
||||
var pek = new DataStoreSecretDecryptor(encryptedPEK, bootKey);
|
||||
|
||||
// Re-encrypt with a different boot key
|
||||
byte[] encryptedPEK2 = pek.ToByteArray(bootKey2);
|
||||
|
||||
// And decrypt again with the new boot key
|
||||
var pek2 = new DataStoreSecretDecryptor(encryptedPEK2, bootKey2);
|
||||
|
||||
// Check if the new PEK looks like the original one
|
||||
Assert.AreEqual(pek.Version, pek2.Version);
|
||||
Assert.AreEqual(pek.LastGenerated, pek2.LastGenerated);
|
||||
Assert.AreEqual(pek.EncryptionType, pek2.EncryptionType);
|
||||
Assert.AreEqual(pek.CurrentKeyIndex, pek2.CurrentKeyIndex);
|
||||
Assert.AreEqual(pek.CurrentKey.ToHex(), pek2.CurrentKey.ToHex());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreDecryptPEK_LDS()
|
||||
{
|
||||
@ -103,7 +154,7 @@ namespace DSInternals.DataStore.Test
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreNTHash_W2016_Encrypt()
|
||||
{
|
||||
// Win 2000 - Win 2012 R2
|
||||
// Win 2016
|
||||
byte[] originalHash = "92937945B518814341DE3F726500D4FF".HexToBinary();
|
||||
byte[] binaryPek = "56d98148ec91d111905a00c04fc2d4cfd02cd74ef843d1010000000001000000000000006a35d3fc0e9949135463ab766cac7dbb0c0c0c0c0c0c0c0c0c0c0c0ca93445b678ce5fbe02de23c3c71ff800".HexToBinary();
|
||||
int rid = 500;
|
||||
@ -118,7 +169,7 @@ namespace DSInternals.DataStore.Test
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreNTHashHistory_W2k()
|
||||
public void PasswordEncryptionKey_DataStoreNTHashHistory_W2k_Decrypt()
|
||||
{
|
||||
byte[] binaryPek = "56d98148ec91d111905a00c04fc2d4cfb0b0f777efcece0100000000010000000000000004b7b3fd6df689af9d6837e840abdc8c".HexToBinary();
|
||||
var pek = new DataStoreSecretDecryptor(binaryPek, PekListVersion.W2k);
|
||||
@ -130,7 +181,31 @@ namespace DSInternals.DataStore.Test
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreNTHashHistory_W2016()
|
||||
public void PasswordEncryptionKey_DataStoreNTHashHistory_W2k_Encrypt()
|
||||
{
|
||||
// Prepare the input data
|
||||
byte[] hash1 = "92937945B518814341DE3F726500D4FF".HexToBinary();
|
||||
byte[] hash2 = "31D6CFE0D16AE931B73C59D7E0C089C0".HexToBinary();
|
||||
byte[][] hashHistory = new byte[][] { hash1, hash2 };
|
||||
|
||||
int rid = 500;
|
||||
byte[] binaryPek = "56d98148ec91d111905a00c04fc2d4cfb0b0f777efcece0100000000010000000000000004b7b3fd6df689af9d6837e840abdc8c".HexToBinary();
|
||||
var pek = new DataStoreSecretDecryptor(binaryPek, PekListVersion.W2k);
|
||||
|
||||
// Encrypt and then decrypt the hash history
|
||||
byte[] encryptedHashHistory = pek.EncryptHashHistory(hashHistory, rid);
|
||||
byte[][] decryptedHashHistory = pek.DecryptHashHistory(encryptedHashHistory, rid);
|
||||
|
||||
// Compare the result with the original data
|
||||
Assert.AreEqual(hashHistory.Length, decryptedHashHistory.Length);
|
||||
for(int i = 0; i < hashHistory.Length; i++)
|
||||
{
|
||||
CollectionAssert.AreEqual(hashHistory[i], decryptedHashHistory[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreNTHashHistory_W2016_Decrypt()
|
||||
{
|
||||
byte[] binaryPek = "56d98148ec91d111905a00c04fc2d4cfd02cd74ef843d1010000000001000000000000006a35d3fc0e9949135463ab766cac7dbb0c0c0c0c0c0c0c0c0c0c0c0ca93445b678ce5fbe02de23c3c71ff800".HexToBinary();
|
||||
var pek = new DataStoreSecretDecryptor(binaryPek, PekListVersion.W2016);
|
||||
@ -142,7 +217,7 @@ namespace DSInternals.DataStore.Test
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PasswordEncryptionKey_DataStoreSupplementalCredentials_Decrypt_W2016()
|
||||
public void PasswordEncryptionKey_DataStoreSupplementalCredentials_W2016_Decrypt()
|
||||
{
|
||||
byte[] binaryPek = "56d98148ec91d111905a00c04fc2d4cfd02cd74ef843d1010000000001000000000000006a35d3fc0e9949135463ab766cac7dbb0c0c0c0c0c0c0c0c0c0c0c0ca93445b678ce5fbe02de23c3c71ff800".HexToBinary();
|
||||
var pek = new DataStoreSecretDecryptor(binaryPek, PekListVersion.W2016);
|
||||
@ -158,4 +233,4 @@ namespace DSInternals.DataStore.Test
|
||||
Assert.AreEqual(4096, cred.KerberosNew.DefaultIterationCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -124,8 +124,8 @@ namespace DSInternals.DataStore
|
||||
decryptedSecret = DecryptUsingRC4(encryptedSecret, salt, decryptionKey);
|
||||
break;
|
||||
case SecretEncryptionType.DatabaseAES:
|
||||
byte[] decryptedSecretWithPadding = DecryptUsingAES(encryptedSecret, salt, decryptionKey);
|
||||
decryptedSecret = decryptedSecretWithPadding.Cut(0, secretLength);
|
||||
decryptedSecret = DecryptUsingAES(encryptedSecret, salt, decryptionKey, PaddingMode.PKCS7);
|
||||
// TODO: Check that decryptedSecret.Lenght == secretLength;
|
||||
break;
|
||||
default:
|
||||
// TODO: Extract as resource
|
||||
@ -154,7 +154,7 @@ namespace DSInternals.DataStore
|
||||
|
||||
// Generate and Write Salt(16B)
|
||||
byte[] salt = new byte[SaltSize];
|
||||
using (var rng = RNGCryptoServiceProvider.Create())
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
@ -166,7 +166,7 @@ namespace DSInternals.DataStore
|
||||
switch (this.EncryptionType)
|
||||
{
|
||||
case SecretEncryptionType.DatabaseAES:
|
||||
encryptedSecret = EncryptUsingAES(secret, salt, this.CurrentKey);
|
||||
encryptedSecret = EncryptUsingAES(secret, salt, this.CurrentKey, PaddingMode.PKCS7);
|
||||
// Write the Secret Length (4B)
|
||||
writer.Write(secret.Length);
|
||||
break;
|
||||
@ -230,10 +230,11 @@ namespace DSInternals.DataStore
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] EncryptPekList(byte[] cleartextBlob, byte[] bootKey = null)
|
||||
private static byte[] EncryptPekList(byte[] cleartextBlob, PekListVersion pekListVersion, byte[] bootKey = null)
|
||||
{
|
||||
// Do not encrypt by default.
|
||||
PekListFlags flags = PekListFlags.Clear;
|
||||
|
||||
if (bootKey != null)
|
||||
{
|
||||
// Encrypt if the boot key is provided.
|
||||
@ -241,24 +242,23 @@ namespace DSInternals.DataStore
|
||||
flags = PekListFlags.Encrypted;
|
||||
}
|
||||
|
||||
int bufferSize = EncryptedPekListOffset + cleartextBlob.Length;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
// Generate random salt
|
||||
byte[] salt = new byte[SaltSize];
|
||||
using (var rng = new RNGCryptoServiceProvider())
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
using (MemoryStream stream = new MemoryStream(buffer))
|
||||
|
||||
// Encode the data structure
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
// Header
|
||||
// TODO: Write version corresponding to the DB or original version!!!
|
||||
writer.Write((uint)PekListVersion.W2k);
|
||||
writer.Write((uint)pekListVersion);
|
||||
writer.Write((uint)flags);
|
||||
writer.Write(salt);
|
||||
|
||||
// Data
|
||||
switch(flags)
|
||||
{
|
||||
@ -266,14 +266,26 @@ namespace DSInternals.DataStore
|
||||
writer.Write(cleartextBlob);
|
||||
break;
|
||||
case PekListFlags.Encrypted:
|
||||
byte[] encryptedBlob = EncryptUsingRC4(cleartextBlob, salt, bootKey, BootKeySaltHashRounds);
|
||||
byte[] encryptedBlob;
|
||||
switch (pekListVersion)
|
||||
{
|
||||
case PekListVersion.W2016:
|
||||
encryptedBlob = EncryptUsingAES(cleartextBlob, salt, bootKey, PaddingMode.Zeros);
|
||||
break;
|
||||
case PekListVersion.W2k:
|
||||
encryptedBlob = EncryptUsingRC4(cleartextBlob, salt, bootKey, BootKeySaltHashRounds);
|
||||
break;
|
||||
default:
|
||||
// TODO: Extract as a resource.
|
||||
throw new FormatException("Unsupported PEK list version.");
|
||||
}
|
||||
writer.Write(encryptedBlob);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] DecryptPekList(byte[] encryptedBlob, byte[] bootKey)
|
||||
@ -309,7 +321,7 @@ namespace DSInternals.DataStore
|
||||
decryptedPekList = DecryptUsingRC4(encryptedPekList, salt, bootKey, BootKeySaltHashRounds);
|
||||
break;
|
||||
case PekListVersion.W2016:
|
||||
decryptedPekList = DecryptUsingAES(encryptedPekList, salt, bootKey);
|
||||
decryptedPekList = DecryptUsingAES(encryptedPekList, salt, bootKey, PaddingMode.Zeros);
|
||||
break;
|
||||
default:
|
||||
// TODO: Extract as resource.
|
||||
@ -328,6 +340,11 @@ namespace DSInternals.DataStore
|
||||
return decryptedPekList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a binary version of the password encryption key list.
|
||||
/// </summary>
|
||||
/// <param name="bootKey">Optional boot key.</param>
|
||||
/// <returns>Binary PEK list, optionally encrypted.</returns>
|
||||
public byte[] ToByteArray(byte[] bootKey = null)
|
||||
{
|
||||
if (this.Keys.Length == 0)
|
||||
@ -355,8 +372,9 @@ namespace DSInternals.DataStore
|
||||
}
|
||||
}
|
||||
}
|
||||
byte[] encryptedPekList = EncryptPekList(buffer, bootKey);
|
||||
|
||||
byte[] encryptedPekList = EncryptPekList(buffer, this.Version, bootKey);
|
||||
return encryptedPekList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -419,20 +419,6 @@ namespace DSInternals.DataStore
|
||||
return dataTableCursor.GotoKey(Key.Compose(parentDNTag));
|
||||
}
|
||||
|
||||
public static bool FindChildren(this Cursor dataTableCursor, DirectorySchema schema)
|
||||
{
|
||||
// TODO: Check if we are really dealing with the datatable.
|
||||
|
||||
|
||||
|
||||
// Read parent DN Tag of the current record
|
||||
int parentDNTag = dataTableCursor.RetrieveColumnAsDNTag(schema.FindColumnId(CommonDirectoryAttributes.ParentDNTag)).Value;
|
||||
// Set the index to PDNT column
|
||||
dataTableCursor.CurrentIndex = schema.FindIndexName(CommonDirectoryAttributes.ParentDNTag);
|
||||
// Position the cursor to the only matching record
|
||||
dataTableCursor.FindRecords(MatchCriteria.EqualTo, Key.Compose(parentDNTag));
|
||||
}
|
||||
|
||||
public static bool MoveToFirst(this Cursor cursor)
|
||||
{
|
||||
cursor.MoveBeforeFirst();
|
||||
|
Loading…
Reference in New Issue
Block a user