Improvements in secret encryption

This commit is contained in:
Michael Grafnetter 2018-07-17 23:43:56 +02:00
parent cb59397d68
commit 34cc68aae0
4 changed files with 144 additions and 44 deletions

View File

@ -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);

View File

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

View File

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

View File

@ -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();