initial commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,341 @@
|
||||
#if NETFRAMEWORK
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Pulsar.Common.Cryptography
|
||||
{
|
||||
internal static class AesGcmCng
|
||||
{
|
||||
private const uint ERROR_SUCCESS = 0x00000000;
|
||||
private const uint STATUS_AUTH_TAG_MISMATCH = 0xC000A002;
|
||||
|
||||
private const string BCRYPT_AES_ALGORITHM = "AES";
|
||||
private const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider";
|
||||
private const string BCRYPT_CHAINING_MODE = "ChainingMode";
|
||||
private const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
|
||||
private const string BCRYPT_OBJECT_LENGTH = "ObjectLength";
|
||||
private const string BCRYPT_KEY_DATA_BLOB = "KeyDataBlob";
|
||||
|
||||
private static readonly byte[] KeyBlobMagic = BitConverter.GetBytes(0x4d42444b); // "KDBM"
|
||||
|
||||
internal static void Encrypt(byte[] key, byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
if (nonce == null) throw new ArgumentNullException(nameof(nonce));
|
||||
if (plaintext == null) throw new ArgumentNullException(nameof(plaintext));
|
||||
if (ciphertext == null) throw new ArgumentNullException(nameof(ciphertext));
|
||||
if (tag == null) throw new ArgumentNullException(nameof(tag));
|
||||
|
||||
if (ciphertext.Length != plaintext.Length)
|
||||
{
|
||||
throw new CryptographicException("Ciphertext buffer length must match plaintext length.");
|
||||
}
|
||||
|
||||
using (SafeAlgorithmHandle algorithm = OpenAlgorithm())
|
||||
using (SafeKeyHandle keyHandle = ImportKey(algorithm, key))
|
||||
{
|
||||
byte[] output = new byte[ciphertext.Length];
|
||||
byte[] tagBuffer = new byte[tag.Length];
|
||||
AuthInfo authInfo = new AuthInfo(nonce, null, tagBuffer);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] ivBuffer = (byte[])nonce.Clone();
|
||||
int result = 0;
|
||||
uint status = BCryptEncrypt(keyHandle.DangerousGetHandle(), plaintext, plaintext.Length, ref authInfo.Info, ivBuffer, ivBuffer.Length, output, output.Length, ref result, 0);
|
||||
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptEncrypt failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
if (result != ciphertext.Length)
|
||||
{
|
||||
throw new CryptographicException("Ciphertext length mismatch during encryption.");
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(output, 0, ciphertext, 0, result);
|
||||
authInfo.CopyTag(tag);
|
||||
}
|
||||
finally
|
||||
{
|
||||
authInfo.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Decrypt(byte[] key, byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
if (nonce == null) throw new ArgumentNullException(nameof(nonce));
|
||||
if (ciphertext == null) throw new ArgumentNullException(nameof(ciphertext));
|
||||
if (tag == null) throw new ArgumentNullException(nameof(tag));
|
||||
if (plaintext == null) throw new ArgumentNullException(nameof(plaintext));
|
||||
|
||||
if (ciphertext.Length != plaintext.Length)
|
||||
{
|
||||
throw new CryptographicException("Plaintext buffer length must match ciphertext length.");
|
||||
}
|
||||
|
||||
using (SafeAlgorithmHandle algorithm = OpenAlgorithm())
|
||||
using (SafeKeyHandle keyHandle = ImportKey(algorithm, key))
|
||||
{
|
||||
byte[] output = new byte[plaintext.Length];
|
||||
AuthInfo authInfo = new AuthInfo(nonce, null, tag);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] ivBuffer = (byte[])nonce.Clone();
|
||||
int result = 0;
|
||||
uint status = BCryptDecrypt(keyHandle.DangerousGetHandle(), ciphertext, ciphertext.Length, ref authInfo.Info, ivBuffer, ivBuffer.Length, output, output.Length, ref result, 0);
|
||||
|
||||
if (status == STATUS_AUTH_TAG_MISMATCH)
|
||||
{
|
||||
throw new CryptographicException("Authentication tag mismatch during decryption.");
|
||||
}
|
||||
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptDecrypt failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
if (result != plaintext.Length)
|
||||
{
|
||||
throw new CryptographicException("Plaintext length mismatch during decryption.");
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(output, 0, plaintext, 0, result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
authInfo.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SafeAlgorithmHandle OpenAlgorithm()
|
||||
{
|
||||
IntPtr rawHandle;
|
||||
uint status = BCryptOpenAlgorithmProvider(out rawHandle, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptOpenAlgorithmProvider failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
SafeAlgorithmHandle handle = new SafeAlgorithmHandle(rawHandle);
|
||||
try
|
||||
{
|
||||
byte[] chainMode = Encoding.Unicode.GetBytes(BCRYPT_CHAIN_MODE_GCM);
|
||||
status = BCryptSetAlgorithmProperty(handle.DangerousGetHandle(), BCRYPT_CHAINING_MODE, chainMode, chainMode.Length, 0);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptSetAlgorithmProperty failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
catch
|
||||
{
|
||||
handle.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static SafeKeyHandle ImportKey(SafeAlgorithmHandle algorithm, byte[] key)
|
||||
{
|
||||
byte[] objectLength = GetAlgorithmProperty(algorithm.DangerousGetHandle(), BCRYPT_OBJECT_LENGTH);
|
||||
int keyObjectSize = BitConverter.ToInt32(objectLength, 0);
|
||||
IntPtr keyObject = Marshal.AllocHGlobal(keyObjectSize);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] blob = BuildKeyBlob(key);
|
||||
IntPtr rawKey;
|
||||
uint status = BCryptImportKey(algorithm.DangerousGetHandle(), IntPtr.Zero, BCRYPT_KEY_DATA_BLOB, out rawKey, keyObject, keyObjectSize, blob, blob.Length, 0);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptImportKey failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
return new SafeKeyHandle(rawKey, keyObject);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Marshal.FreeHGlobal(keyObject);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] GetAlgorithmProperty(IntPtr handle, string property)
|
||||
{
|
||||
int size = 0;
|
||||
uint status = BCryptGetProperty(handle, property, null, 0, ref size, 0);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptGetProperty (query size) failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[size];
|
||||
status = BCryptGetProperty(handle, property, buffer, buffer.Length, ref size, 0);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
throw new CryptographicException(string.Format("BCryptGetProperty failed with status code 0x{0:X8}.", status));
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static byte[] BuildKeyBlob(byte[] key)
|
||||
{
|
||||
byte[] blob = new byte[KeyBlobMagic.Length + sizeof(int) + sizeof(int) + key.Length];
|
||||
Buffer.BlockCopy(KeyBlobMagic, 0, blob, 0, KeyBlobMagic.Length);
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(1), 0, blob, KeyBlobMagic.Length, sizeof(int));
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(key.Length), 0, blob, KeyBlobMagic.Length + sizeof(int), sizeof(int));
|
||||
Buffer.BlockCopy(key, 0, blob, KeyBlobMagic.Length + (sizeof(int) * 2), key.Length);
|
||||
return blob;
|
||||
}
|
||||
|
||||
private sealed class AuthInfo : IDisposable
|
||||
{
|
||||
internal BCryptAuthenticatedCipherModeInfo Info;
|
||||
private GCHandle _nonceHandle;
|
||||
private GCHandle _aadHandle;
|
||||
private GCHandle _tagHandle;
|
||||
private GCHandle _macHandle;
|
||||
private byte[] _tagBuffer;
|
||||
|
||||
internal AuthInfo(byte[] nonce, byte[] aad, byte[] tag)
|
||||
{
|
||||
Info = new BCryptAuthenticatedCipherModeInfo();
|
||||
Info.cbSize = Marshal.SizeOf(typeof(BCryptAuthenticatedCipherModeInfo));
|
||||
Info.dwInfoVersion = 1;
|
||||
|
||||
if (nonce != null && nonce.Length > 0)
|
||||
{
|
||||
byte[] nonceCopy = (byte[])nonce.Clone();
|
||||
_nonceHandle = GCHandle.Alloc(nonceCopy, GCHandleType.Pinned);
|
||||
Info.pbNonce = _nonceHandle.AddrOfPinnedObject();
|
||||
Info.cbNonce = nonceCopy.Length;
|
||||
}
|
||||
|
||||
if (aad != null && aad.Length > 0)
|
||||
{
|
||||
byte[] aadCopy = (byte[])aad.Clone();
|
||||
_aadHandle = GCHandle.Alloc(aadCopy, GCHandleType.Pinned);
|
||||
Info.pbAuthData = _aadHandle.AddrOfPinnedObject();
|
||||
Info.cbAuthData = aadCopy.Length;
|
||||
Info.cbAAD = aadCopy.Length;
|
||||
}
|
||||
|
||||
if (tag != null && tag.Length > 0)
|
||||
{
|
||||
_tagBuffer = (byte[])tag.Clone();
|
||||
_tagHandle = GCHandle.Alloc(_tagBuffer, GCHandleType.Pinned);
|
||||
Info.pbTag = _tagHandle.AddrOfPinnedObject();
|
||||
Info.cbTag = _tagBuffer.Length;
|
||||
|
||||
byte[] mac = new byte[_tagBuffer.Length];
|
||||
_macHandle = GCHandle.Alloc(mac, GCHandleType.Pinned);
|
||||
Info.pbMacContext = _macHandle.AddrOfPinnedObject();
|
||||
Info.cbMacContext = mac.Length;
|
||||
}
|
||||
}
|
||||
|
||||
internal void CopyTag(byte[] destination)
|
||||
{
|
||||
if (_tagBuffer != null && destination != null)
|
||||
{
|
||||
Buffer.BlockCopy(_tagBuffer, 0, destination, 0, Math.Min(_tagBuffer.Length, destination.Length));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_macHandle.IsAllocated) _macHandle.Free();
|
||||
if (_tagHandle.IsAllocated) _tagHandle.Free();
|
||||
if (_aadHandle.IsAllocated) _aadHandle.Free();
|
||||
if (_nonceHandle.IsAllocated) _nonceHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct BCryptAuthenticatedCipherModeInfo
|
||||
{
|
||||
internal int cbSize;
|
||||
internal int dwInfoVersion;
|
||||
internal IntPtr pbNonce;
|
||||
internal int cbNonce;
|
||||
internal IntPtr pbAuthData;
|
||||
internal int cbAuthData;
|
||||
internal IntPtr pbTag;
|
||||
internal int cbTag;
|
||||
internal IntPtr pbMacContext;
|
||||
internal int cbMacContext;
|
||||
internal int cbAAD;
|
||||
internal long cbData;
|
||||
internal int dwFlags;
|
||||
}
|
||||
|
||||
private sealed class SafeAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
internal SafeAlgorithmHandle(IntPtr handle) : base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return BCryptCloseAlgorithmProvider(handle, 0) == ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SafeKeyHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private readonly IntPtr _keyObject;
|
||||
|
||||
internal SafeKeyHandle(IntPtr handle, IntPtr keyObject) : base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
_keyObject = keyObject;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (_keyObject != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(_keyObject);
|
||||
}
|
||||
|
||||
return BCryptDestroyKey(handle) == ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("bcrypt.dll")]
|
||||
private static extern uint BCryptOpenAlgorithmProvider(out IntPtr phAlgorithm, [MarshalAs(UnmanagedType.LPWStr)] string pszAlgId, [MarshalAs(UnmanagedType.LPWStr)] string pszImplementation, uint dwFlags);
|
||||
|
||||
[DllImport("bcrypt.dll")]
|
||||
private static extern uint BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, uint flags);
|
||||
|
||||
[DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")]
|
||||
private static extern uint BCryptGetProperty(IntPtr hObject, [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, byte[] pbOutput, int cbOutput, ref int pcbResult, uint flags);
|
||||
|
||||
[DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")]
|
||||
private static extern uint BCryptSetAlgorithmProperty(IntPtr hObject, [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, byte[] pbInput, int cbInput, int dwFlags);
|
||||
|
||||
[DllImport("bcrypt.dll")]
|
||||
private static extern uint BCryptImportKey(IntPtr hAlgorithm, IntPtr hImportKey, [MarshalAs(UnmanagedType.LPWStr)] string pszBlobType, out IntPtr phKey, IntPtr pbKeyObject, int cbKeyObject, byte[] pbInput, int cbInput, uint dwFlags);
|
||||
|
||||
[DllImport("bcrypt.dll")]
|
||||
private static extern uint BCryptDestroyKey(IntPtr hKey);
|
||||
|
||||
[DllImport("bcrypt.dll")]
|
||||
private static extern uint BCryptEncrypt(IntPtr hKey, byte[] pbInput, int cbInput, ref BCryptAuthenticatedCipherModeInfo pPaddingInfo, byte[] pbIV, int cbIV, byte[] pbOutput, int cbOutput, ref int pcbResult, uint dwFlags);
|
||||
|
||||
[DllImport("bcrypt.dll")]
|
||||
private static extern uint BCryptDecrypt(IntPtr hKey, byte[] pbInput, int cbInput, ref BCryptAuthenticatedCipherModeInfo pPaddingInfo, byte[] pbIV, int cbIV, byte[] pbOutput, int cbOutput, ref int pcbResult, uint dwFlags);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Pulsar.Common.Cryptography
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides byte rotation obfuscation methods for simple data protection.
|
||||
/// </summary>
|
||||
public static class ByteRotationObfuscator
|
||||
{
|
||||
/// <summary>
|
||||
/// The rotation amount used for obfuscation.
|
||||
/// </summary>
|
||||
private const int ROTATION_AMOUNT = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Obfuscates data by rotating each byte by a fixed amount with overflow wrapping.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to obfuscate.</param>
|
||||
/// <returns>The obfuscated data.</returns>
|
||||
public static byte[] Obfuscate(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
Debug.WriteLine("Failed to Obfuscate. Data is null.");
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
byte[] result = new byte[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
result[i] = RotateByte(data[i], ROTATION_AMOUNT);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deobfuscates data by rotating each byte back by the fixed amount with overflow wrapping.
|
||||
/// </summary>
|
||||
/// <param name="data">The obfuscated data to deobfuscate.</param>
|
||||
/// <returns>The original data.</returns>
|
||||
public static byte[] Deobfuscate(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
Debug.WriteLine("Failed to Deobfuscate. Data is null.");
|
||||
return data;
|
||||
}
|
||||
|
||||
byte[] result = new byte[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
result[i] = RotateByte(data[i], -ROTATION_AMOUNT);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a byte by the specified amount with overflow wrapping.
|
||||
/// </summary>
|
||||
/// <param name="value">The byte to rotate.</param>
|
||||
/// <param name="amount">The rotation amount (can be positive or negative).</param>
|
||||
/// <returns>The rotated byte.</returns>
|
||||
private static byte RotateByte(byte value, int amount)
|
||||
{
|
||||
amount = ((amount % 256) + 256) % 256;
|
||||
|
||||
int result = (value + amount) % 256;
|
||||
return (byte)result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the rotated value for a given byte and rotation amount.
|
||||
/// This is a helper method for testing and verification.
|
||||
/// </summary>
|
||||
/// <param name="value">The byte value to rotate.</param>
|
||||
/// <param name="amount">The rotation amount.</param>
|
||||
/// <returns>The rotated byte value.</returns>
|
||||
public static byte CalculateRotation(byte value, int amount)
|
||||
{
|
||||
return RotateByte(value, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Pulsar.Common.Cryptography
|
||||
{
|
||||
public class SafeComparison
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares two byte arrays for equality.
|
||||
/// </summary>
|
||||
/// <param name="a1">Byte array to compare</param>
|
||||
/// <param name="a2">Byte array to compare</param>
|
||||
/// <returns>True if equal, else false</returns>
|
||||
/// <remarks>
|
||||
/// Assumes that the byte arrays have the same length.
|
||||
/// This method is safe against timing attacks.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static bool AreEqual(byte[] a1, byte[] a2)
|
||||
{
|
||||
bool result = true;
|
||||
for (int i = 0; i < a1.Length; ++i)
|
||||
{
|
||||
if (a1[i] != a2[i])
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Pulsar.Common.Cryptography
|
||||
{
|
||||
public static class Sha256
|
||||
{
|
||||
public static string ComputeHash(string input)
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
using (SHA256Managed sha = new SHA256Managed())
|
||||
{
|
||||
data = sha.ComputeHash(data);
|
||||
}
|
||||
|
||||
StringBuilder hash = new StringBuilder();
|
||||
|
||||
foreach (byte _byte in data)
|
||||
hash.Append(_byte.ToString("X2"));
|
||||
|
||||
return hash.ToString().ToUpper();
|
||||
}
|
||||
|
||||
public static byte[] ComputeHash(byte[] input)
|
||||
{
|
||||
using (SHA256Managed sha = new SHA256Managed())
|
||||
{
|
||||
return sha.ComputeHash(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user