AsconAead128
Ascon-AEAD128 was introduced as part of the NIST Lightweight Cryptography
competition and descriLEd in the NIST Special Publication SP 800-232 (Initial
Public Draft).
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Utilities;
using System;
using System.Runtime.CompilerServices;
namespace Org.BouncyCastle.Crypto.Modes
{
public sealed class AsconAead128 : IAeadCipher
{
private enum State
{
Uninitialized,
EncInit,
EncAad,
EncData,
EncFinal,
DecInit,
DecAad,
DecData,
DecFinal
}
private const ulong AsconIV = 17594342703105;
private const int BufSizeDecrypt = 32;
private const int CryptoABytes = 16;
private const int CryptoKeyBytes = 16;
private const int Rate = 16;
private readonly byte[] m_buf;
private byte[] m_initialAssociatedText;
private byte[] m_mac;
private ulong K0;
private ulong K1;
private ulong N0;
private ulong N1;
private ulong S0;
private ulong S1;
private ulong S2;
private ulong S3;
private ulong S4;
private State m_state;
private int m_bufPos;
public string AlgorithmName => "Ascon-AEAD128";
public AsconAead128()
{
m_buf = new byte[32];
}
public int GetKeyBytesSize()
{
return 16;
}
public int GetIVBytesSize()
{
return 16;
}
public void Init(bool forEncryption, ICipherParameters parameters)
{
AeadParameters aeadParameters = parameters as AeadParameters;
KeyParameter keyParameter;
ReadOnlySpan<byte> bs;
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler;
if (aeadParameters != null) {
keyParameter = aeadParameters.Key;
bs = aeadParameters.Nonce;
m_initialAssociatedText = aeadParameters.GetAssociatedText();
int macSize = aeadParameters.MacSize;
if (macSize != 128) {
defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(28, 1);
defaultInterpolatedStringHandler.AppendLiteral("Invalid value for MAC size: ");
defaultInterpolatedStringHandler.AppendFormatted(macSize);
throw new ArgumentException(defaultInterpolatedStringHandler.ToStringAndClear());
}
} else {
ParametersWithIV parametersWithIV = parameters as ParametersWithIV;
if (parametersWithIV == null)
throw new ArgumentException("invalid parameters passed to " + AlgorithmName);
keyParameter = (parametersWithIV.Parameters as KeyParameter);
bs = parametersWithIV.IV;
m_initialAssociatedText = null;
}
if (keyParameter == null)
throw new ArgumentException(AlgorithmName + " Init parameters must include a key");
if (bs.Length != 16) {
defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(30, 2);
defaultInterpolatedStringHandler.AppendFormatted(AlgorithmName);
defaultInterpolatedStringHandler.AppendLiteral(" requires exactly ");
defaultInterpolatedStringHandler.AppendFormatted(16);
defaultInterpolatedStringHandler.AppendLiteral(" bytes of IV");
throw new ArgumentException(defaultInterpolatedStringHandler.ToStringAndClear());
}
ReadOnlySpan<byte> key = keyParameter.Key;
if (key.Length != 16) {
defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(24, 2);
defaultInterpolatedStringHandler.AppendFormatted(AlgorithmName);
defaultInterpolatedStringHandler.AppendLiteral(" key must be ");
defaultInterpolatedStringHandler.AppendFormatted(16);
defaultInterpolatedStringHandler.AppendLiteral(" bytes long");
throw new ArgumentException(defaultInterpolatedStringHandler.ToStringAndClear());
}
K0 = Pack.LE_To_UInt64(key, 0);
K1 = Pack.LE_To_UInt64(key, 8);
N0 = Pack.LE_To_UInt64(bs, 0);
N1 = Pack.LE_To_UInt64(bs, 8);
m_state = (forEncryption ? State.EncInit : State.DecInit);
Reset(true);
}
public void ProcessAadByte(byte input)
{
CheckAad();
m_buf[m_bufPos] = input;
if (++m_bufPos == 16) {
ProcessBufferAad(m_buf);
m_bufPos = 0;
}
}
public void ProcessAadBytes(byte[] inBytes, int inOff, int len)
{
Check.DataLength(inBytes, inOff, len, "input buffer too short");
ProcessAadBytes(inBytes.AsSpan(inOff, len));
}
public void ProcessAadBytes(ReadOnlySpan<byte> input)
{
if (!input.IsEmpty) {
CheckAad();
if (m_bufPos > 0) {
int num = 16 - m_bufPos;
if (input.Length < num) {
input.CopyTo(m_buf.AsSpan(m_bufPos));
m_bufPos += input.Length;
return;
}
input.Slice(0, num).CopyTo(m_buf.AsSpan(m_bufPos));
int num2 = num;
input = input.Slice(num2, input.Length - num2);
ProcessBufferAad(m_buf);
}
while (input.Length >= 16) {
ProcessBufferAad(input);
input = input.Slice(16, input.Length - 16);
}
input.CopyTo(m_buf);
m_bufPos = input.Length;
}
}
public int ProcessByte(byte input, byte[] outBytes, int outOff)
{
return ProcessByte(input, Spans.FromNullable(outBytes, outOff));
}
public unsafe int ProcessByte(byte input, Span<byte> output)
{
byte* intPtr = stackalloc byte[1];
*intPtr = input;
Span<byte> span = new Span<byte>(intPtr, 1);
return ProcessBytes(span, output);
}
public int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
{
Check.DataLength(inBytes, inOff, len, "input buffer too short");
return ProcessBytes(inBytes.AsSpan(inOff, len), Spans.FromNullable(outBytes, outOff));
}
public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
{
bool num = CheckData();
int num2 = 0;
ReadOnlySpan<byte> readOnlySpan;
if (num) {
if (m_bufPos > 0) {
int num3 = 16 - m_bufPos;
if (input.Length < num3) {
input.CopyTo(m_buf.AsSpan(m_bufPos));
m_bufPos += input.Length;
return 0;
}
readOnlySpan = input.Slice(0, num3);
readOnlySpan.CopyTo(m_buf.AsSpan(m_bufPos));
int num4 = num3;
input = input.Slice(num4, input.Length - num4);
ProcessBufferEncrypt(m_buf, output);
num2 = 16;
}
while (input.Length >= 16) {
ReadOnlySpan<byte> buffer = input;
int num4 = num2;
ProcessBufferEncrypt(buffer, output.Slice(num4, output.Length - num4));
input = input.Slice(16, input.Length - 16);
num2 += 16;
}
} else {
int num5 = 32 - m_bufPos;
if (input.Length < num5) {
input.CopyTo(m_buf.AsSpan(m_bufPos));
m_bufPos += input.Length;
return 0;
}
int num4;
if (m_bufPos >= 16) {
ReadOnlySpan<byte> buffer2 = m_buf;
num4 = num2;
ProcessBufferDecrypt(buffer2, output.Slice(num4, output.Length - num4));
num2 += 16;
m_bufPos -= 16;
m_buf.AsSpan(0, m_bufPos).CopyFrom(m_buf.AsSpan(16));
num5 += 16;
if (input.Length < num5) {
input.CopyTo(m_buf.AsSpan(m_bufPos));
m_bufPos += input.Length;
return num2;
}
}
num5 = 16 - m_bufPos;
readOnlySpan = input.Slice(0, num5);
readOnlySpan.CopyTo(m_buf.AsSpan(m_bufPos));
num4 = num5;
input = input.Slice(num4, input.Length - num4);
ReadOnlySpan<byte> buffer3 = m_buf;
num4 = num2;
ProcessBufferDecrypt(buffer3, output.Slice(num4, output.Length - num4));
num2 += 16;
while (input.Length >= 32) {
ReadOnlySpan<byte> buffer4 = input;
num4 = num2;
ProcessBufferDecrypt(buffer4, output.Slice(num4, output.Length - num4));
input = input.Slice(16, input.Length - 16);
num2 += 16;
}
}
input.CopyTo(m_buf);
m_bufPos = input.Length;
return num2;
}
public int DoFinal(byte[] outBytes, int outOff)
{
return DoFinal(outBytes.AsSpan(outOff));
}
public int DoFinal(Span<byte> output)
{
int num;
if (CheckData()) {
num = m_bufPos + 16;
Check.OutputLength(output, num, "output buffer too short");
ProcessFinalEncrypt(m_buf.AsSpan(0, m_bufPos), output);
m_mac = new byte[16];
Pack.UInt64_To_LE(S3, m_mac.AsSpan());
Pack.UInt64_To_LE(S4, m_mac.AsSpan(8));
byte[] mac = m_mac;
int bufPos = m_bufPos;
mac.CopyTo(output.Slice(bufPos, output.Length - bufPos));
Reset(false);
} else {
if (m_bufPos < 16)
throw new InvalidCipherTextException("data too short");
m_bufPos -= 16;
num = m_bufPos;
Check.OutputLength(output, num, "output buffer too short");
ProcessFinalDecrypt(m_buf.AsSpan(0, m_bufPos), output);
S3 ^= Pack.LE_To_UInt64(m_buf.AsSpan(m_bufPos));
S4 ^= Pack.LE_To_UInt64(m_buf.AsSpan(m_bufPos + 8));
if ((S3 | S4) != 0)
throw new InvalidCipherTextException("mac check in " + AlgorithmName + " failed");
Reset(true);
}
return num;
}
public byte[] GetMac()
{
return m_mac;
}
public int GetUpdateOutputSize(int len)
{
int num = System.Math.Max(0, len);
switch (m_state) {
case State.DecInit:
case State.DecAad:
num = System.Math.Max(0, num - 16);
break;
case State.DecData:
case State.DecFinal:
num = System.Math.Max(0, num + m_bufPos - 16);
break;
case State.EncData:
case State.EncFinal:
num += m_bufPos;
break;
}
return num - num % 16;
}
public int GetOutputSize(int len)
{
int num = System.Math.Max(0, len);
switch (m_state) {
case State.DecInit:
case State.DecAad:
return System.Math.Max(0, num - 16);
case State.DecData:
case State.DecFinal:
return System.Math.Max(0, num + m_bufPos - 16);
case State.EncData:
case State.EncFinal:
return num + m_bufPos + 16;
default:
return num + 16;
}
}
public void Reset()
{
Reset(true);
}
private void CheckAad()
{
switch (m_state) {
case State.EncAad:
case State.DecAad:
break;
case State.DecInit:
m_state = State.DecAad;
break;
case State.EncInit:
m_state = State.EncAad;
break;
case State.EncFinal:
throw new InvalidOperationException(AlgorithmName + " cannot be reused for encryption");
default:
throw new InvalidOperationException(AlgorithmName + " needs to be initialized");
}
}
private bool CheckData()
{
switch (m_state) {
case State.DecInit:
case State.DecAad:
FinishAad(State.DecData);
return false;
case State.EncInit:
case State.EncAad:
FinishAad(State.EncData);
return true;
case State.DecData:
return false;
case State.EncData:
return true;
case State.EncFinal:
throw new InvalidOperationException(AlgorithmName + " cannot be reused for encryption");
default:
throw new InvalidOperationException(AlgorithmName + " needs to be initialized");
}
}
private void FinishAad(State nextState)
{
State state = m_state;
if (state == State.EncAad || state == State.DecAad) {
m_buf[m_bufPos] = 1;
if (m_bufPos >= 8) {
S0 ^= Pack.LE_To_UInt64(m_buf, 0);
S1 ^= (Pack.LE_To_UInt64(m_buf, 8) & (ulong.MaxValue >> 56 - (m_bufPos - 8 << 3)));
} else
S0 ^= (Pack.LE_To_UInt64(m_buf, 0) & (ulong.MaxValue >> 56 - (m_bufPos << 3)));
P8();
}
S4 ^= 9223372036854775808;
m_bufPos = 0;
m_state = nextState;
}
private void FinishData(State nextState)
{
S2 ^= K0;
S3 ^= K1;
P12();
S3 ^= K0;
S4 ^= K1;
m_state = nextState;
}
private void InitState()
{
S0 = 17594342703105;
S1 = K0;
S2 = K1;
S3 = N0;
S4 = N1;
P12();
S3 ^= K0;
S4 ^= K1;
}
private void P8()
{
Round(180);
Round(165);
Round(150);
Round(135);
Round(120);
Round(105);
Round(90);
Round(75);
}
private void P12()
{
Round(240);
Round(225);
Round(210);
Round(195);
Round(180);
Round(165);
Round(150);
Round(135);
Round(120);
Round(105);
Round(90);
Round(75);
}
private void ProcessBufferAad(ReadOnlySpan<byte> buffer)
{
S0 ^= Pack.LE_To_UInt64(buffer);
S1 ^= Pack.LE_To_UInt64(buffer.Slice(8, buffer.Length - 8));
P8();
}
private void ProcessBufferDecrypt(ReadOnlySpan<byte> buffer, Span<byte> output)
{
Check.OutputLength(output, 16, "output buffer too short");
ulong num = Pack.LE_To_UInt64(buffer);
Pack.UInt64_To_LE(S0 ^ num, output);
S0 = num;
ulong num2 = Pack.LE_To_UInt64(buffer.Slice(8, buffer.Length - 8));
Pack.UInt64_To_LE(S1 ^ num2, output.Slice(8, output.Length - 8));
S1 = num2;
P8();
}
private void ProcessBufferEncrypt(ReadOnlySpan<byte> buffer, Span<byte> output)
{
Check.OutputLength(output, 16, "output buffer too short");
S0 ^= Pack.LE_To_UInt64(buffer);
Pack.UInt64_To_LE(S0, output);
S1 ^= Pack.LE_To_UInt64(buffer.Slice(8, buffer.Length - 8));
Pack.UInt64_To_LE(S1, output.Slice(8, output.Length - 8));
P8();
}
private void ProcessFinalDecrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length >= 8) {
ulong num = Pack.LE_To_UInt64(input);
Pack.UInt64_To_LE(S0 ^ num, output);
S0 = num;
input = input.Slice(8, input.Length - 8);
if (!input.IsEmpty)
ProcessFinalDecrypt64(input, output.Slice(8, output.Length - 8), ref S1);
S1 ^= Pad(input.Length);
} else {
if (!input.IsEmpty)
ProcessFinalDecrypt64(input, output, ref S0);
S0 ^= Pad(input.Length);
}
FinishData(State.DecFinal);
}
private static void ProcessFinalDecrypt64(ReadOnlySpan<byte> input, Span<byte> output, ref ulong s)
{
int length = input.Length;
ulong num = Pack.LE_To_UInt64_Low(input);
Pack.UInt64_To_LE_Low(s ^ num, output.Slice(0, length));
s &= (ulong)(-1 << (length << 3));
s ^= num;
}
private void ProcessFinalEncrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length >= 8) {
S0 ^= Pack.LE_To_UInt64(input);
Pack.UInt64_To_LE(S0, output);
input = input.Slice(8, input.Length - 8);
if (!input.IsEmpty)
ProcessFinalEncrypt64(input, output.Slice(8, output.Length - 8), ref S1);
S1 ^= Pad(input.Length);
} else {
if (!input.IsEmpty)
ProcessFinalEncrypt64(input, output, ref S0);
S0 ^= Pad(input.Length);
}
FinishData(State.EncFinal);
}
private static void ProcessFinalEncrypt64(ReadOnlySpan<byte> input, Span<byte> output, ref ulong s)
{
int length = input.Length;
s ^= Pack.LE_To_UInt64_Low(input);
Pack.UInt64_To_LE_Low(s, output.Slice(0, length));
}
private void Reset(bool clearMac)
{
if (clearMac)
m_mac = null;
Array.Clear(m_buf, 0, m_buf.Length);
m_bufPos = 0;
switch (m_state) {
case State.DecAad:
case State.DecData:
case State.DecFinal:
m_state = State.DecInit;
break;
case State.EncAad:
case State.EncData:
case State.EncFinal:
m_state = State.EncFinal;
return;
default:
throw new InvalidOperationException(AlgorithmName + " needs to be initialized");
case State.EncInit:
case State.DecInit:
break;
}
InitState();
if (m_initialAssociatedText != null)
ProcessAadBytes(m_initialAssociatedText);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Round(ulong c)
{
ulong num = S2 ^ c;
ulong num2 = S0 ^ S1 ^ num ^ S3 ^ (S1 & (S0 ^ num ^ S4));
ulong num3 = S0 ^ num ^ S3 ^ S4 ^ ((S1 ^ num) & (S1 ^ S3));
ulong num4 = S1 ^ num ^ S4 ^ (S3 & S4);
ulong num5 = S0 ^ S1 ^ num ^ (~S0 & (S3 ^ S4));
ulong num6 = S1 ^ S3 ^ S4 ^ ((S0 ^ S4) & S1);
S0 = (num2 ^ Longs.RotateRight(num2, 19) ^ Longs.RotateRight(num2, 28));
S1 = (num3 ^ Longs.RotateRight(num3, 39) ^ Longs.RotateRight(num3, 61));
S2 = ~(num4 ^ Longs.RotateRight(num4, 1) ^ Longs.RotateRight(num4, 6));
S3 = (num5 ^ Longs.RotateRight(num5, 10) ^ Longs.RotateRight(num5, 17));
S4 = (num6 ^ Longs.RotateRight(num6, 7) ^ Longs.RotateRight(num6, 41));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Pad(int i)
{
return (ulong)(1 << (i << 3));
}
}
}