Last active
April 3, 2024 19:22
-
-
Save viruseg/d30cdca9df952c798e4da56e1b01428f to your computer and use it in GitHub Desktop.
Encrypting & Decrypting (file, string, byte array) in C#, NET 8.0 and Unity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//The code is based on the code from here: https://stackoverflow.com/a/10177020/1221983 | |
//And adapted for NET 8 and Unity. | |
//Reduced memory consumption. | |
//Added possibility to encrypt byte arrays, not only strings. | |
//You can also attach custom data that will not be encrypted. | |
var password = "nso%dhfkl$siohf"; | |
var str = "This is test string!№;%^#@^&"; | |
var bytesArr = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 128, 129, 130, 250, 255 }; | |
var strE = Encryptor.Encrypt(str, password, "custom data"); | |
var bytesArrE = Encryptor.Encrypt(bytesArr, password, new byte[] { 9, 65, 14 }); | |
{ | |
var customData = Encryptor.GetCustomData(strE); | |
var size = Encryptor.GetDataSize(strE); | |
} | |
{ | |
var customData = Encryptor.GetCustomData(bytesArrE); | |
var size = Encryptor.GetDataSize(bytesArrE); | |
} | |
{ | |
var strD = Encryptor.Decrypt(strE, password, out var customData); | |
var bytesArrD = Encryptor.Decrypt(bytesArrE, password, out var customDataArr); | |
} | |
{ | |
var fileBytes = File.ReadAllBytes(@"F:\Test.jpg"); | |
var fileBytesE = Encryptor.Encrypt(fileBytes, password, null); | |
File.WriteAllBytes(@"F:\Encrypted.enc", fileBytesE); | |
} | |
{ | |
var fileBytes = File.ReadAllBytes(@"F:\Encrypted.enc"); | |
var fileBytesD = Encryptor.Decrypt(fileBytes, password, out _); | |
File.WriteAllBytes(@"F:\Decrypted.jpg", fileBytesD); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace EncryptorLib | |
{ | |
public static unsafe class Encryptor | |
{ | |
private const int BUFFER_SIZE = 128 / 8; | |
private const int DERIVATION_ITERATIONS = 1000; | |
public static string Encrypt(string text, string password, string? customData) | |
{ | |
var textAsBytes = Encoding.Default.GetBytes(text); | |
var customDataAsBytes = customData != null ? Encoding.Default.GetBytes(customData) : Array.Empty<byte>(); | |
var cipherTextBytes = Encrypt(textAsBytes, password, customDataAsBytes); | |
return Convert.ToBase64String(cipherTextBytes); | |
} | |
public static byte[] Encrypt(byte[] bytes, string password, byte[] customData) | |
{ | |
return Encrypt(new ReadOnlySpan<byte>(bytes), password, new ReadOnlySpan<byte>(customData)); | |
} | |
public static byte[] Encrypt(ReadOnlySpan<byte> bytes, string password, ReadOnlySpan<byte> customData) | |
{ | |
var saltStringBytes = Generate256BitsOfRandomEntropy(); | |
var ivStringBytes = Generate256BitsOfRandomEntropy(); | |
using var passwordKey = new Rfc2898DeriveBytes(password, saltStringBytes, DERIVATION_ITERATIONS, HashAlgorithmName.SHA512); | |
var keyBytes = passwordKey.GetBytes(BUFFER_SIZE); | |
using var symmetricKey = Aes.Create(); | |
symmetricKey.BlockSize = 128; | |
symmetricKey.Mode = CipherMode.CBC; | |
symmetricKey.Padding = PaddingMode.PKCS7; | |
using var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes); | |
using var memoryStream = new MemoryStream(); | |
using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); | |
cryptoStream.Write(bytes); | |
cryptoStream.FlushFinalBlock(); | |
var customDataSize = customData.Length; | |
var result = new byte[sizeof(int) + sizeof(int) + customDataSize + BUFFER_SIZE + BUFFER_SIZE + memoryStream.Length]; | |
fixed (byte* customDataArrPtr = customData) | |
fixed (byte* saltStringBytesArrPtr = saltStringBytes) | |
fixed (byte* ivStringBytesArrPtr = ivStringBytes) | |
fixed (byte* resultPtr = result) | |
{ | |
var sizePtr = (int*) resultPtr; | |
var customDataSizePtr = (int*) ((byte*) sizePtr + sizeof(int)); | |
var customDataPtr = (byte*) customDataSizePtr + sizeof(int); | |
var saltStringBytesPtr = customDataPtr + customDataSize; | |
var ivStringBytesPtr = saltStringBytesPtr + BUFFER_SIZE; | |
*sizePtr = bytes.Length; | |
*customDataSizePtr = customDataSize; | |
Buffer.MemoryCopy(customDataArrPtr, customDataPtr, customDataSize, customDataSize); | |
Buffer.MemoryCopy(saltStringBytesArrPtr, saltStringBytesPtr, BUFFER_SIZE, BUFFER_SIZE); | |
Buffer.MemoryCopy(ivStringBytesArrPtr, ivStringBytesPtr, BUFFER_SIZE, BUFFER_SIZE); | |
memoryStream.Position = 0; | |
#if UNITY_2021_3_OR_NEWER | |
memoryStream.Read(result, sizeof(int) + sizeof(int) + customDataSize + BUFFER_SIZE + BUFFER_SIZE, (int) memoryStream.Length); | |
#else | |
memoryStream.ReadExactly(result, sizeof(int) + sizeof(int) + customDataSize + BUFFER_SIZE + BUFFER_SIZE, (int) memoryStream.Length); | |
#endif | |
} | |
return result; | |
} | |
public static string GetCustomData(string cipherText) | |
{ | |
var encryptedBytes = Convert.FromBase64String(cipherText); | |
encryptedBytes = GetCustomData(encryptedBytes); | |
return Encoding.Default.GetString(encryptedBytes); | |
} | |
public static byte[] GetCustomData(byte[] encryptedBytes) | |
{ | |
return GetCustomData(new ReadOnlySpan<byte>(encryptedBytes)); | |
} | |
public static byte[] GetCustomData(ReadOnlySpan<byte> encryptedBytes) | |
{ | |
fixed (byte* encryptedBytesPtr = encryptedBytes) | |
{ | |
var sizePtr = (int*) encryptedBytesPtr; | |
var customDataSizePtr = (int*) ((byte*) sizePtr + sizeof(int)); | |
var customDataPtr = (byte*) customDataSizePtr + sizeof(int); | |
var customData = *customDataSizePtr == 0 ? Array.Empty<byte>() : new byte[*customDataSizePtr]; | |
fixed (byte* customDataArrPtr = customData) | |
Buffer.MemoryCopy(customDataPtr, customDataArrPtr, *customDataSizePtr, *customDataSizePtr); | |
return customData; | |
} | |
} | |
/// <summary> | |
/// Source string size | |
/// </summary> | |
public static int GetDataSize(string cipherText) | |
{ | |
var encryptedBytes = Convert.FromBase64String(cipherText); | |
return GetDataSize(encryptedBytes) / sizeof(char); | |
} | |
/// <summary> | |
/// Length of the source array | |
/// </summary> | |
public static int GetDataSize(byte[] encryptedBytes) | |
{ | |
return GetDataSize(new ReadOnlySpan<byte>(encryptedBytes)); | |
} | |
/// <summary> | |
/// Length of the source array | |
/// </summary> | |
public static int GetDataSize(ReadOnlySpan<byte> encryptedBytes) | |
{ | |
fixed (byte* encryptedBytesPtr = encryptedBytes) | |
{ | |
var sizePtr = (int*) encryptedBytesPtr; | |
return *sizePtr; | |
} | |
} | |
public static string Decrypt(string cipherText, string password, out string customData) | |
{ | |
var encryptedBytes = Convert.FromBase64String(cipherText); | |
encryptedBytes = Decrypt(encryptedBytes, password, out var customDataBytes); | |
customData = Encoding.Default.GetString(customDataBytes); | |
return Encoding.Default.GetString(encryptedBytes); | |
} | |
public static byte[] Decrypt(byte[] encryptedBytes, string password, out byte[] customData) | |
{ | |
return Decrypt(new ReadOnlySpan<byte>(encryptedBytes), password, out customData); | |
} | |
public static byte[] Decrypt(ReadOnlySpan<byte> encryptedBytes, string password, out byte[] customData) | |
{ | |
int size; | |
var saltStringBytes = new byte[BUFFER_SIZE]; | |
var ivStringBytes = new byte[BUFFER_SIZE]; | |
ReadOnlySpan<byte> encryptedBytesASpan; | |
fixed (byte* saltStringBytesArrPtr = saltStringBytes) | |
fixed (byte* ivStringBytesArrPtr = ivStringBytes) | |
fixed (byte* encryptedBytesPtr = encryptedBytes) | |
{ | |
var sizePtr = (int*) encryptedBytesPtr; | |
var customDataSizePtr = (int*) ((byte*) sizePtr + sizeof(int)); | |
var customDataPtr = (byte*) customDataSizePtr + sizeof(int); | |
var saltStringBytesPtr = customDataPtr + *customDataSizePtr; | |
var ivStringBytesPtr = saltStringBytesPtr + BUFFER_SIZE; | |
size = *sizePtr; | |
Buffer.MemoryCopy(saltStringBytesPtr, saltStringBytesArrPtr, BUFFER_SIZE, BUFFER_SIZE); | |
Buffer.MemoryCopy(ivStringBytesPtr, ivStringBytesArrPtr, BUFFER_SIZE, BUFFER_SIZE); | |
customData = *customDataSizePtr == 0 ? Array.Empty<byte>() : new byte[*customDataSizePtr]; | |
fixed (byte* customDataArrPtr = customData) | |
Buffer.MemoryCopy(customDataPtr, customDataArrPtr, *customDataSizePtr, *customDataSizePtr); | |
var dataOffset = sizeof(int) + sizeof(int) + customData.Length + BUFFER_SIZE + BUFFER_SIZE; | |
encryptedBytesASpan = new ReadOnlySpan<byte>(encryptedBytesPtr + dataOffset, encryptedBytes.Length - dataOffset); | |
} | |
using var passwordKey = new Rfc2898DeriveBytes(password, saltStringBytes, DERIVATION_ITERATIONS, HashAlgorithmName.SHA512); | |
var keyBytes = passwordKey.GetBytes(BUFFER_SIZE); | |
using var symmetricKey = Aes.Create(); | |
symmetricKey.BlockSize = 128; | |
symmetricKey.Mode = CipherMode.CBC; | |
symmetricKey.Padding = PaddingMode.PKCS7; | |
using var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes); | |
using var memoryStream = new MemoryStream(encryptedBytesASpan.Length); | |
memoryStream.Write(encryptedBytesASpan); | |
memoryStream.Position = 0; | |
using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); | |
var result = new byte[size]; | |
try | |
{ | |
#if UNITY_2021_3_OR_NEWER | |
cryptoStream.Read(result, 0, size); | |
#else | |
cryptoStream.ReadExactly(result, 0, size); | |
#endif | |
} | |
catch (Exception e) | |
{ | |
// ignored | |
} | |
return result; | |
} | |
private static byte[] Generate256BitsOfRandomEntropy() | |
{ | |
#if UNITY_2021_3_OR_NEWER | |
var randomBytes = new byte[BUFFER_SIZE]; | |
using var rngCsp = new RNGCryptoServiceProvider(); | |
rngCsp.GetBytes(randomBytes); | |
return randomBytes; | |
#else | |
return RandomNumberGenerator.GetBytes(BUFFER_SIZE); | |
#endif | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thank you so much