Skip to content

Instantly share code, notes, and snippets.

@bjornbouetsmith
Last active August 29, 2023 12:13
Show Gist options
  • Save bjornbouetsmith/a9a46931fa2741aee62b79a67b3edd53 to your computer and use it in GitHub Desktop.
Save bjornbouetsmith/a9a46931fa2741aee62b79a67b3edd53 to your computer and use it in GitHub Desktop.
// Based on https://gist.github.com/halmartin/28e013a9034be04777073bccc918cb95 (python)
// And uses Crc32 and Base32 from other github code
// Code should run on .NET Framework 4.7+
using System.Security.Cryptography;
// The 3 constants is byte definitions inside the library mentioned in the original code
// These should be HEX encoded to strings without leading 0x, i.e. "0d0a" etc.
const string AES_IV = "";// This is one of the values you need to dig out of the library;
const string HMAC_KEY = "";// This is one of the values you need to dig out of the library
const string HMAC_MSG_RAW = "";// This is one of the values you need to dig out of the library
string HMAC_MSG = $"{HMAC_MSG_RAW}{HMAC_MSG_RAW}{HMAC_MSG_RAW}{HMAC_MSG_RAW}";
void Main()
{
var type = ChassisType.Tx;
var features = Features.KvmMedia;
var serial = "YMBK013588";
var license = GenerateLicense(features, type, serial);
license.Dump();
}
byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
string MakeLicenseString(string rawLicense)
{
return string.Join("-", Enumerable.Range(0, rawLicense.Length)
.Where(x => x % 4 == 0)
.Select(x => x + 4 < rawLicense.Length ? rawLicense.Substring(x, 4) : rawLicense.Substring(x))
.ToArray());
}
string GenerateLicense(Features features, ChassisType type, string serial)
{
var featureBytes = new byte[4] { (byte)features, 0, 0, 0 };
return GenerateLicense(featureBytes, type, serial);
}
string GenerateLicense(byte[] featureBytes, ChassisType type, string serial)
{
List<byte> bytes = new List<byte>();
bytes.AddRange(Encoding.ASCII.GetBytes("iRMC"));
bytes.AddRange(featureBytes);
if (type == ChassisType.Tx)
{
bytes.AddRange(StringToByteArray("ffffff00"));
}
else
{
bytes.AddRange(StringToByteArray("ffffff05"));
}
var serialBytes = GenerateCrc32(serial);
bytes.AddRange(serialBytes);
var data = bytes.ToArray();
var licensBytes = new byte[16];
using (var sha = HMACSHA1.Create())
{
sha.Key = StringToByteArray(HMAC_KEY);
var aesKey = sha.ComputeHash(StringToByteArray(HMAC_MSG)).Take(16).ToArray();
using (var aes = AesManaged.Create())
{
aes.Key = aesKey;
aes.IV = StringToByteArray(AES_IV);
aes.Mode = CipherMode.CBC;
using (var encryptor = aes.CreateEncryptor())
{
encryptor.TransformBlock(data, 0, data.Length, licensBytes, 0);
var licenseString = Base32.ToBase32String(licensBytes);
return MakeLicenseString(licenseString);
}
}
}
}
byte[] GenerateCrc32(string value)
{
var crc = new Crc32();
var bytes = Encoding.ASCII.GetBytes(value);
return crc.ComputeHash(bytes);
}
public enum Features
{
Kvm = 0x01,
KvmMedia = 0x03,
KvmMediaElcm = 0x0f
}
public enum ChassisType
{
Tx,
Rx
}
///https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/Security/Cryptography/Crc32.cs
public sealed class Crc32 : HashAlgorithm
{
public const UInt32 DefaultPolynomial = 0xedb88320u;
public const UInt32 DefaultSeed = 0xffffffffu;
static UInt32[] defaultTable;
readonly UInt32 seed;
readonly UInt32[] table;
UInt32 hash;
/// <summary>
/// Create a new <see cref="Crc32"/> with a <see cref="DefaultPolynomial"/> and <see cref="DefaultSeed"/>.
/// </summary>
public Crc32()
: this(DefaultPolynomial, DefaultSeed)
{
}
/// <summary>
/// Create a new <see cref="Crc32"/> with a supplied polynomial and see.
/// </summary>
/// <param name="polynomial">The polynomial to use in calculating.</param>
/// <param name="polynomial">The initial seed to start from.</param>
public Crc32(UInt32 polynomial, UInt32 seed)
{
if (!BitConverter.IsLittleEndian)
throw new PlatformNotSupportedException("Not supported on Big Endian processors");
table = InitializeTable(polynomial);
this.seed = hash = seed;
}
/// <inheritdoc/>
public override void Initialize()
{
hash = seed;
}
/// <inheritdoc/>
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
hash = CalculateHash(table, hash, array, ibStart, cbSize);
}
/// <inheritdoc/>
protected override byte[] HashFinal()
{
var hashBuffer = UInt32ToBigEndianBytes(~hash);
HashValue = hashBuffer;
return hashBuffer;
}
/// <inheritdoc/>
public override int HashSize => 32;
/// <summary>
/// Calculate the <see cref="Crc32"/> for a given <paramref name="buffer"/> with the
/// <see cref="DefaultSeed"/> and <see cref="DefaultPolynomial"/>.
/// </summary>
/// <param name="buffer">The <see cref="byte[]"/> buffer to calcuate a CRC32 for.</param>
/// <returns>The CRC32 for the buffer.</returns>
public static UInt32 Compute(byte[] buffer) => Compute(DefaultSeed, buffer);
/// <summary>
/// Calculate the <see cref="Crc32"/> for a given <paramref name="buffer"/> with a
/// specified <paramref name="seed"/> and <see cref="DefaultPolynomial"/>.
/// </summary>
/// <param name="seed">The initial seed to start from.</param>
/// <param name="buffer">The <see cref="byte[]"/> buffer to calcuate a CRC32 for.</param>
/// <returns>The CRC32 for the buffer.</returns>
public static UInt32 Compute(UInt32 seed, byte[] buffer) => Compute(DefaultPolynomial, seed, buffer);
/// <summary>
/// Calculate the <see cref="Crc32"/> for a given <paramref name="buffer"/> with a
/// specified <paramref name="seed"/> and <paramref name="polynomial"/>.
/// </summary>
/// <param name="seed">The initial seed to start from.</param>
/// <param name="buffer">The <see cref="byte[]"/> buffer to calcuate a CRC32 for.</param>
/// <returns>The CRC32 for the buffer.</returns>
public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) =>
~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
/// <summary>
/// Initialize a CRC32 calculation table for a given polynomial.
/// </summary>
/// <param name="polynomial">The polynomial to calculate a table for.</param>
/// <returns>A <see cref="UInt32[]"/> table to be used in calculating a CRC32.</returns>
static UInt32[] InitializeTable(UInt32 polynomial)
{
if (polynomial == DefaultPolynomial && defaultTable != null)
return defaultTable;
var createTable = new UInt32[256];
for (var i = 0; i < 256; i++)
{
var entry = (UInt32)i;
for (var j = 0; j < 8; j++)
if ((entry & 1) == 1)
entry = (entry >> 1) ^ polynomial;
else
entry >>= 1;
createTable[i] = entry;
}
if (polynomial == DefaultPolynomial)
defaultTable = createTable;
return createTable;
}
/// <summary>
/// Calculate an inverted CRC32 for a given <paramref name="buffer"/> using a polynomial-derived <paramref name="table"/>.
/// </summary>
/// <param name="table">The polynomial-derived table such as from <see cref="InitializeTable(UInt32)"/>.</param>
/// <param name="seed">The initial seed to start from.</param>
/// <param name="buffer">The <see cref="IList{byte}"/> buffer to calculate the CRC32 from.</param>
/// <param name="start">What position within the <paramref name="buffer"/> to start calculating from.</param>
/// <param name="size">How many bytes within the <paramref name="buffer"/> to read in calculating the CRC32.</param>
/// <returns>The bit-inverted CRC32.</returns>
/// <remarks>This hash is bit-inverted. Use other methods in this class or <see langword="~"/> the result from this method.</remarks>
static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList<byte> buffer, int start, int size)
{
var hash = seed;
for (var i = start; i < start + size; i++)
hash = (hash >> 8) ^ table[buffer[i] ^ hash & 0xff];
return hash;
}
/// <summary>
/// Convert a <see cref="UInt32"/> to a <see cref="byte[]"/> taking care
/// to reverse the bytes on little endian processors.
/// </summary>
/// <param name="uint32">The <see cref="UInt32"/> to convert.</param>
/// <returns>The <see cref="byte[]"/> containing the converted bytes.</returns>
static byte[] UInt32ToBigEndianBytes(UInt32 uint32)
{
var result = BitConverter.GetBytes(uint32);
if (BitConverter.IsLittleEndian)
Array.Reverse(result);
return result;
}
}
///https://gist.github.com/erdomke/9335c394c5cc65404c4cf9aceab04143
public static class Base32
{
private static readonly char[] _digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray();
private const int _mask = 31;
private const int _shift = 5;
private static int CharToInt(char c)
{
switch (c)
{
case 'A': return 0;
case 'B': return 1;
case 'C': return 2;
case 'D': return 3;
case 'E': return 4;
case 'F': return 5;
case 'G': return 6;
case 'H': return 7;
case 'I': return 8;
case 'J': return 9;
case 'K': return 10;
case 'L': return 11;
case 'M': return 12;
case 'N': return 13;
case 'O': return 14;
case 'P': return 15;
case 'Q': return 16;
case 'R': return 17;
case 'S': return 18;
case 'T': return 19;
case 'U': return 20;
case 'V': return 21;
case 'W': return 22;
case 'X': return 23;
case 'Y': return 24;
case 'Z': return 25;
case '2': return 26;
case '3': return 27;
case '4': return 28;
case '5': return 29;
case '6': return 30;
case '7': return 31;
}
return -1;
}
public static byte[] FromBase32String(string encoded)
{
if (encoded == null)
throw new ArgumentNullException(nameof(encoded));
// Remove whitespace and padding. Note: the padding is used as hint
// to determine how many bits to decode from the last incomplete chunk
// Also, canonicalize to all upper case
encoded = encoded.Trim().TrimEnd('=').ToUpper();
if (encoded.Length == 0)
return new byte[0];
var outLength = encoded.Length * _shift / 8;
var result = new byte[outLength];
var buffer = 0;
var next = 0;
var bitsLeft = 0;
var charValue = 0;
foreach (var c in encoded)
{
charValue = CharToInt(c);
if (charValue < 0)
throw new FormatException("Illegal character: `" + c + "`");
buffer <<= _shift;
buffer |= charValue & _mask;
bitsLeft += _shift;
if (bitsLeft >= 8)
{
result[next++] = (byte)(buffer >> (bitsLeft - 8));
bitsLeft -= 8;
}
}
return result;
}
public static string ToBase32String(byte[] data, bool padOutput = false)
{
return ToBase32String(data, 0, data.Length, padOutput);
}
public static string ToBase32String(byte[] data, int offset, int length, bool padOutput = false)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset));
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length));
if ((offset + length) > data.Length)
throw new ArgumentOutOfRangeException();
if (length == 0)
return "";
// SHIFT is the number of bits per output character, so the length of the
// output is the length of the input multiplied by 8/SHIFT, rounded up.
// The computation below will fail, so don't do it.
if (length >= (1 << 28))
throw new ArgumentOutOfRangeException(nameof(data));
var outputLength = (length * 8 + _shift - 1) / _shift;
var result = new StringBuilder(outputLength);
var last = offset + length;
int buffer = data[offset++];
var bitsLeft = 8;
while (bitsLeft > 0 || offset < last)
{
if (bitsLeft < _shift)
{
if (offset < last)
{
buffer <<= 8;
buffer |= (data[offset++] & 0xff);
bitsLeft += 8;
}
else
{
int pad = _shift - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
int index = _mask & (buffer >> (bitsLeft - _shift));
bitsLeft -= _shift;
result.Append(_digits[index]);
}
if (padOutput)
{
int padding = 8 - (result.Length % 8);
if (padding > 0) result.Append('=', padding == 8 ? 0 : padding);
}
return result.ToString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment