using System.Buffers.Binary; using System.Security.Cryptography; namespace WxAgent.Core; public static class SqlCipherPageVerifier { public const int PageSize = 4096; public const int SaltSize = 16; public const int KeySize = 32; public const int HmacSize = 64; public static bool Verify(ReadOnlySpan page, ReadOnlySpan key, uint pageNumber = 1) { if (page.Length != PageSize || key.Length != KeySize || pageNumber == 0) { return false; } Span hmacSalt = stackalloc byte[SaltSize]; for (var index = 0; index < SaltSize; index++) { hmacSalt[index] = (byte)(page[index] ^ 0x3a); } Span hmacKey = stackalloc byte[KeySize]; Rfc2898DeriveBytes.Pbkdf2(key, hmacSalt, hmacKey, 2, HashAlgorithmName.SHA512); var authenticatedLength = PageSize - SaltSize - HmacSize; var authenticated = new byte[authenticatedLength + sizeof(uint)]; page.Slice(SaltSize, authenticatedLength).CopyTo(authenticated); BinaryPrimitives.WriteUInt32LittleEndian(authenticated.AsSpan(authenticatedLength), pageNumber); var computed = HMACSHA512.HashData(hmacKey, authenticated); var valid = CryptographicOperations.FixedTimeEquals(computed, page[^HmacSize..]); CryptographicOperations.ZeroMemory(hmacKey); CryptographicOperations.ZeroMemory(authenticated); CryptographicOperations.ZeroMemory(computed); return valid; } public static bool VerifyHexKey(ReadOnlySpan page, string hexKey, uint pageNumber = 1) { if (hexKey.Length != KeySize * 2) { return false; } byte[] key; try { key = Convert.FromHexString(hexKey); } catch (FormatException) { return false; } try { return Verify(page, key, pageNumber); } finally { CryptographicOperations.ZeroMemory(key); } } }