/Crypto

:books: Provides a handy way to hash data using Adler32, CRC (8-bit to 82-bit, and customizable without bit restriction), MD5, SHA-1 and SHA-2 (256-bit, 384-bit and 512-bit), including HMAC keyed hashing for some types. Functions for encrypting and decrypting data with Rijndael (128-bit, 192-bit and 256-bit) are also offered

Primary LanguageC#MIT LicenseMIT

.NET Core Cross-platform License

Build+Test Commits Source

NuGet NuGet Website Mirror

Roydl.Crypto

The idea was to create a simple way to hash any type of data. So, there are generic extensions for almost any type. A handful algorithms are currently offered, but more will be added over time. Some algorithms are performance optimized and probably more powerful than any other pure C# library of its kind.

Install:

$ dotnet add package Roydl.Crypto

Checksum Algorithms:

Name Bit Width Algorithm Type Hardware Support
Adler-32 32-bit Standard Cyclic SSE2 CPU (limited)
CRC from 8-bit
to 82-bit
88 presets available + customizable Cyclic iSCSI @ SSE4.2 CPU
iSCSI+PKZip @ ARM
MD5 128-bit Built-in + HMAC keyed-hash support Cryptographic ✖️
SHA-1 160-bit Built-in + HMAC keyed-hash support Cryptographic ✖️
SHA-2 256-bit
384-bit
512-bit
Built-in + HMAC keyed-hash support Cryptographic ✖️

Checksum Performance:

Especially for Alder and CRC, the performance in software mode should be much better than with any other pure C# library, but similar to libraries that work with C/C++ imports. However, I couldn't find any other library with hardware support, not even with imports.

Algorithm Library Mode Speed
Adler-32 This Software 1566,2 MiB/s
Adler-32 This Hardware 2099,4 MiB/s
CRC-32 Crc32.NET Software 1602,7 MiB/s
CRC-32 This Software 2040,9 MiB/s
CRC-32 This Hardware 8393.9 MiB/s
SHA-256 Built-in Software 1846,7 MiB/s

In the test case, a 64 KiB packet with random bytes is generated, which is sent over and over again within 9 seconds by the function that computes the hash. During this process, it is determined several times how much data could be hashed within 1 second. It seems like 9 seconds is the sweet spot. Increasing this time does not provide more accurate results. However, repetitions offer better results by saving all results, determining the maximum and minimum values and thus identifying fluctuations. The most accurate result seems to be the average of 20 repetitions. You can find the test case here.

Usage:

The GetChecksum extension method retrieves a string representation of the computed hash.

The value can be almost anything. bool, sbyte, byte, short, ushort, char, int, uint, long, ulong, Half, float, double, decimal, Enum, IntPtr, UIntPtr, Vector{T}, Vector2, Vector3, Vector4, Matrix3x2, Matrix4x4, Plane, Quaternion, Complex, BigInteger, DateTime, DateTimeOffset, TimeSpan, Guid, Rune, Stream, StreamReader, FileInfo, any IEnumerable{T} byte sequence, i.e. Array, or any IEnumerable{T} char sequence, i.e. string, any many more.

Not every type makes sense, but is supported anyway.

string hash = value.GetChecksum(ChecksumAlgo.Sha1);
Console.WriteLine(hash);

// Output:
// 12a5ba5baa1664f73e6279f23354bd90c8981a81

However, a string containing a file path has an additional method.

string hash = value.GetFileChecksum(); // SHA-256 is used when `ChecksumAlgo` is undefined

The GetCipher extension method retrieves an unsigned 64-bit integer representation of the computed hash. It follows the same rules outlined earlier. This can be useful with cyclic computed hashes.

ulong hash = value.GetCipher(ChecksumAlgo.Crc64);

Note that HMAC keyed-hashing is only supported for cryptographic algorithms via instances by setting a secret key.

Sha512 instance = Sha512.Create(new byte[128] { /* some bytes */ });

The ComputeHash methods uses the secret key until DestroySecretKey is called.

instance.ComputeHash(value);

An instance provides a computed hash in several variants.

ReadOnlySpan<byte> rawHash = instance.RawHash;
BigInteger cipher = instance.CipherHash; // The integral type depends on the bit length, e.g. CRC-32 is `UInt32`
string lowercase = instance.Hash;
string uppercase = instance.ToString(true);

Casting is also supported to get a hash.

byte[] copyOfRawHash = (byte[])instance;
ulong cipher = (ulong)instance; // Numeric conversions are unchecked conversions of the `instance.CipherHash` field
string lowercase = (string)instance;

Instances also provide equality operators for quick comparison.

bool equ = (instance1 == instance2);
bool neq = (instance1 != instance2);

CRC customization:

If you need a different CRC algorithm, you can easily create your own variation.

This is an example for CRC-32/POSIX, but it should support many others from 8-bit to almost infinite bits.

const int width = 32;
const uint check = 0x765e7680u;
const uint poly = 0x04c11db7u;
const uint init = 0x00000000u;
const bool refIn = false;
const bool refOut = false;
const uint xorOut = 0xffffffffu;
const uint mask = 0xffffffffu;
const bool skipValidation = false;

Sets a new CrcConfig with the constants from above. The data are automatically validated with the given check.

var cfg = new CrcConfig32(width, check, poly, init, refIn, refOut, xorOut, mask, skipValidation);

Compute the hash directly via the configuration structure.

cfg.ComputeHash(stream, out uint cipher);

Or load it into the CRC class which has more features, and compute the hash code from there.

The value can be from type Stream, byte[], string, FileInfo, or a string containing a file path.

var crc = new Crc<uint>(config);
crc.ComputeHash(value);

As mentioned earlier, instances offer computed hashes in several variants. It follows the same rules that have already been explained above.

ReadOnlyMemory<byte> rawHash = crc.RawHash;
uint cipher = crc.CipherHash;
string lowercase = crc.Hash;

Check out the CRC configuration manager to see more examples.


Other included algorithm:

Name Algorithm
Rijndael 128 bit block size; optional: 128, 192 or 256 bit key size, cipher and padding modes

Usage:

byte[] password = new byte[] { /* some bytes */ };
byte[] salt = new byte[] { /* some bytes */ };
using var aes = new Rijndael(password, salt, 1000, SymmetricKeySize.Large);
aes.Encrypt(streamToEncrypt, encryptedStream);
aes.Decrypt(streamToDecrypt, decryptedStream);

Would you like to help?

  • Star this Project ⭐ and show me that this project interests you 🤗
  • Open an Issue ☕ to give me your feedback and tell me your ideas and wishes for the future 😎
  • Open a Ticket 📫 if you don't have a GitHub account, you can contact me directly on my website 😉
  • Donate by PayPal 💸 to buy me some cakes 🍰

Please note that I cannot fix bugs that are unknown to me. So do yourself and me the favor and get in touch with me. 🤕