You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
6.8 KiB
214 lines
6.8 KiB
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) |
|
#pragma warning disable |
|
using System; |
|
using System.Diagnostics; |
|
|
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Digests; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; |
|
|
|
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Generators |
|
{ |
|
/// <summary>Implementation of the scrypt a password-based key derivation function.</summary> |
|
/// <remarks> |
|
/// Scrypt was created by Colin Percival and is specified in |
|
/// <a href="http://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01">draft-josefsson-scrypt-kd</a>. |
|
/// </remarks> |
|
public class SCrypt |
|
{ |
|
/// <summary>Generate a key using the scrypt key derivation function.</summary> |
|
/// <param name="P">the bytes of the pass phrase.</param> |
|
/// <param name="S">the salt to use for this invocation.</param> |
|
/// <param name="N">CPU/Memory cost parameter. Must be larger than 1, a power of 2 and less than |
|
/// <code>2^(128 * r / 8)</code>.</param> |
|
/// <param name="r">the block size, must be >= 1.</param> |
|
/// <param name="p">Parallelization parameter. Must be a positive integer less than or equal to |
|
/// <code>Int32.MaxValue / (128 * r * 8)</code>.</param> |
|
/// <param name="dkLen">the length of the key to generate.</param> |
|
/// <returns>the generated key.</returns> |
|
public static byte[] Generate(byte[] P, byte[] S, int N, int r, int p, int dkLen) |
|
{ |
|
if (P == null) |
|
throw new ArgumentNullException("Passphrase P must be provided."); |
|
if (S == null) |
|
throw new ArgumentNullException("Salt S must be provided."); |
|
if (N <= 1 || !IsPowerOf2(N)) |
|
throw new ArgumentException("Cost parameter N must be > 1 and a power of 2."); |
|
// Only value of r that cost (as an int) could be exceeded for is 1 |
|
if (r == 1 && N >= 65536) |
|
throw new ArgumentException("Cost parameter N must be > 1 and < 65536."); |
|
if (r < 1) |
|
throw new ArgumentException("Block size r must be >= 1."); |
|
int maxParallel = Int32.MaxValue / (128 * r * 8); |
|
if (p < 1 || p > maxParallel) |
|
{ |
|
throw new ArgumentException("Parallelisation parameter p must be >= 1 and <= " + maxParallel |
|
+ " (based on block size r of " + r + ")"); |
|
} |
|
if (dkLen < 1) |
|
throw new ArgumentException("Generated key length dkLen must be >= 1."); |
|
|
|
return MFcrypt(P, S, N, r, p, dkLen); |
|
} |
|
|
|
private static byte[] MFcrypt(byte[] P, byte[] S, int N, int r, int p, int dkLen) |
|
{ |
|
int MFLenBytes = r * 128; |
|
byte[] bytes = SingleIterationPBKDF2(P, S, p * MFLenBytes); |
|
|
|
uint[] B = null; |
|
|
|
try |
|
{ |
|
int BLen = bytes.Length >> 2; |
|
B = new uint[BLen]; |
|
|
|
Pack.LE_To_UInt32(bytes, 0, B); |
|
|
|
/* |
|
* Chunk memory allocations; We choose 'd' so that there will be 2**d chunks, each not |
|
* larger than 32KiB, except that the minimum chunk size is 2 * r * 32. |
|
*/ |
|
int d = 0, total = N * r; |
|
while ((N - d) > 2 && total > (1 << 10)) |
|
{ |
|
++d; |
|
total >>= 1; |
|
} |
|
|
|
int MFLenWords = MFLenBytes >> 2; |
|
for (int BOff = 0; BOff < BLen; BOff += MFLenWords) |
|
{ |
|
// TODO These can be done in parallel threads |
|
SMix(B, BOff, N, d, r); |
|
} |
|
|
|
Pack.UInt32_To_LE(B, bytes, 0); |
|
|
|
return SingleIterationPBKDF2(P, bytes, dkLen); |
|
} |
|
finally |
|
{ |
|
ClearAll(bytes, B); |
|
} |
|
} |
|
|
|
private static byte[] SingleIterationPBKDF2(byte[] P, byte[] S, int dkLen) |
|
{ |
|
PbeParametersGenerator pGen = new Pkcs5S2ParametersGenerator(new Sha256Digest()); |
|
pGen.Init(P, S, 1); |
|
KeyParameter key = (KeyParameter)pGen.GenerateDerivedMacParameters(dkLen * 8); |
|
return key.GetKey(); |
|
} |
|
|
|
private static void SMix(uint[] B, int BOff, int N, int d, int r) |
|
{ |
|
int powN = Integers.NumberOfTrailingZeros(N); |
|
int blocksPerChunk = N >> d; |
|
int chunkCount = 1 << d, chunkMask = blocksPerChunk - 1, chunkPow = powN - d; |
|
|
|
int BCount = r * 32; |
|
|
|
uint[] blockX1 = new uint[16]; |
|
uint[] blockX2 = new uint[16]; |
|
uint[] blockY = new uint[BCount]; |
|
|
|
uint[] X = new uint[BCount]; |
|
uint[][] VV = new uint[chunkCount][]; |
|
|
|
try |
|
{ |
|
Array.Copy(B, BOff, X, 0, BCount); |
|
|
|
for (int c = 0; c < chunkCount; ++c) |
|
{ |
|
uint[] V = new uint[blocksPerChunk * BCount]; |
|
VV[c] = V; |
|
|
|
int off = 0; |
|
for (int i = 0; i < blocksPerChunk; i += 2) |
|
{ |
|
Array.Copy(X, 0, V, off, BCount); |
|
off += BCount; |
|
BlockMix(X, blockX1, blockX2, blockY, r); |
|
Array.Copy(blockY, 0, V, off, BCount); |
|
off += BCount; |
|
BlockMix(blockY, blockX1, blockX2, X, r); |
|
} |
|
} |
|
|
|
uint mask = (uint)N - 1; |
|
for (int i = 0; i < N; ++i) |
|
{ |
|
int j = (int)(X[BCount - 16] & mask); |
|
uint[] V = VV[j >> chunkPow]; |
|
int VOff = (j & chunkMask) * BCount; |
|
Array.Copy(V, VOff, blockY, 0, BCount); |
|
Xor(blockY, X, 0, blockY); |
|
BlockMix(blockY, blockX1, blockX2, X, r); |
|
} |
|
|
|
Array.Copy(X, 0, B, BOff, BCount); |
|
} |
|
finally |
|
{ |
|
ClearAll(VV); |
|
ClearAll(X, blockX1, blockX2, blockY); |
|
} |
|
} |
|
|
|
private static void BlockMix(uint[] B, uint[] X1, uint[] X2, uint[] Y, int r) |
|
{ |
|
Array.Copy(B, B.Length - 16, X1, 0, 16); |
|
|
|
int BOff = 0, YOff = 0, halfLen = B.Length >> 1; |
|
|
|
for (int i = 2 * r; i > 0; --i) |
|
{ |
|
Xor(X1, B, BOff, X2); |
|
|
|
Salsa20Engine.SalsaCore(8, X2, X1); |
|
Array.Copy(X1, 0, Y, YOff, 16); |
|
|
|
YOff = halfLen + BOff - YOff; |
|
BOff += 16; |
|
} |
|
} |
|
|
|
private static void Xor(uint[] a, uint[] b, int bOff, uint[] output) |
|
{ |
|
for (int i = output.Length - 1; i >= 0; --i) |
|
{ |
|
output[i] = a[i] ^ b[bOff + i]; |
|
} |
|
} |
|
|
|
private static void Clear(Array array) |
|
{ |
|
if (array != null) |
|
{ |
|
Array.Clear(array, 0, array.Length); |
|
} |
|
} |
|
|
|
private static void ClearAll(params Array[] arrays) |
|
{ |
|
foreach (Array array in arrays) |
|
{ |
|
Clear(array); |
|
} |
|
} |
|
|
|
// note: we know X is non-zero |
|
private static bool IsPowerOf2(int x) |
|
{ |
|
Debug.Assert(x != 0); |
|
|
|
return (x & (x - 1)) == 0; |
|
} |
|
} |
|
} |
|
#pragma warning restore |
|
#endif
|
|
|