245 lines
6.6 KiB
C#
245 lines
6.6 KiB
C#
using System;
|
|||
|
|
using System.Numerics;
|
||
|
|
using System.Security.Cryptography;
|
||
|
|
|
||
|
|
namespace Intelix.Helper.Encrypted;
|
||
|
|
|
||
|
|
public static class ChaCha20Poly1305
|
||
|
|
{
|
||
|
|
public static byte[] Decrypt(byte[] key32, byte[] iv12, byte[] ciphertext, byte[] tag, byte[] aad = null)
|
||
|
|
{
|
||
|
|
if (key32 == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException("key32");
|
||
|
|
}
|
||
|
|
if (key32.Length != 32)
|
||
|
|
{
|
||
|
|
throw new ArgumentException("Key must be 32 bytes", "key32");
|
||
|
|
}
|
||
|
|
if (iv12 == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException("iv12");
|
||
|
|
}
|
||
|
|
if (iv12.Length != 12)
|
||
|
|
{
|
||
|
|
throw new ArgumentException("IV must be 12 bytes", "iv12");
|
||
|
|
}
|
||
|
|
if (ciphertext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException("ciphertext");
|
||
|
|
}
|
||
|
|
if (tag == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException("tag");
|
||
|
|
}
|
||
|
|
if (tag.Length != 16)
|
||
|
|
{
|
||
|
|
throw new ArgumentException("Tag must be 16 bytes", "tag");
|
||
|
|
}
|
||
|
|
if (aad == null)
|
||
|
|
{
|
||
|
|
aad = Array.Empty<byte>();
|
||
|
|
}
|
||
|
|
byte[] array = ChaCha20Block(key32, 0u, iv12);
|
||
|
|
byte[] array2 = new byte[32];
|
||
|
|
Buffer.BlockCopy(array, 0, array2, 0, 32);
|
||
|
|
byte[] msg = BuildPoly1305Message(aad, ciphertext);
|
||
|
|
if (!FixedTimeEquals(Poly1305TagWithBigInteger(array2, msg), tag))
|
||
|
|
{
|
||
|
|
Array.Clear(array, 0, array.Length);
|
||
|
|
Array.Clear(array2, 0, array2.Length);
|
||
|
|
throw new CryptographicException("ChaCha20-Poly1305 authentication failed (tag mismatch).");
|
||
|
|
}
|
||
|
|
byte[] array3 = new byte[ciphertext.Length];
|
||
|
|
ChaCha20Xor(key32, 1u, iv12, ciphertext, array3);
|
||
|
|
Array.Clear(array, 0, array.Length);
|
||
|
|
Array.Clear(array2, 0, array2.Length);
|
||
|
|
return array3;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static byte[] ChaCha20Block(byte[] key32, uint counter, byte[] nonce12)
|
||
|
|
{
|
||
|
|
uint[] array = new uint[16]
|
||
|
|
{
|
||
|
|
1634760805u, 857760878u, 2036477234u, 1797285236u, 0u, 0u, 0u, 0u, 0u, 0u,
|
||
|
|
0u, 0u, 0u, 0u, 0u, 0u
|
||
|
|
};
|
||
|
|
for (int i = 0; i < 8; i++)
|
||
|
|
{
|
||
|
|
array[4 + i] = ToUInt32Little(key32, i * 4);
|
||
|
|
}
|
||
|
|
array[12] = counter;
|
||
|
|
array[13] = ToUInt32Little(nonce12, 0);
|
||
|
|
array[14] = ToUInt32Little(nonce12, 4);
|
||
|
|
array[15] = ToUInt32Little(nonce12, 8);
|
||
|
|
uint[] array2 = new uint[16];
|
||
|
|
Array.Copy(array, array2, 16);
|
||
|
|
for (int j = 0; j < 10; j++)
|
||
|
|
{
|
||
|
|
QuarterRound(ref array2[0], ref array2[4], ref array2[8], ref array2[12]);
|
||
|
|
QuarterRound(ref array2[1], ref array2[5], ref array2[9], ref array2[13]);
|
||
|
|
QuarterRound(ref array2[2], ref array2[6], ref array2[10], ref array2[14]);
|
||
|
|
QuarterRound(ref array2[3], ref array2[7], ref array2[11], ref array2[15]);
|
||
|
|
QuarterRound(ref array2[0], ref array2[5], ref array2[10], ref array2[15]);
|
||
|
|
QuarterRound(ref array2[1], ref array2[6], ref array2[11], ref array2[12]);
|
||
|
|
QuarterRound(ref array2[2], ref array2[7], ref array2[8], ref array2[13]);
|
||
|
|
QuarterRound(ref array2[3], ref array2[4], ref array2[9], ref array2[14]);
|
||
|
|
}
|
||
|
|
byte[] array3 = new byte[64];
|
||
|
|
for (int k = 0; k < 16; k++)
|
||
|
|
{
|
||
|
|
LittleEndian(array2[k] + array[k], array3, k * 4);
|
||
|
|
}
|
||
|
|
return array3;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void QuarterRound(ref uint a, ref uint b, ref uint c, ref uint d)
|
||
|
|
{
|
||
|
|
a += b;
|
||
|
|
d ^= a;
|
||
|
|
d = Rol(d, 16);
|
||
|
|
c += d;
|
||
|
|
b ^= c;
|
||
|
|
b = Rol(b, 12);
|
||
|
|
a += b;
|
||
|
|
d ^= a;
|
||
|
|
d = Rol(d, 8);
|
||
|
|
c += d;
|
||
|
|
b ^= c;
|
||
|
|
b = Rol(b, 7);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static uint Rol(uint x, int n)
|
||
|
|
{
|
||
|
|
return (x << n) | (x >> 32 - n);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static uint ToUInt32Little(byte[] bs, int off)
|
||
|
|
{
|
||
|
|
return (uint)(bs[off] | (bs[off + 1] << 8) | (bs[off + 2] << 16) | (bs[off + 3] << 24));
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void LittleEndian(uint v, byte[] outbuf, int off)
|
||
|
|
{
|
||
|
|
outbuf[off] = (byte)(v & 0xFF);
|
||
|
|
outbuf[off + 1] = (byte)((v >> 8) & 0xFF);
|
||
|
|
outbuf[off + 2] = (byte)((v >> 16) & 0xFF);
|
||
|
|
outbuf[off + 3] = (byte)((v >> 24) & 0xFF);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void ChaCha20Xor(byte[] key, uint counter, byte[] nonce, byte[] input, byte[] output)
|
||
|
|
{
|
||
|
|
if (input == null || input.Length == 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
int i = 0;
|
||
|
|
uint num = counter;
|
||
|
|
int num2;
|
||
|
|
for (; i < input.Length; i += num2)
|
||
|
|
{
|
||
|
|
byte[] array = ChaCha20Block(key, num, nonce);
|
||
|
|
num++;
|
||
|
|
num2 = Math.Min(64, input.Length - i);
|
||
|
|
for (int j = 0; j < num2; j++)
|
||
|
|
{
|
||
|
|
output[i + j] = (byte)(input[i + j] ^ array[j]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static byte[] BuildPoly1305Message(byte[] aad, byte[] ciphertext)
|
||
|
|
{
|
||
|
|
int num = ((aad != null) ? aad.Length : 0);
|
||
|
|
int num2 = ((ciphertext != null) ? ciphertext.Length : 0);
|
||
|
|
int num3 = (16 - num % 16) % 16;
|
||
|
|
int num4 = (16 - num2 % 16) % 16;
|
||
|
|
byte[] array = new byte[num + num3 + num2 + num4 + 8 + 8];
|
||
|
|
int num5 = 0;
|
||
|
|
if (num > 0)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(aad, 0, array, num5, num);
|
||
|
|
num5 += num;
|
||
|
|
}
|
||
|
|
if (num3 > 0)
|
||
|
|
{
|
||
|
|
num5 += num3;
|
||
|
|
}
|
||
|
|
if (num2 > 0)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(ciphertext, 0, array, num5, num2);
|
||
|
|
num5 += num2;
|
||
|
|
}
|
||
|
|
if (num4 > 0)
|
||
|
|
{
|
||
|
|
num5 += num4;
|
||
|
|
}
|
||
|
|
byte[] bytes = BitConverter.GetBytes((ulong)num);
|
||
|
|
byte[] bytes2 = BitConverter.GetBytes((ulong)num2);
|
||
|
|
Buffer.BlockCopy(bytes, 0, array, num5, 8);
|
||
|
|
num5 += 8;
|
||
|
|
Buffer.BlockCopy(bytes2, 0, array, num5, 8);
|
||
|
|
num5 += 8;
|
||
|
|
return array;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static byte[] Poly1305TagWithBigInteger(byte[] oneTimeKey32, byte[] msg)
|
||
|
|
{
|
||
|
|
byte[] array = new byte[16];
|
||
|
|
Buffer.BlockCopy(oneTimeKey32, 0, array, 0, 16);
|
||
|
|
array[3] &= 15;
|
||
|
|
array[7] &= 15;
|
||
|
|
array[11] &= 15;
|
||
|
|
array[15] &= 15;
|
||
|
|
array[4] &= 252;
|
||
|
|
array[8] &= 252;
|
||
|
|
array[12] &= 252;
|
||
|
|
byte[] array2 = new byte[16];
|
||
|
|
Buffer.BlockCopy(oneTimeKey32, 16, array2, 0, 16);
|
||
|
|
BigInteger bigInteger = new BigInteger(AppendZero(array));
|
||
|
|
BigInteger bigInteger2 = new BigInteger(AppendZero(array2));
|
||
|
|
BigInteger bigInteger3 = (BigInteger.One << 130) - 5;
|
||
|
|
BigInteger bigInteger4 = BigInteger.Zero;
|
||
|
|
for (int i = 0; i < msg.Length; i += 16)
|
||
|
|
{
|
||
|
|
int num = Math.Min(16, msg.Length - i);
|
||
|
|
byte[] array3 = new byte[num];
|
||
|
|
Buffer.BlockCopy(msg, i, array3, 0, num);
|
||
|
|
BigInteger bigInteger5 = new BigInteger(AppendZero(array3));
|
||
|
|
BigInteger bigInteger6 = BigInteger.One << 8 * num;
|
||
|
|
bigInteger5 += bigInteger6;
|
||
|
|
bigInteger4 += bigInteger5;
|
||
|
|
bigInteger4 = bigInteger4 * bigInteger % bigInteger3;
|
||
|
|
}
|
||
|
|
byte[] array4 = (bigInteger4 + bigInteger2).ToByteArray();
|
||
|
|
byte[] array5 = new byte[16];
|
||
|
|
for (int j = 0; j < 16 && j < array4.Length; j++)
|
||
|
|
{
|
||
|
|
array5[j] = array4[j];
|
||
|
|
}
|
||
|
|
return array5;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static byte[] AppendZero(byte[] b)
|
||
|
|
{
|
||
|
|
byte[] array = new byte[b.Length + 1];
|
||
|
|
Buffer.BlockCopy(b, 0, array, 0, b.Length);
|
||
|
|
array[array.Length - 1] = 0;
|
||
|
|
return array;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool FixedTimeEquals(byte[] a, byte[] b)
|
||
|
|
{
|
||
|
|
if (a == null || b == null || a.Length != b.Length)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
int num = 0;
|
||
|
|
for (int i = 0; i < a.Length; i++)
|
||
|
|
{
|
||
|
|
num |= a[i] ^ b[i];
|
||
|
|
}
|
||
|
|
return num == 0;
|
||
|
|
}
|
||
|
|
}
|