From d09436c9b39b2fa36687ddc03aff9b5b2795a5b5 Mon Sep 17 00:00:00 2001 From: Michael Grafnetter Date: Thu, 3 Jan 2019 11:52:50 +0100 Subject: [PATCH] Fixed pekList padding --- .../Cryptography/DirectorySecretDecryptor.cs | 10 +++++ .../DataStoreSecretDecryptorTester.cs | 26 +++++++++++- .../Cryptography/DataStoreSecretDecryptor.cs | 41 ++++++++++++------- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/Src/DSInternals.Common/Cryptography/DirectorySecretDecryptor.cs b/Src/DSInternals.Common/Cryptography/DirectorySecretDecryptor.cs index 053efe4..7daea30 100644 --- a/Src/DSInternals.Common/Cryptography/DirectorySecretDecryptor.cs +++ b/Src/DSInternals.Common/Cryptography/DirectorySecretDecryptor.cs @@ -188,5 +188,15 @@ namespace DSInternals.Common.Cryptography return md5.Hash; } } + + protected static byte[] GenerateSalt(int size) + { + byte[] salt = new byte[size]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + return salt; + } + } } } diff --git a/Src/DSInternals.DataStore.Test/DataStoreSecretDecryptorTester.cs b/Src/DSInternals.DataStore.Test/DataStoreSecretDecryptorTester.cs index 424440c..402ca46 100644 --- a/Src/DSInternals.DataStore.Test/DataStoreSecretDecryptorTester.cs +++ b/Src/DSInternals.DataStore.Test/DataStoreSecretDecryptorTester.cs @@ -80,6 +80,30 @@ namespace DSInternals.DataStore.Test Assert.AreEqual(pek.CurrentKey.ToHex(), pek2.CurrentKey.ToHex()); } + [TestMethod] + public void PasswordEncryptionKey_DataStorePEK_W2019() + { + // Win 2019 RTM (Format is the same as WS 2016) + byte[] encryptedPEK = "030000000100000065DB55C82F7AB29C7FF2CC3518C0DC00433C80629D23D64420D9264BB2FE54288C3121B396CD4DC9BF094EDCBF559DAD3545C52399B883BD0F374EEAF3FA35C71C75DD1447FD0A59C81C60F6703F9B7000000000000000000000000000000000".HexToBinary(); + byte[] bootKey = "f51aa1df3bb0175efbd6842bffba81c9".HexToBinary(); + byte[] bootKey2 = "c965a6c04ac771ae10932f25efd8d85c".HexToBinary(); + + // Decrypt + var pek = new DataStoreSecretDecryptor(encryptedPEK, bootKey); + + // Re-encrypt with a different boot key + byte[] encryptedPEK2 = pek.ToByteArray(bootKey2); + + // Decrypt again with the new boot key + var pek2 = new DataStoreSecretDecryptor(encryptedPEK2, bootKey2); + + // And re-encrypt with the original BootKey + byte[] encryptedPEK3 = pek2.ToByteArray(bootKey); + + // Check if the newly encrypted PEK has the same length as the original one + Assert.AreEqual(encryptedPEK.Length, encryptedPEK3.Length); + } + [TestMethod] public void PasswordEncryptionKey_DataStoreDecryptPEK_LDS() { @@ -233,4 +257,4 @@ namespace DSInternals.DataStore.Test Assert.AreEqual(4096, cred.KerberosNew.DefaultIterationCount); } } -} \ No newline at end of file +} diff --git a/Src/DSInternals.DataStore/Cryptography/DataStoreSecretDecryptor.cs b/Src/DSInternals.DataStore/Cryptography/DataStoreSecretDecryptor.cs index 9eb003c..4db6723 100644 --- a/Src/DSInternals.DataStore/Cryptography/DataStoreSecretDecryptor.cs +++ b/Src/DSInternals.DataStore/Cryptography/DataStoreSecretDecryptor.cs @@ -11,6 +11,7 @@ namespace DSInternals.DataStore { private const int BootKeySaltHashRounds = 1000; private const int EncryptedPekListOffset = sizeof(PekListVersion) + sizeof(PekListFlags) + SaltSize; + private const int AESBlockSize = 16; // The flags field is probably not used by AD and is always 0. private const ushort EncryptedSecretFlags = 0; @@ -85,8 +86,8 @@ namespace DSInternals.DataStore // Blob structure Win2k: Algorithm ID (2B), Flags (2B), PEK ID (4B), Salt (16B), Encrypted secret (rest) const int EncryptedDataOffsetDES = 2 * sizeof(short) + sizeof(uint) + SaltSize; // Blob structure Win2016: Algorithm ID (2B), Flags (2B), PEK ID (4B), Salt (16B), Secret Length (4B), Encrypted secret (rest) - const int EncryptedDataOffsetAES = 2 * sizeof(short) + SaltSize + sizeof(ulong); - + // const int EncryptedDataOffsetAES = 2 * sizeof(short) + SaltSize + sizeof(ulong); + // Validate (DES has shorter blob) Validator.AssertMinLength(blob, EncryptedDataOffsetDES + 1, "blob"); @@ -116,6 +117,7 @@ namespace DSInternals.DataStore encryptedSecret = stream.ReadToEnd(); } } + // Decrypt byte[] decryptedSecret; switch (encryptionType) @@ -151,13 +153,9 @@ namespace DSInternals.DataStore // Write PEK ID(4B) writer.Write(this.CurrentKeyIndex); - + // Generate and Write Salt(16B) - byte[] salt = new byte[SaltSize]; - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(salt); - } + byte[] salt = GenerateSalt(SaltSize); writer.Write(salt); // Perform the encryption @@ -243,11 +241,7 @@ namespace DSInternals.DataStore } // Generate random salt - byte[] salt = new byte[SaltSize]; - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(salt); - } + byte[] salt = GenerateSalt(SaltSize); // Encode the data structure using (MemoryStream stream = new MemoryStream()) @@ -270,7 +264,24 @@ namespace DSInternals.DataStore switch (pekListVersion) { case PekListVersion.W2016: - encryptedBlob = EncryptUsingAES(cleartextBlob, salt, bootKey, PaddingMode.Zeros); + // We need to add some additional salt and padding to the data: + byte[] salt2 = GenerateSalt(SaltSize); + int cleartextWithSalt2Size = cleartextBlob.Length + SaltSize; + int paddingSize = (AESBlockSize - cleartextWithSalt2Size % AESBlockSize) % AESBlockSize; + byte[] paddedBlob = new byte[cleartextWithSalt2Size+paddingSize]; + cleartextBlob.CopyTo(paddedBlob, 0); + + // Fill the blob with padding bytes (similar to PKCS#7 padding mode) + for (int i = cleartextBlob.Length; i < cleartextBlob.Length + paddingSize; i++) + { + paddedBlob[i] = (byte)paddingSize; + } + + // Add the salt to the end: + salt2.CopyTo(paddedBlob, cleartextBlob.Length + paddingSize); + + // We can finally do the encryption + encryptedBlob = EncryptUsingAES(paddedBlob, salt, bootKey, PaddingMode.Zeros); break; case PekListVersion.W2k: encryptedBlob = EncryptUsingRC4(cleartextBlob, salt, bootKey, BootKeySaltHashRounds); @@ -377,4 +388,4 @@ namespace DSInternals.DataStore return encryptedPekList; } } -} \ No newline at end of file +}