Files
SPHERE/SPHERE/Configure/Encryption.cs
2025-03-16 21:38:09 -05:00

1293 lines
53 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Security.Cryptography;
using SPHERE.Blockchain;
using SPHERE.Security;
using SPHERE.Configure.Logging;
using System.Security;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Serialization;
using System.Security.Principal;
using System.Text.Json;
using System.Security.AccessControl;
using static SPHERE.Security.KeyGenerator;
using System.Xml.Linq;
namespace SPHERE.Configure
{
/// <summary>
/// Encryption is vital for the success of the system.
/// We use need to encrypt the contact with the Local Symmetric Key(LSK).
/// We then need to encrypt the LSK with the Semi Public Key(SPK). This is added to the block.
///
/// Only if provided with the SPK can a node or user decrypt the LSK and then the contact in the block.
///
/// We also encrypt message to other nodes with their public Node Signature key
/// We Decrypt message sent to the node that was encrypted with its public Node Signature Key.
///
/// Contacts can also be used to encrypt a message with the Personal Key (PublicCommunicationKey) that allows only the contact to read it.
///
/// The application used by a user can access messages sent them and decrypt it with their Private Communication key.
///
///
/// </summary>
// Encryption/Decryption helper classes
public static class Encryption
{
// The Local Symmetric Key is used to Encrypt the blockContact.
public static byte[] EncryptContactWithSymmetric(Contact contactData, byte[] key)
{
byte[] convertedKey = key;
string data = JsonSerializer.Serialize(contactData);
using var aes = Aes.Create();
aes.Key = convertedKey;
aes.GenerateIV();
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using var ms = new MemoryStream();
ms.Write(aes.IV, 0, aes.IV.Length); // Prepend IV to the encrypted data
using (var cryptoStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
using (var writer = new StreamWriter(cryptoStream))
{
writer.Write(data);
}
return ms.ToArray(); // Return Base64-encoded encrypted data
}
public static Contact DecryptContactWithSymmetricKey(string data, byte[] key)
{
byte[] encryptedData = Convert.FromBase64String(data);
using var aes = Aes.Create();
// Extract IV from the encrypted data
byte[] iv = new byte[16];
Array.Copy(encryptedData, 0, iv, 0, iv.Length);
aes.IV = iv;
aes.Key = key;
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using var ms = new MemoryStream(encryptedData, iv.Length, encryptedData.Length - iv.Length);
using var cryptoStream = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var reader = new StreamReader(cryptoStream);
string decryptedData = reader.ReadToEnd();
// Deserialize the contact from the data.
Contact contact = JsonSerializer.Deserialize<Contact>(decryptedData);
if (contact != null)
{
return contact;
}
else
{
return null;
}
}
//Encrypt with a personal(public) Key Provides a secretKey to Validate its use.
public static byte[] EncryptPacketWithPublicKey(Node node, byte[] data, byte[] recipientPublicKey)
{
try
{
PrivateKeyManager keyManager = node.KeyManager;
bool isTesting = Environment.GetEnvironmentVariable("SPHERE_TEST_MODE") == "true";
byte[] recipientSubjectPublicKey = recipientPublicKey;
if (isTesting)
{
byte[] sendersPrivateKey = keyManager.UseKeyInStorageContainer(node, KeyGenerator.KeyType.PrivateNodeEncryptionKey);
using var recipientKey = ECDiffieHellman.Create();
recipientKey.ImportSubjectPublicKeyInfo(recipientSubjectPublicKey, out _);
// Create sender key pair and import sender's private key
using var senderKeyPair = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
senderKeyPair.ImportPkcs8PrivateKey(sendersPrivateKey, out _);
// Derive shared secret using recipient's actual public key
byte[] sharedSecret = senderKeyPair.DeriveKeyMaterial(recipientKey.PublicKey);
// Hash shared secret to create AES key
using var sha256 = SHA256.Create();
byte[] aesKey = sha256.ComputeHash(sharedSecret);
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Key = aesKey;
aes.GenerateIV();
aes.Padding = PaddingMode.PKCS7;
byte[] iv = aes.IV;
using var encryptor = aes.CreateEncryptor();
byte[] encryptedData = encryptor.TransformFinalBlock(data, 0, data.Length);
// Construct final message: [IV] + [Encrypted Data]
byte[] result = new byte[iv.Length + encryptedData.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedData, 0, result, iv.Length, encryptedData.Length);
return result;
}
else
{
byte[] sendersPrivateKey = keyManager.UseKeyInStorageContainer(node, KeyGenerator.KeyType.PrivateNodeEncryptionKey);
using var recipientKey = ECDiffieHellman.Create();
recipientKey.ImportSubjectPublicKeyInfo(recipientSubjectPublicKey, out _);
// Create sender key pair and import sender's private key
using var senderKeyPair = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
senderKeyPair.ImportPkcs8PrivateKey(sendersPrivateKey, out _);
// Derive shared secret using recipient's actual public key
byte[] sharedSecret = senderKeyPair.DeriveKeyMaterial(recipientKey.PublicKey);
// Hash shared secret to create AES key
using var sha256 = SHA256.Create();
byte[] aesKey = sha256.ComputeHash(sharedSecret);
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Key = aesKey;
aes.GenerateIV();
aes.Padding = PaddingMode.PKCS7;
byte[] iv = aes.IV;
using var encryptor = aes.CreateEncryptor();
byte[] encryptedData = encryptor.TransformFinalBlock(data, 0, data.Length);
// Construct final message: [IV] + [Encrypted Data]
byte[] result = new byte[iv.Length + encryptedData.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedData, 0, result, iv.Length, encryptedData.Length);
return result;
}
}
catch (Exception ex)
{
SystemLogger.Log($"EncryptPacketWithPublicKey: Encryption error: {ex.Message}");
throw;
}
}
//Decrypt with the Private Key Stored in the CNG Container and the shared secret
public static byte[] DecryptPacketWithPrivateKey(Node node, byte[] encryptedData, byte[] sendersPublicKey)
{
try
{
PrivateKeyManager privateKeyManager = node.KeyManager;
bool isTesting = Environment.GetEnvironmentVariable("SPHERE_TEST_MODE") == "true";
byte[] senderSubjectPublicKey = sendersPublicKey;
if (isTesting)
{
byte[] recipientPrivateKey = privateKeyManager.UseKeyInStorageContainer(node, KeyGenerator.KeyType.PrivateNodeEncryptionKey);
using var recipientKeyPair = ECDiffieHellman.Create();
recipientKeyPair.ImportPkcs8PrivateKey(recipientPrivateKey, out _);
// Ensure the sender's public key is in SubjectPublicKeyInfo format
using var senderKeyPair = ECDiffieHellman.Create();
senderKeyPair.ImportSubjectPublicKeyInfo(senderSubjectPublicKey, out _);
// Derive the shared secret using recipients private key and senders public key
byte[] sharedSecret = recipientKeyPair.DeriveKeyMaterial(senderKeyPair.PublicKey);
// Hash the shared secret to create AES key
using var sha256 = SHA256.Create();
byte[] aesKey = sha256.ComputeHash(sharedSecret);
// Extract IV and Cipher text
byte[] iv = new byte[16];
byte[] ciphertext = new byte[encryptedData.Length - 16];
Buffer.BlockCopy(encryptedData, 0, iv, 0, 16);
Buffer.BlockCopy(encryptedData, 16, ciphertext, 0, ciphertext.Length);
using var aes = Aes.Create();
aes.Key = aesKey;
aes.IV = iv;
aes.Padding = PaddingMode.PKCS7;
using var decryptor = aes.CreateDecryptor();
return decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
}
else
{
byte[] recipientPrivateKey = privateKeyManager.UseKeyInStorageContainer(node, KeyGenerator.KeyType.PublicNodeEncryptionKey);
using var recipientKeyPair = ECDiffieHellman.Create();
recipientKeyPair.ImportPkcs8PrivateKey(recipientPrivateKey, out _);
// Ensure the sender's public key is in SubjectPublicKeyInfo format
using var senderKeyPair = ECDiffieHellman.Create();
senderKeyPair.ImportSubjectPublicKeyInfo(senderSubjectPublicKey, out _);
// Derive the shared secret using recipients private key and senders public key
byte[] sharedSecret = recipientKeyPair.DeriveKeyMaterial(senderKeyPair.PublicKey);
// Hash the shared secret to create AES key
using var sha256 = SHA256.Create();
byte[] aesKey = sha256.ComputeHash(sharedSecret);
// Extract IV and Cipher text
byte[] iv = new byte[16];
byte[] ciphertext = new byte[encryptedData.Length - 16];
Buffer.BlockCopy(encryptedData, 0, iv, 0, 16);
Buffer.BlockCopy(encryptedData, 16, ciphertext, 0, ciphertext.Length);
using var aes = Aes.Create();
aes.Key = aesKey;
aes.IV = iv;
aes.Padding = PaddingMode.PKCS7;
using var decryptor = aes.CreateDecryptor();
return decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
}
}
catch (Exception ex)
{
SystemLogger.Log($"DecryptPacketWithPrivateKey: Decryption error: {ex.Message}");
throw;
}
}
// Local Symmetric Keys(LSA) are used to Encrypt a contact. They are encrypted with a Semi Public Key(SPK) so only someone with the SBK can decrypt the LSK and in turn the contact.
public static byte[] EncryptLocalSymmetricKey(byte[] localSymmetricKey, byte[] semiPublicKey)
{
// Convert the keys to byte arrays
using var aes = Aes.Create();
aes.Key = semiPublicKey;
aes.GenerateIV();
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using var ms = new MemoryStream();
ms.Write(aes.IV, 0, aes.IV.Length); // Prepend IV to the encrypted data
using (var cryptoStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(localSymmetricKey, 0, localSymmetricKey.Length);
}
return ms.ToArray(); // Return Base64-encoded encrypted data
}
// Decrypts the Local Symmetric Key(LSK) with the Semi Public Key(SPK)
public static byte[] DecryptLocalSymmetricKey(byte[] encryptedLocalSymmetricKey, byte[] semiPublicKey)
{
// Convert the keys and encrypted data to byte arrays
using var aes = Aes.Create();
// Extract the IV from the encrypted data
byte[] iv = new byte[16];
Array.Copy(encryptedLocalSymmetricKey, 0, iv, 0, iv.Length);
aes.IV = iv;
aes.Key = semiPublicKey;
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using var ms = new MemoryStream(encryptedLocalSymmetricKey, iv.Length, encryptedLocalSymmetricKey.Length - iv.Length);
using var cryptoStream = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var reader = new BinaryReader(cryptoStream);
byte[] decryptedKeyBytes = reader.ReadBytes((int)(ms.Length - iv.Length));
return decryptedKeyBytes; // Return Base64-encoded localSymmetricKey
}
// Encrypts data using a local symmetric key and a salt
public static byte[] EncryptLocalSymmetricKeyWithSalt(byte[] localKey, byte[] data, byte[] salt)
{
// Derive a strong encryption key using PBKDF2
using var keyDerivation = new Rfc2898DeriveBytes(localKey, salt, 100_000, HashAlgorithmName.SHA256);
byte[] derivedKey = keyDerivation.GetBytes(32); // AES-256 key
using var aes = Aes.Create();
aes.Key = derivedKey;
aes.GenerateIV(); // Generate random IV
using var encryptorAES = aes.CreateEncryptor(aes.Key, aes.IV);
using var ms = new MemoryStream();
ms.Write(aes.IV, 0, aes.IV.Length); // Prepend IV to the encrypted data
using (var cryptoStream = new CryptoStream(ms, encryptorAES, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
}
return ms.ToArray(); // Return the encrypted byte array
}
// Decrypts data using a local symmetric key and a salt
public static byte[] DecryptLocalSymmetricKeyWithSalt(byte[] localKey, byte[] encryptedData, byte[] salt)
{
try
{
// Derive the encryption key using PBKDF2
using var keyDerivation = new Rfc2898DeriveBytes(localKey, salt, 100_000, HashAlgorithmName.SHA256);
byte[] derivedKey = keyDerivation.GetBytes(32); // AES-256 key
using var aes = Aes.Create();
aes.Key = derivedKey;
// Extract IV (first 16 bytes of the encrypted data)
byte[] iv = encryptedData.Take(16).ToArray();
aes.IV = iv;
using var decryptorAES = aes.CreateDecryptor(aes.Key, aes.IV);
using var ms = new MemoryStream(encryptedData.Skip(16).ToArray());
using var cryptoStream = new CryptoStream(ms, decryptorAES, CryptoStreamMode.Read);
using var decryptedStream = new MemoryStream();
cryptoStream.CopyTo(decryptedStream);
return decryptedStream.ToArray(); // Return the decrypted data
}
catch(Exception ex)
{
SystemLogger.Log($"Error-DecryptLocalSymmetricKeyWithSalt: Failed to decrypt Key. Reason:{ex.Message}");
return null;
}
}
// Converts a CNG public key to SubjectPublicKeyInfo format
}
public class KeyParts
{
public byte[] Alpha { get; set; }
public byte[] Beta { get; set; }
public byte[] Delta { get; set; }
public int SplitIndex { get; set; } // Added split index for tracking the starting point
}
public class EncryptedKeyParts
{
[JsonPropertyName("Alpha")]
public byte Alpha { get; set; } // The known starting point for decryption
[JsonPropertyName("AllParts")]
public byte[][] AllParts { get; set; } // Contains both real and fake encrypted key parts
[JsonPropertyName("SplitIndex")]
public int SplitIndex { get; set; } // Added split index for tracking the starting point
}
public class NodeKeyStorageFile
{
[JsonPropertyName("NodePublicSignatureKey")]
public string NodePublicSignatureKey { get; set; }
[JsonPropertyName("NodePublicEncryptionKey")]
public string NodePublicEncryptionKey { get; set; }
[JsonPropertyName("NodePrivateSignatureKey")]
public string NodePrivateSignatureKey { get; set; }
[JsonPropertyName("NodePrivateEncryptionKey")]
public string NodePrivateEncryptionKey { get; set; }
// 🔹 Convert Base64 string to `byte[]`
public byte[] GetByteArray(string key) => string.IsNullOrEmpty(key) ? null : Convert.FromBase64String(key);
// 🔹 Convert `byte[]` to Base64 and store as string
public void SetByteArray(ref string property, byte[] value) => property = value != null ? Convert.ToBase64String(value) : null;
}
internal class PrivateKeyManager
{
// Stores the private Contact in Memory
private SecureString PrivatePersonalEncryptionKey { get; set; } = new SecureString();
private SecureString PrivatePersonalSignatureKey { get; set; } = new SecureString();
private SecureString PublicPersonalEncryptionKey { get; set; } = new SecureString();
private SecureString PublicPersonalSignatureKey { get; set; } = new SecureString();
private SecureString SemiPublicKey { get; set; } = new SecureString();
private SecureString LocalSymmetricKey { get; set; } = new SecureString();
private SecureString EncryptedLocalSymmetricKey { get; set; } = new SecureString();
private readonly string BaseKeyFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Sphere\\Container\\");
internal string KeyFilePath { get; private set; } = "";
//-----New Encryption Method for the BlockContact-----\\
private static KeyParts DynamicLabeledKeySplit(byte[] fullKey)
{
using var sha256 = SHA256.Create();
byte[] hash = sha256.ComputeHash(fullKey);
// Minimum 32 bits (4 bytes) per part
int minPartSize = 32;
int remainingBits = 256 - (minPartSize * 3);
// Dynamically determine sizes
int partASize = (hash[0] % (remainingBits + 1)) + minPartSize;
int partBSize = (hash[1] % (remainingBits - (partASize - minPartSize) + 1)) + minPartSize;
int partCSize = 256 - (partASize + partBSize);
// Perform the split
byte[] part1 = fullKey.Take(partASize / 8).ToArray();
byte[] part2 = fullKey.Skip(partASize / 8).Take(partBSize / 8).ToArray();
byte[] part3 = fullKey.Skip((partASize + partBSize) / 8).ToArray();
// Deterministically assign labels based on hash byte parity
KeyParts labeledParts = new KeyParts();
int splitIndex = hash[2] % 3; // Determines the first key to use
switch (splitIndex)
{
case 0:
labeledParts.Alpha = part1;
labeledParts.Beta = part2;
labeledParts.Delta = part3;
break;
case 1:
labeledParts.Alpha = part2;
labeledParts.Beta = part3;
labeledParts.Delta = part1;
break;
case 2:
labeledParts.Alpha = part3;
labeledParts.Beta = part1;
labeledParts.Delta = part2;
break;
}
labeledParts.SplitIndex = splitIndex; // Store the split index for later reconstruction
return labeledParts;
}
// Retrieves the private Contact key from memory
internal byte[] GetPersonalKey(Node node, KeyGenerator.KeyType keyType)
{
//SystemLogger.Log("Debug-GetPersonalKey: Attempting to retrieve a personal key.");
SecureString secureKey = keyType switch
{
KeyGenerator.KeyType.PrivatePersonalEncryptionKey => node.KeyManager.PrivatePersonalEncryptionKey,
KeyGenerator.KeyType.PrivatePersonalSignatureKey => node.KeyManager.PrivatePersonalSignatureKey,
KeyGenerator.KeyType.PublicPersonalSignatureKey => node.KeyManager.PublicPersonalSignatureKey,
KeyGenerator.KeyType.PublicPersonalEncryptionKey => node.KeyManager.PublicPersonalEncryptionKey,
KeyGenerator.KeyType.SemiPublicKey => node.KeyManager.SemiPublicKey,
KeyGenerator.KeyType.LocalSymmetricKey => node.KeyManager.LocalSymmetricKey,
KeyGenerator.KeyType.EncryptedLocalSymmetricKey => node.KeyManager.EncryptedLocalSymmetricKey,
_ => null
};
if (secureKey == null || secureKey.Length == 0)
{
SystemLogger.Log($"Error-GetPersonalKey: Key {keyType} is not available in memory.");
return null;
}
// Convert SecureString to byte[]
IntPtr ptr = Marshal.SecureStringToGlobalAllocUnicode(secureKey);
try
{
string base64Key = Marshal.PtrToStringUni(ptr);
if (string.IsNullOrEmpty(base64Key))
{
SystemLogger.Log($"Error-GetPersonalKey: Extracted key string is empty for {keyType}");
return null;
}
//SystemLogger.Log($"Debug-GetPersonalKey: Successfully retrieved {keyType}");
return Convert.FromBase64String(base64Key);
}
catch (FormatException ex)
{
SystemLogger.Log($"Error-GetPersonalKey: Failed to decode Base64 key. Reason: {ex.Message}");
return null;
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
}
// Retrieves the private Node key from memory
internal byte[] GetNodeKey(KeyGenerator.KeyType keyType)
{
// SystemLogger.Log($"Debug-GetNodeKey: Getting Node Key...@.@");
try
{
if (!File.Exists(KeyFilePath))
{
SystemLogger.Log($"Error-GetNodeKey: Error Key File did not exist. ({KeyFilePath})");
return null;
}
NodeKeyStorageFile keyfile = LoadKeyFile();
if (keyfile == null)
{
SystemLogger.Log($"Error-GetNodeKey: Key file could not be loaded.");
return null;
}
// Convert Base64 strings to byte arrays
byte[] keyToReturn = keyType switch
{
KeyGenerator.KeyType.PublicNodeSignatureKey => ConvertKeyFromBase64(keyfile.NodePublicSignatureKey),
KeyGenerator.KeyType.PublicNodeEncryptionKey => ConvertKeyFromBase64(keyfile.NodePublicEncryptionKey),
KeyGenerator.KeyType.PrivateNodeSignatureKey => ConvertKeyFromBase64(keyfile.NodePrivateSignatureKey),
KeyGenerator.KeyType.PrivateNodeEncryptionKey => ConvertKeyFromBase64(keyfile.NodePrivateEncryptionKey),
_ => null
};
if (keyToReturn == null || keyToReturn.Length == 0)
{
SystemLogger.Log($"Error-GetNodeKey: Retrieved key is NULL or EMPTY.");
return null;
}
return keyToReturn;
}
catch (Exception ex)
{
SystemLogger.Log($"Error-GetNodeKey: Failed to Get the Key. Reason: {ex.Message}");
return null;
}
}
private byte[] ConvertKeyFromBase64(string base64Key)
{
if (string.IsNullOrEmpty(base64Key))
{
return null;
}
try
{
return Convert.FromBase64String(base64Key);
}
catch (FormatException ex)
{
SystemLogger.Log($"Error-ConvertKeyFromBase64: Invalid Base64 format. Reason: {ex.Message}");
return null;
}
}
//Securely Wipe Key from Storage
private void SecureWipeKeyStorage(ref NodeKeyStorageFile keyfile)
{
try
{
if (keyfile == null) return;
// Function to wipe Base64-encoded keys safely
string WipeBase64String(string base64Key)
{
if (string.IsNullOrEmpty(base64Key)) return null;
byte[] keyBytes = Convert.FromBase64String(base64Key);
RandomNumberGenerator.Fill(keyBytes);
return Convert.ToBase64String(keyBytes);
}
// Overwrite each key securely
keyfile.NodePublicSignatureKey = WipeBase64String(keyfile.NodePublicSignatureKey);
keyfile.NodePublicEncryptionKey = WipeBase64String(keyfile.NodePublicEncryptionKey);
keyfile.NodePrivateSignatureKey = WipeBase64String(keyfile.NodePrivateSignatureKey);
keyfile.NodePrivateEncryptionKey = WipeBase64String(keyfile.NodePrivateEncryptionKey);
// Nullify the reference
keyfile = null;
}
catch (Exception ex)
{
SystemLogger.Log($"Error-SecureWipeKeyStorage: Failed to wipe Key from storage, Reason:{ex.Message}");
}
}
// Securely wipes the private Contact key from memory
internal void WipePersonalKeyFromMemory(KeyGenerator.KeyType keyType)
{
try
{
if (keyType != KeyGenerator.KeyType.PrivateNodeEncryptionKey || keyType != KeyGenerator.KeyType.PrivateNodeSignatureKey || keyType != KeyGenerator.KeyType.LocalSymmetricKey || keyType != KeyGenerator.KeyType.SemiPublicKey)
{
return;
}
switch (keyType)
{
case KeyGenerator.KeyType.PrivateNodeEncryptionKey:
PrivatePersonalEncryptionKey?.Dispose();
PrivatePersonalEncryptionKey= new SecureString();
break;
case KeyGenerator.KeyType.PrivateNodeSignatureKey:
PrivatePersonalSignatureKey?.Dispose();
PrivatePersonalSignatureKey = new SecureString();
break;
case KeyGenerator.KeyType.SemiPublicKey:
SemiPublicKey?.Dispose();
SemiPublicKey = new SecureString();
break;
case KeyGenerator.KeyType.LocalSymmetricKey:
LocalSymmetricKey?.Dispose();
LocalSymmetricKey = new SecureString();
break;
}
}
catch(Exception ex)
{
SystemLogger.Log($"Error- WipePersonalKeyFromMemory: Failed to wipe Key from memory, Reason:{ex.Message}");
}
}
//Set the Private Personal Key in Memory
internal void SetPrivatePersonalKey(Node node, byte[] key, KeyGenerator.KeyType keyType)
{
// SystemLogger.Log($"Debug-SetPrivatePersonalKey: Using KeyManager instance {node.KeyManager.GetHashCode()} to retrieve {keyType}");
if (key == null || key.Length == 0)
{
SystemLogger.Log("Error-SetPrivatePersonalKey: Key cannot be null or empty.");
return;
}
string keyString = Convert.ToBase64String(key);
SecureString newSecureString = new SecureString();
foreach (char c in keyString)
{
newSecureString.AppendChar(c);
}
newSecureString.MakeReadOnly();
switch (keyType)
{
case KeyGenerator.KeyType.PrivatePersonalEncryptionKey:
node.KeyManager.PrivatePersonalEncryptionKey?.Dispose();
node.KeyManager.PrivatePersonalEncryptionKey = newSecureString;
// SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
case KeyGenerator.KeyType.PrivatePersonalSignatureKey:
node.KeyManager.PrivatePersonalSignatureKey?.Dispose();
node.KeyManager.PrivatePersonalSignatureKey = newSecureString;
//SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
case KeyGenerator.KeyType.PublicPersonalEncryptionKey:
node.KeyManager.PublicPersonalEncryptionKey?.Dispose();
node.KeyManager.PublicPersonalEncryptionKey = newSecureString;
//SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
case KeyGenerator.KeyType.PublicPersonalSignatureKey:
node.KeyManager.PublicPersonalSignatureKey?.Dispose();
node.KeyManager.PublicPersonalSignatureKey = newSecureString;
//SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
case KeyGenerator.KeyType.SemiPublicKey:
node.KeyManager.SemiPublicKey?.Dispose();
node.KeyManager.SemiPublicKey = newSecureString;
//SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
case KeyGenerator.KeyType.LocalSymmetricKey:
node.KeyManager.LocalSymmetricKey?.Dispose();
node.KeyManager.LocalSymmetricKey = newSecureString;
// SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
case KeyGenerator.KeyType.EncryptedLocalSymmetricKey:
node.KeyManager.EncryptedLocalSymmetricKey?.Dispose();
node.KeyManager.EncryptedLocalSymmetricKey = newSecureString;
//SystemLogger.Log($"Debug-SetPrivatePersonalKey: Successfully stored {keyType}");
break;
default:
SystemLogger.Log($"Error-SetPrivatePersonalKey: Unhandled KeyType {keyType}");
return;
}
}
//Set Node Encryption Path
internal void SetNodeEncryptionFilePath(Node node)
{
if (!Directory.Exists(BaseKeyFilePath))
{
Directory.CreateDirectory(BaseKeyFilePath);
}
string fist12ofNodeID= node.Peer.NodeId.Length >= 12 ? node.Peer.NodeId.Substring(0, 12) : node.Peer.NodeId;
string nodePath = Path.Combine(BaseKeyFilePath + fist12ofNodeID + ".dat");
node.KeyManager.KeyFilePath = nodePath;
}
// Encrypts the key parts and returns the encrypted parts
internal EncryptedKeyParts EncryptPersonalKeyParts(byte[] fullKey, string password, string pin)
{
KeyParts realParts = DynamicLabeledKeySplit(fullKey);
try
{
using var sha256 = SHA256.Create();
// Create 3-4 fake key parts filled with random data
byte[][] fakeParts = new byte[3][];
for (int i = 0; i < fakeParts.Length; i++)
{
fakeParts[i] = RandomNumberGenerator.GetBytes(realParts.Alpha.Length); // Fake junk data
}
// Mix real parts with fake parts in a random order
byte[][] allParts = new byte[6][];
allParts[0] = realParts.Alpha;
allParts[1] = realParts.Beta;
allParts[2] = realParts.Delta;
allParts[3] = fakeParts[0];
allParts[4] = fakeParts[1];
allParts[5] = fakeParts[2];
// Encrypt each part with its dependencies
byte[] pinBytes = Encoding.UTF8.GetBytes(pin);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
byte[] combinedPassPin = passwordBytes.Concat(pinBytes).ToArray();
byte[] combinedKeySalt = allParts[0].Concat(combinedPassPin).ToArray();
byte[] passSalt = sha256.ComputeHash(combinedPassPin);
byte[] keySalt = sha256.ComputeHash(combinedKeySalt);
byte[] encryptedC = Encryption.EncryptLocalSymmetricKeyWithSalt(allParts[1], allParts[2], sha256.ComputeHash(allParts[0]));
byte[] modifiedB = InjectVerificationBit(allParts[1], encryptedC[0]);
byte[] encryptedB = Encryption.EncryptLocalSymmetricKeyWithSalt(allParts[0], allParts[1], passSalt);
byte[] modifiedA = InjectVerificationBit(allParts[0], encryptedB[0]);
byte[] encryptedA = Encryption.EncryptLocalSymmetricKeyWithSalt(modifiedA, passwordBytes, pinBytes);
allParts[0] = encryptedA;
allParts[1] = encryptedB;
allParts[2] = encryptedC;
ShuffleArray(allParts);
return new EncryptedKeyParts
{
Alpha = encryptedA[0],
SplitIndex = realParts.SplitIndex,
AllParts = allParts
};
}
catch (Exception ex)
{
SystemLogger.Log($"Error-EncryptPersonalKeyParts: Failed to Encrypt Personal Key Parts. Reason: {ex.Message}");
return null;
}
}
// Decrypts the key parts and reconstructs the full key
internal byte[] DecryptKeyWithFakeDetection(EncryptedKeyParts encryptedParts, string password, string pin)
{
try
{
using var sha256 = SHA256.Create();
byte[] pinBytes = Encoding.UTF8.GetBytes(pin);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
byte[] combinedPassPin = passwordBytes.Concat(pinBytes).ToArray();
byte[] alphaKeyEncrypted = FindMatchingPart(encryptedParts.AllParts, encryptedParts.Alpha);
byte[] decryptedA = Encryption.DecryptLocalSymmetricKeyWithSalt(passwordBytes, alphaKeyEncrypted, pinBytes);
byte[] passSalt = sha256.ComputeHash(combinedPassPin);
var (AlphaFollowerBit, keyACleaned) = ExtractVerificationBit(decryptedA);
byte[] Alpha = keyACleaned;
byte[] keySalt = sha256.ComputeHash(Alpha.Concat(passSalt).ToArray());
byte[] nextKey = FindMatchingPart(encryptedParts.AllParts, AlphaFollowerBit);
if (nextKey == null)
{
throw new Exception("No matching part found! Possible data corruption or incorrect verification bit.");
}
byte[] decryptedB = Encryption.DecryptLocalSymmetricKeyWithSalt(Alpha, nextKey, passSalt);
var (BetaFollowerBit, keyBCleaned) = ExtractVerificationBit(decryptedB);
nextKey = FindMatchingPart(encryptedParts.AllParts, BetaFollowerBit);
byte[] Beta = keyBCleaned;
byte[] decryptedC = Encryption.DecryptLocalSymmetricKeyWithSalt(Beta, nextKey, keySalt);
return CombineByteArrays(decryptedA, decryptedB, decryptedC, encryptedParts.SplitIndex);
}
catch (Exception ex)
{
SystemLogger.Log($"Error-DecryptKeyWithFakeDetection: Failed to decrypt the key. Reason:{ex.Message}");
return null;
}
}
// Shuffle the Key Array
internal void ShuffleArray(byte[][] array)
{
try
{
Random rng = new Random();
int n = array.Length;
for (int i = n - 1; i > 0; i--)
{
int j = rng.Next(i + 1);
(array[i], array[j]) = (array[j], array[i]); // Swap elements
}
}
catch (Exception ex)
{
SystemLogger.Log($"Error-ShuffleArray: Failed to Shuffle Array. Reason {ex.Message}");
}
}
// Injects a verification bit into the data
internal byte[] InjectVerificationBit(byte[] targetData, byte verificationByte)
{
try
{
byte[] modifiedData = new byte[targetData.Length + 1];
int insertPosition = targetData.Length / 4; // Example: Inject after 1/4 of the data
Array.Copy(targetData, 0, modifiedData, 0, insertPosition);
modifiedData[insertPosition] = verificationByte; // Insert verification bit
Array.Copy(targetData, insertPosition, modifiedData, insertPosition + 1, targetData.Length - insertPosition);
return modifiedData;
}
catch (Exception ex)
{
SystemLogger.Log($"Error-InjectVerificationBit: Failed to Inject VerificationBit. Reason:{ex.Message}");
return null;
}
}
// Extracts the verification bit from the data
internal (byte verificationByte, byte[] restoredData) ExtractVerificationBit(byte[] modifiedData)
{
try
{
if (modifiedData == null || modifiedData.Length < 4)
{
SystemLogger.Log("Error-ExtractVerificationBit: Input data is null or too short.");
return (0, null);
}
int insertPosition = modifiedData.Length / 4;
if (insertPosition < 0 || insertPosition >= modifiedData.Length)
{
SystemLogger.Log("Error-ExtractVerificationBit: Insert position out of range.");
return (0, null);
}
byte verificationByte = modifiedData[insertPosition];
byte[] restoredData = new byte[modifiedData.Length - 1];
Array.Copy(modifiedData, 0, restoredData, 0, insertPosition);
Array.Copy(modifiedData, insertPosition + 1, restoredData, insertPosition, restoredData.Length - insertPosition);
return (verificationByte, restoredData);
}
catch (Exception ex)
{
SystemLogger.Log($"Error-ExtractVerificationBit: Failed to Extract VerificationBit. Reason:{ex.Message}");
return (0, new byte[0]);
}
}
// Finds the matching part based on the verification bit
internal byte[] FindMatchingPart(byte[][] allParts, byte verificationBit)
{
try
{
foreach (byte[] part in allParts)
{
if (part.Length > 0 && part[0] == verificationBit) // Check if the first byte matches
{
return part; // Found the correct part
}
}
throw new Exception("No matching part found! Possible data corruption or incorrect verification bit.");
}
catch (Exception ex)
{
SystemLogger.Log($"Error-InjectVerificationBit: Failed to Inject VerificationBit. Reason:{ex.Message}");
return null;
}
}
internal byte[] CombineByteArrays(byte[] first, byte[] second, byte[] third, int splitIndex)
{
byte[][] parts = new byte[3][];
try
{
// Reassemble based on the stored SplitIndex
switch (splitIndex)
{
case 0:
parts[0] = first; // Alpha (originally part1)
parts[1] = second; // Beta (originally part2)
parts[2] = third; // Delta (originally part3)
break;
case 1:
parts[0] = second; // Alpha (originally part2)
parts[1] = third; // Beta (originally part3)
parts[2] = first; // Delta (originally part1)
break;
case 2:
parts[0] = third; // Alpha (originally part3)
parts[1] = first; // Beta (originally part1)
parts[2] = second; // Delta (originally part2)
break;
default:
throw new ArgumentException("Invalid split index provided.");
}
using var ms = new MemoryStream();
ms.Write(parts[0], 0, parts[0].Length);
ms.Write(parts[1], 0, parts[1].Length);
ms.Write(parts[2], 0, parts[2].Length);
return ms.ToArray();
}
catch (Exception ex)
{
SystemLogger.Log($"Error-CombineByteArrays: Failed to Combine Byte Arrays. Reason:{ex.Message}");
return null;
}
}
//Encrypt and Store the Node Key Locally
internal void StoreNodeKeyLocally(byte[] key, KeyGenerator.KeyType keyType)
{
try
{
//SystemLogger.Log($"Debug-StoreKeyLocally: Attempting to Store a Key of type {keyType}");
if (key == null || key.Length == 0)
{
SystemLogger.Log($"Error-StoreKeyLocally: Received an empty or NULL key.");
return;
}
NodeKeyStorageFile newKeyStorage;
if (File.Exists(KeyFilePath))
{
//SystemLogger.Log($"Debug-StoreKeyLocally: File Path Exists - Loading existing file.");
newKeyStorage = LoadKeyFile() ?? new NodeKeyStorageFile();
}
else
{
//SystemLogger.Log($"Debug-StoreKeyLocally: No File Existed. Creating new File...");
newKeyStorage = new NodeKeyStorageFile();
}
//SystemLogger.Log($"Debug-StoreKeyLocally: Storing Keys:");
switch (keyType)
{
case KeyGenerator.KeyType.PublicNodeSignatureKey:
newKeyStorage.NodePublicSignatureKey = Convert.ToBase64String(key);
break;
case KeyGenerator.KeyType.PublicNodeEncryptionKey:
newKeyStorage.NodePublicEncryptionKey = Convert.ToBase64String(key);
break;
case KeyGenerator.KeyType.PrivateNodeSignatureKey:
newKeyStorage.NodePrivateSignatureKey = Convert.ToBase64String(key);
break;
case KeyGenerator.KeyType.PrivateNodeEncryptionKey:
newKeyStorage.NodePrivateEncryptionKey = Convert.ToBase64String(key);
break;
}
SaveKeyStorage(newKeyStorage);
}
catch (Exception ex)
{
SystemLogger.Log($"Error-StoreKeyLocally: Failed to store Key {keyType}. Reason: {ex.Message}");
}
}
//Load the Key Storage File.
internal NodeKeyStorageFile LoadKeyFile()
{
try
{
// SystemLogger.Log($"Debug-LoadKeyFile: Loading Key File...");
if (!File.Exists(KeyFilePath))
{
SystemLogger.Log($"Error-LoadKeyFile: Key file does not exist at {KeyFilePath}");
return null;
}
byte[] jsonData = File.ReadAllBytes(KeyFilePath);
if (jsonData == null || jsonData.Length == 0)
{
SystemLogger.Log($"Error-LoadKeyFile: Key file exists but is empty at {KeyFilePath}");
return null;
}
byte[] decryptedData = Encryption.DecryptLocalSymmetricKeyWithSalt(GenerateUserKey(), jsonData, GenerateUserSalt());
if (decryptedData == null || decryptedData.Length == 0)
{
SystemLogger.Log($"Error-LoadKeyFile: Decryption failed, resulting in empty data.");
return null;
}
NodeKeyStorageFile file = JsonSerializer.Deserialize<NodeKeyStorageFile>(decryptedData);
if (file == null)
{
SystemLogger.Log($"Error-LoadKeyFile: JSON Deserialization failed.");
return null;
}
// SystemLogger.Log($"Debug-LoadKeyFile: Successfully deserialized key file.");
return file;
}
catch (Exception ex)
{
SystemLogger.Log($"Error-LoadKeyFile: Failed to load Key file. Reason {ex.Message}");
return null;
}
}
//Save the KeyStoreFile to disk
private void SaveKeyStorage(NodeKeyStorageFile keyStorage)
{
try
{
if (keyStorage == null)
{
SystemLogger.Log($"Error-SaveKeyStorage: Attempted to save a NULL key storage object.");
return;
}
//SystemLogger.Log($"Debug-SaveKeyStorage: Saving Node Key Storage:");
// Prevent saving if all keys are NULL
if (string.IsNullOrEmpty(keyStorage.NodePublicSignatureKey) &&
string.IsNullOrEmpty(keyStorage.NodePublicEncryptionKey) &&
string.IsNullOrEmpty(keyStorage.NodePrivateSignatureKey) &&
string.IsNullOrEmpty(keyStorage.NodePrivateEncryptionKey))
{
SystemLogger.Log($"Error-SaveKeyStorage: All keys are NULL. Skipping save operation.");
return;
}
byte[] jsonData = JsonSerializer.SerializeToUtf8Bytes(keyStorage);
byte[] encryptedData = Encryption.EncryptLocalSymmetricKeyWithSalt(GenerateUserKey(), jsonData, GenerateUserSalt());
if (encryptedData == null || encryptedData.Length == 0)
{
SystemLogger.Log($"Error-LoadKeyFile: Decryption failed, resulting in empty data.");
return;
}
File.WriteAllBytes(KeyFilePath, encryptedData);
// SystemLogger.Log($"Debug-SaveKeyStorage: Successfully wrote Key to file.");
SetSecureFilePermissions(KeyFilePath);
}
catch (Exception ex)
{
SystemLogger.Log($"Error-SaveKeyStorage: Failed to Save KeyStorage. Reason:{ex.Message}");
}
}
//Set the security setting of the Key File
private void SetSecureFilePermissions(string filePath)
{
try
{
var fileInfo = new FileInfo(filePath);
var security = fileInfo.GetAccessControl();
security.SetAccessRule(new FileSystemAccessRule(
Environment.UserName,
FileSystemRights.FullControl,
AccessControlType.Allow));
fileInfo.SetAccessControl(security);
}
catch (Exception ex)
{
SystemLogger.Log($"Error-SetSecureFilePermissions: Failed to set file protections. Reason {ex.Message}");
}
}
//Generate a user specific key for encryption
public byte[] GenerateUserKey()
{
try
{
string userName = Environment.UserName; // Get Windows username
string userSID = WindowsIdentity.GetCurrent().User.Value; // More unique user ID
using var sha256 = SHA256.Create();
return sha256.ComputeHash(Encoding.UTF8.GetBytes(userName + userSID));
}
catch (Exception ex)
{
SystemLogger.Log($"Error-GenerateUserKey:Failed to Generate User Key. Reason:{ex.Message}");
throw new InvalidOperationException("Failed to generate user key, encryption aborted.", ex);
}
}
//Generate a user specific salt for encryption
public byte[] GenerateUserSalt()
{
try
{
string userDomain = Environment.UserDomainName; // Windows domain
string systemDrive = Environment.GetFolderPath(Environment.SpecialFolder.System); // System root path
using var sha256 = SHA256.Create();
return sha256.ComputeHash(Encoding.UTF8.GetBytes(userDomain + systemDrive));
}
catch (Exception ex)
{
SystemLogger.Log($"Error-GenerateUserSalt:Failed to Generate User Salt. Reason:{ex.Message}");
throw new InvalidOperationException("Failed to generate user salt, encryption aborted.", ex);
}
}
internal byte[] UseKeyInStorageContainer(Node node, KeyGenerator.KeyType keyType)
{
try
{
// SystemLogger.Log($"Debug-UseKeyInStorageContainer: Using KeyManager instance {node.KeyManager.GetHashCode()} to retrieve {keyType}");
// SystemLogger.Log($"Debug-UseKeyInStorageContainer: Attempting to get Key.");
byte[] keyData= null;
PrivateKeyManager privateKeyManager = node.KeyManager;
switch (keyType)
{
case KeyType.PrivateNodeEncryptionKey:
keyData = privateKeyManager.GetNodeKey(KeyType.PrivateNodeEncryptionKey);
break;
case KeyType.PrivateNodeSignatureKey:
keyData = privateKeyManager.GetNodeKey(KeyType.PrivateNodeSignatureKey);
break;
case KeyType.PublicNodeEncryptionKey:
keyData = privateKeyManager.GetNodeKey(KeyType.PublicNodeEncryptionKey);
break;
case KeyType.PublicNodeSignatureKey:
keyData = privateKeyManager.GetNodeKey(KeyType.PublicNodeSignatureKey);
break;
case KeyType.PrivatePersonalEncryptionKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.PrivatePersonalEncryptionKey);
break;
case KeyType.PrivatePersonalSignatureKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.PrivatePersonalSignatureKey);
break;
case KeyType.PublicPersonalEncryptionKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.PublicPersonalEncryptionKey);
break;
case KeyType.PublicPersonalSignatureKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.PublicPersonalSignatureKey);
break;
case KeyType.SemiPublicKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.SemiPublicKey);
break;
case KeyType.LocalSymmetricKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.LocalSymmetricKey);
break;
case KeyType.EncryptedLocalSymmetricKey:
keyData = privateKeyManager.GetPersonalKey(node, KeyType.EncryptedLocalSymmetricKey);
break;
default:
SystemLogger.Log($"Error-UseKeyInStorageContainer: Invalid key type: {keyType}");
return null;
}
if (keyData == null || keyData.Length == 0)
{
// SystemLogger.Log($"Error-UseKeyInStorageContainer: Retrieved key is null or empty for keyType: {keyType}");
throw new Exception($"Key retrieval failed for {keyType}, keyData is null or empty.");
}
return keyData;
}
catch (Exception ex)
{
SystemLogger.Log($"Error-UseKeyInStorageContainer: Error retrieving key '{keyType.ToString()}' Reason: {ex.Message}");
throw;
}
}
}
}