Argon2BytesGenerator
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Math.Raw;
using Org.BouncyCastle.Utilities;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Org.BouncyCastle.Crypto.Generators
{
public sealed class Argon2BytesGenerator
{
private sealed class FillBlock
{
private readonly Block R = new Block();
private readonly Block Z = new Block();
internal readonly Block addressBlock = new Block();
internal readonly Block inputBlock = new Block();
internal void ApplyBlake()
{
ulong[] v = Z.v;
for (int i = 0; i < 128; i += 16) {
GB(ref v[i], ref v[i + 4], ref v[i + 8], ref v[i + 12]);
GB(ref v[i + 1], ref v[i + 5], ref v[i + 9], ref v[i + 13]);
GB(ref v[i + 2], ref v[i + 6], ref v[i + 10], ref v[i + 14]);
GB(ref v[i + 3], ref v[i + 7], ref v[i + 11], ref v[i + 15]);
GB(ref v[i], ref v[i + 5], ref v[i + 10], ref v[i + 15]);
GB(ref v[i + 1], ref v[i + 6], ref v[i + 11], ref v[i + 12]);
GB(ref v[i + 2], ref v[i + 7], ref v[i + 8], ref v[i + 13]);
GB(ref v[i + 3], ref v[i + 4], ref v[i + 9], ref v[i + 14]);
}
for (int j = 0; j < 16; j += 2) {
GB(ref v[j], ref v[j + 32], ref v[j + 64], ref v[j + 96]);
GB(ref v[j + 1], ref v[j + 33], ref v[j + 65], ref v[j + 97]);
GB(ref v[j + 16], ref v[j + 48], ref v[j + 80], ref v[j + 112]);
GB(ref v[j + 17], ref v[j + 49], ref v[j + 81], ref v[j + 113]);
GB(ref v[j], ref v[j + 33], ref v[j + 80], ref v[j + 113]);
GB(ref v[j + 1], ref v[j + 48], ref v[j + 81], ref v[j + 96]);
GB(ref v[j + 16], ref v[j + 49], ref v[j + 64], ref v[j + 97]);
GB(ref v[j + 17], ref v[j + 32], ref v[j + 65], ref v[j + 112]);
}
}
internal void Fill(Block Y, Block currentBlock)
{
Z.CopyBlock(Y);
ApplyBlake();
currentBlock.Xor(Y, Z);
}
internal void Fill(Block X, Block Y, Block currentBlock)
{
R.Xor(X, Y);
Z.CopyBlock(R);
ApplyBlake();
currentBlock.Xor(R, Z);
}
internal void FillBlockWithXor(Block X, Block Y, Block currentBlock)
{
R.Xor(X, Y);
Z.CopyBlock(R);
ApplyBlake();
currentBlock.XorWith(R, Z);
}
}
private sealed class Block
{
private const int Size = 128;
internal readonly ulong[] v;
internal Block()
{
v = new ulong[128];
}
internal void FromBytes(byte[] input)
{
if (input.Length < 1024)
throw new ArgumentException("input shorter than blocksize");
Pack.LE_To_UInt64(input, 0, v);
}
internal void ToBytes(byte[] output)
{
if (output.Length < 1024)
throw new ArgumentException("output shorter than blocksize");
Pack.UInt64_To_LE(v, output, 0);
}
internal void CopyBlock(Block other)
{
Array.Copy(other.v, 0, v, 0, 128);
}
internal void Xor(Block b1, Block b2)
{
Nat.Xor64(128, b1.v, b2.v, v);
}
internal void XorWith(Block b1)
{
Nat.XorTo64(128, b1.v, v);
}
internal void XorWith(Block b1, Block b2)
{
Nat.XorBothTo64(128, b1.v, b2.v, v);
}
internal Block Clear()
{
Arrays.Fill(v, 0);
return this;
}
}
private sealed class Position
{
internal readonly int pass;
internal readonly int slice;
internal readonly int lane;
internal Position(int pass, int slice, int lane)
{
this.pass = pass;
this.slice = slice;
this.lane = lane;
}
}
private const int Argon2BlockSize = 1024;
private const int Argon2QwordsInBlock = 128;
private const int Argon2AddressesInBlock = 128;
private const int Argon2PrehashDigestLength = 64;
private const int Argon2PrehashSeedLength = 72;
private const int Argon2SyncPoints = 4;
private const int MinParallelism = 1;
private const int MaxParallelism = 16777215;
private const int MinOutlen = 4;
private const int MinIterations = 1;
private readonly byte[] ZeroBytes = new byte[4];
private readonly TaskFactory m_taskFactory;
private Argon2Parameters parameters;
private Block[] memory;
private int segmentLength;
private int laneLength;
public Argon2BytesGenerator()
: this(null)
{
}
public Argon2BytesGenerator(TaskFactory taskFactory)
{
m_taskFactory = taskFactory;
}
public void Init(Argon2Parameters parameters)
{
if (parameters.Version != Argon2Parameters.Version10 && parameters.Version != Argon2Parameters.Version13)
throw new NotSupportedException("unknown Argon2 version");
if (parameters.Type != Argon2Parameters.Argon2d && parameters.Type != Argon2Parameters.Argon2i && parameters.Type != Argon2Parameters.Argon2id)
throw new NotSupportedException("unknown Argon2 type");
int num;
if (parameters.Parallelism < 1) {
num = 1;
throw new InvalidOperationException("parallelism must be at least " + num.ToString());
}
if (parameters.Parallelism > 16777215) {
num = 16777215;
throw new InvalidOperationException("parallelism must be at most " + num.ToString());
}
if (parameters.Iterations < 1) {
num = 1;
throw new InvalidOperationException("iterations must be at least " + num.ToString());
}
this.parameters = parameters;
int num2 = System.Math.Max(parameters.Memory, 8 * parameters.Parallelism);
segmentLength = num2 / (4 * parameters.Parallelism);
laneLength = segmentLength * 4;
num2 = parameters.Parallelism * laneLength;
memory = new Block[num2];
for (int i = 0; i < memory.Length; i++) {
memory[i] = new Block();
}
}
public int GenerateBytes(char[] password, byte[] output)
{
return GenerateBytes(parameters.CharToByteConverter.Convert(password), output);
}
public int GenerateBytes(char[] password, byte[] output, int outOff, int outLen)
{
return GenerateBytes(parameters.CharToByteConverter.Convert(password), output, outOff, outLen);
}
public int GenerateBytes(byte[] password, byte[] output)
{
return GenerateBytes(password, output, 0, output.Length);
}
public int GenerateBytes(byte[] password, byte[] output, int outOff, int outLen)
{
if (outLen < 4)
throw new InvalidOperationException("output length less than " + 4.ToString());
byte[] tmpBlockBytes = new byte[1024];
Initialize(tmpBlockBytes, password, outLen);
FillMemoryBlocks();
Digest(tmpBlockBytes, output, outOff, outLen);
Reset();
return outLen;
}
private void Reset()
{
if (memory != null) {
for (int i = 0; i < memory.Length; i++) {
memory[i]?.Clear();
}
}
}
private void FillMemoryBlocks()
{
for (int i = 0; i < parameters.Iterations; i++) {
for (int j = 0; j < 4; j++) {
if (m_taskFactory == null || parameters.Parallelism <= 1) {
for (int k = 0; k < parameters.Parallelism; k++) {
Position position2 = new Position(i, j, k);
FillSegment(position2);
}
} else {
Task[] array = new Task[parameters.Parallelism];
for (int l = 0; l < parameters.Parallelism; l++) {
Position position = new Position(i, j, l);
array[l] = m_taskFactory.StartNew(delegate {
FillSegment(position);
});
}
Task.WaitAll(array);
}
}
}
}
private void FillSegment(Position position)
{
Block addressBlock = null;
Block inputBlock = null;
FillBlock fillBlock = new FillBlock();
bool flag = IsDataIndependentAddressing(position);
int startingIndex = GetStartingIndex(position);
int num = position.lane * laneLength + position.slice * segmentLength + startingIndex;
int num2 = GetPrevOffset(num);
if (flag) {
addressBlock = fillBlock.addressBlock.Clear();
inputBlock = fillBlock.inputBlock.Clear();
InitAddressBlocks(fillBlock, position, inputBlock, addressBlock);
}
bool flag2 = IsWithXor(position);
for (int i = startingIndex; i < segmentLength; i++) {
ulong pseudoRandom = GetPseudoRandom(fillBlock, i, addressBlock, inputBlock, num2, flag);
int refLane = GetRefLane(position, pseudoRandom);
int refColumn = GetRefColumn(position, i, pseudoRandom, refLane == position.lane);
Block x = memory[num2];
Block y = memory[laneLength * refLane + refColumn];
Block currentBlock = memory[num];
if (flag2)
fillBlock.FillBlockWithXor(x, y, currentBlock);
else
fillBlock.Fill(x, y, currentBlock);
num2 = num;
num++;
}
}
private bool IsDataIndependentAddressing(Position position)
{
if (parameters.Type != Argon2Parameters.Argon2i) {
if (parameters.Type == Argon2Parameters.Argon2id && position.pass == 0)
return position.slice < 2;
return false;
}
return true;
}
private void InitAddressBlocks(FillBlock filler, Position position, Block inputBlock, Block addressBlock)
{
inputBlock.v[0] = (ulong)position.pass;
inputBlock.v[1] = (ulong)position.lane;
inputBlock.v[2] = (ulong)position.slice;
inputBlock.v[3] = (ulong)memory.Length;
inputBlock.v[4] = (ulong)parameters.Iterations;
inputBlock.v[5] = (ulong)parameters.Type;
if (position.pass == 0 && position.slice == 0)
NextAddresses(filler, inputBlock, addressBlock);
}
private bool IsWithXor(Position position)
{
if (position.pass != 0)
return parameters.Version != Argon2Parameters.Version10;
return false;
}
private int GetPrevOffset(int currentOffset)
{
if (currentOffset % laneLength == 0)
return currentOffset + laneLength - 1;
return currentOffset - 1;
}
private static int GetStartingIndex(Position position)
{
if (position.pass == 0 && position.slice == 0)
return 2;
return 0;
}
private static void NextAddresses(FillBlock filler, Block inputBlock, Block addressBlock)
{
inputBlock.v[6]++;
filler.Fill(inputBlock, addressBlock);
filler.Fill(addressBlock, addressBlock);
}
private ulong GetPseudoRandom(FillBlock filler, int index, Block addressBlock, Block inputBlock, int prevOffset, bool dataIndependentAddressing)
{
if (dataIndependentAddressing) {
int num = index % 128;
if (num == 0)
NextAddresses(filler, inputBlock, addressBlock);
return addressBlock.v[num];
}
return memory[prevOffset].v[0];
}
private int GetRefLane(Position position, ulong pseudoRandom)
{
int result = (int)((long)(pseudoRandom >> 32) % (long)parameters.Parallelism);
if (position.pass == 0 && position.slice == 0)
result = position.lane;
return result;
}
private int GetRefColumn(Position position, int index, ulong pseudoRandom, bool sameLane)
{
ulong num;
ulong num2;
if (position.pass == 0) {
num = 0;
num2 = (ulong)((!sameLane) ? (position.slice * segmentLength + ((index == 0) ? (-1) : 0)) : (position.slice * segmentLength + index - 1));
} else {
num = (ulong)((position.slice + 1) * segmentLength % laneLength);
num2 = (ulong)((!sameLane) ? (laneLength - segmentLength + ((index == 0) ? (-1) : 0)) : (laneLength - segmentLength + index - 1));
}
ulong num3 = pseudoRandom & uint.MaxValue;
num3 = num3 * num3 >> 32;
num3 = num2 - 1 - (num2 * num3 >> 32);
return (int)(num + num3) % laneLength;
}
private void Digest(byte[] tmpBlockBytes, byte[] output, int outOff, int outLen)
{
Block block = memory[laneLength - 1];
for (int i = 1; i < parameters.Parallelism; i++) {
int num = i * laneLength + (laneLength - 1);
block.XorWith(memory[num]);
}
if (BitConverter.IsLittleEndian)
Hash(MemoryMarshal.AsBytes(block.v.AsSpan()), output.AsSpan(outOff, outLen));
else {
block.ToBytes(tmpBlockBytes);
Hash(tmpBlockBytes, output, outOff, outLen);
}
}
private static void Hash(byte[] input, byte[] output, int outOff, int outLen)
{
Hash(input.AsSpan(), output.AsSpan(outOff, outLen));
}
private unsafe static void Hash(ReadOnlySpan<byte> input, Span<byte> output)
{
int length = output.Length;
int num = 0;
Span<byte> span = new Span<byte>(stackalloc byte[4], 4);
Pack.UInt32_To_LE((uint)length, span);
int num2 = 64;
if (length <= num2) {
Blake2bDigest blake2bDigest = new Blake2bDigest(length * 8);
((IDigest)blake2bDigest).BlockUpdate((ReadOnlySpan<byte>)span);
((IDigest)blake2bDigest).BlockUpdate(input);
((IDigest)blake2bDigest).DoFinal(output);
} else {
int num3 = num2 / 2;
int num4 = num;
IDigest digest = new Blake2bDigest(num2 * 8);
byte[] array = new byte[num2];
digest.BlockUpdate(span);
digest.BlockUpdate(input);
digest.DoFinal(array);
RuntimeHelpers.GetSubArray(array, new Range(0, num3)).CopyTo(output);
num4 += num3;
int num5 = (length + 31) / 32 - 2;
int num6 = 2;
int num7;
while (num6 <= num5) {
digest.BlockUpdate(array, 0, array.Length);
digest.DoFinal(array, 0);
byte[] subArray = RuntimeHelpers.GetSubArray(array, new Range(0, num3));
num7 = num4;
subArray.CopyTo(output.Slice(num7, output.Length - num7));
num6++;
num4 += num3;
}
digest = new Blake2bDigest((length - 32 * num5) * 8);
digest.BlockUpdate(array, 0, array.Length);
IDigest digest2 = digest;
num7 = num4;
digest2.DoFinal(output.Slice(num7, output.Length - num7));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GB(ref ulong a, ref ulong b, ref ulong c, ref ulong d)
{
a += b + 2 * Mul32((uint)a, (uint)b);
d = Longs.RotateRight(d ^ a, 32);
c += d + 2 * Mul32((uint)c, (uint)d);
b = Longs.RotateRight(b ^ c, 24);
a += b + 2 * Mul32((uint)a, (uint)b);
d = Longs.RotateRight(d ^ a, 16);
c += d + 2 * Mul32((uint)c, (uint)d);
b = Longs.RotateRight(b ^ c, 63);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mul32(uint a, uint b)
{
return (ulong)((long)a * (long)b);
}
private void Initialize(byte[] tmpBlockBytes, byte[] password, int outputLength)
{
Blake2bDigest blake2bDigest = new Blake2bDigest(512);
uint[] array = new uint[6] {
(uint)parameters.Parallelism,
(uint)outputLength,
(uint)parameters.Memory,
(uint)parameters.Iterations,
(uint)parameters.Version,
(uint)parameters.Type
};
Pack.UInt32_To_LE(array, tmpBlockBytes, 0);
blake2bDigest.BlockUpdate(tmpBlockBytes, 0, array.Length * 4);
AddByteString(tmpBlockBytes, blake2bDigest, password);
AddByteString(tmpBlockBytes, blake2bDigest, parameters.Salt);
AddByteString(tmpBlockBytes, blake2bDigest, parameters.Secret);
AddByteString(tmpBlockBytes, blake2bDigest, parameters.Additional);
byte[] array2 = new byte[72];
blake2bDigest.DoFinal(array2, 0);
FillFirstBlocks(tmpBlockBytes, array2);
}
private void AddByteString(byte[] tmpBlockBytes, IDigest digest, byte[] octets)
{
if (octets == null)
digest.BlockUpdate(ZeroBytes, 0, 4);
else {
Pack.UInt32_To_LE((uint)octets.Length, tmpBlockBytes, 0);
digest.BlockUpdate(tmpBlockBytes, 0, 4);
digest.BlockUpdate(octets, 0, octets.Length);
}
}
private void FillFirstBlocks(byte[] tmpBlockBytes, byte[] initialHashWithZeros)
{
byte[] array = new byte[72];
Array.Copy(initialHashWithZeros, 0, array, 0, 64);
array[64] = 1;
for (int i = 0; i < parameters.Parallelism; i++) {
Pack.UInt32_To_LE((uint)i, initialHashWithZeros, 68);
Pack.UInt32_To_LE((uint)i, array, 68);
if (BitConverter.IsLittleEndian) {
Span<byte> output = MemoryMarshal.AsBytes(memory[i * laneLength].v.AsSpan());
Span<byte> output2 = MemoryMarshal.AsBytes(memory[i * laneLength + 1].v.AsSpan());
Hash(initialHashWithZeros, output);
Hash(array, output2);
} else {
Hash(initialHashWithZeros, tmpBlockBytes, 0, 1024);
memory[i * laneLength].FromBytes(tmpBlockBytes);
Hash(array, tmpBlockBytes, 0, 1024);
memory[i * laneLength + 1].FromBytes(tmpBlockBytes);
}
}
}
}
}