Skip to content

Instantly share code, notes, and snippets.

@FrankSpierings
Last active December 30, 2016 04:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FrankSpierings/c18da658e06948313fff to your computer and use it in GitHub Desktop.
Save FrankSpierings/c18da658e06948313fff to your computer and use it in GitHub Desktop.
$source = @"
/*
* This implementation of Salsa20 is ported from the reference implementation
* by D. J. Bernstein, which can be found at:
* http://cr.yp.to/snuffle/salsa20/ref/salsa20.c
*
* This work is hereby released into the Public Domain. To view a copy of the public domain dedication,
* visit http://creativecommons.org/licenses/publicdomain/ or send a letter to
* Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
*/
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
namespace Logos.Utility.Security.Cryptography
{
/// <summary>
/// Implements the Salsa20 stream encryption cipher, as defined at http://cr.yp.to/snuffle.html.
/// </summary>
/// <remarks>See <a href="http://code.logos.com/blog/2008/06/salsa20_implementation_in_c_1.html">Salsa20 Implementation in C#</a>.</remarks>
public sealed class Salsa20 : SymmetricAlgorithm
{
/// <summary>
/// Initializes a new instance of the <see cref="Salsa20"/> class.
/// </summary>
/// <exception cref="CryptographicException">The implementation of the class derived from the symmetric algorithm is not valid.</exception>
public Salsa20()
{
// set legal values
LegalBlockSizesValue = new KeySizes[] { new KeySizes(512, 512, 0) };
LegalKeySizesValue = new KeySizes[] { new KeySizes(128, 256, 128) };
// set default values
BlockSizeValue = 512;
KeySizeValue = 256;
m_rounds = 20;
}
/// <summary>
/// Creates a symmetric decryptor object with the specified <see cref="SymmetricAlgorithm.Key"/> property
/// and initialization vector (<see cref="SymmetricAlgorithm.IV"/>).
/// </summary>
/// <param name="rgbKey">The secret key to use for the symmetric algorithm.</param>
/// <param name="rgbIV">The initialization vector to use for the symmetric algorithm.</param>
/// <returns>A symmetric decryptor object.</returns>
public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV)
{
// decryption and encryption are symmetrical
return CreateEncryptor(rgbKey, rgbIV);
}
/// <summary>
/// Creates a symmetric encryptor object with the specified <see cref="SymmetricAlgorithm.Key"/> property
/// and initialization vector (<see cref="SymmetricAlgorithm.IV"/>).
/// </summary>
/// <param name="rgbKey">The secret key to use for the symmetric algorithm.</param>
/// <param name="rgbIV">The initialization vector to use for the symmetric algorithm.</param>
/// <returns>A symmetric encryptor object.</returns>
public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV)
{
if (rgbKey == null)
throw new ArgumentNullException("rgbKey");
if (!ValidKeySize(rgbKey.Length * 8))
throw new CryptographicException("Invalid key size; it must be 128 or 256 bits.");
CheckValidIV(rgbIV, "rgbIV");
return new Salsa20CryptoTransform(rgbKey, rgbIV, m_rounds);
}
/// <summary>
/// Generates a random initialization vector (<see cref="SymmetricAlgorithm.IV"/>) to use for the algorithm.
/// </summary>
public override void GenerateIV()
{
// generate a random 8-byte IV
IVValue = GetRandomBytes(8);
}
/// <summary>
/// Generates a random key (<see cref="SymmetricAlgorithm.Key"/>) to use for the algorithm.
/// </summary>
public override void GenerateKey()
{
// generate a random key
KeyValue = GetRandomBytes(KeySize / 8);
}
/// <summary>
/// Gets or sets the initialization vector (<see cref="SymmetricAlgorithm.IV"/>) for the symmetric algorithm.
/// </summary>
/// <value>The initialization vector.</value>
/// <exception cref="ArgumentNullException">An attempt was made to set the initialization vector to null. </exception>
/// <exception cref="CryptographicException">An attempt was made to set the initialization vector to an invalid size. </exception>
public override byte[] IV
{
get
{
return base.IV;
}
set
{
CheckValidIV(value, "value");
IVValue = (byte[]) value.Clone();
}
}
/// <summary>
/// Gets or sets the number of rounds used by the Salsa20 algorithm.
/// </summary>
/// <value>The number of rounds.</value>
public int Rounds
{
get
{
return m_rounds;
}
set
{
if (value != 8 && value != 12 && value != 20)
throw new ArgumentOutOfRangeException("value", "The number of rounds must be 8, 12, or 20.");
m_rounds = value;
}
}
// Verifies that iv is a legal value for a Salsa20 IV.
private static void CheckValidIV(byte[] iv, string paramName)
{
if (iv == null)
throw new ArgumentNullException(paramName);
if (iv.Length != 8)
throw new CryptographicException("Invalid IV size; it must be 8 bytes.");
}
// Returns a new byte array containing the specified number of random bytes.
private static byte[] GetRandomBytes(int byteCount)
{
byte[] bytes = new byte[byteCount];
//using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
RandomNumberGenerator rng = new RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return bytes;
}
int m_rounds;
/// <summary>
/// Salsa20Impl is an implementation of <see cref="ICryptoTransform"/> that uses the Salsa20 algorithm.
/// </summary>
private sealed class Salsa20CryptoTransform : ICryptoTransform
{
public Salsa20CryptoTransform(byte[] key, byte[] iv, int rounds)
{
Debug.Assert(key.Length == 16 || key.Length == 32, "abyKey.Length == 16 || abyKey.Length == 32", "Invalid key size.");
Debug.Assert(iv.Length == 8, "abyIV.Length == 8", "Invalid IV size.");
Debug.Assert(rounds == 8 || rounds == 12 || rounds == 20, "rounds == 8 || rounds == 12 || rounds == 20", "Invalid number of rounds.");
Initialize(key, iv);
m_rounds = rounds;
}
public bool CanReuseTransform
{
get { return false; }
}
public bool CanTransformMultipleBlocks
{
get { return true; }
}
public int InputBlockSize
{
get { return 64; }
}
public int OutputBlockSize
{
get { return 64; }
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
// check arguments
if (inputBuffer == null)
throw new ArgumentNullException("inputBuffer");
if (inputOffset < 0 || inputOffset >= inputBuffer.Length)
throw new ArgumentOutOfRangeException("inputOffset");
if (inputCount < 0 || inputOffset + inputCount > inputBuffer.Length)
throw new ArgumentOutOfRangeException("inputCount");
if (outputBuffer == null)
throw new ArgumentNullException("outputBuffer");
if (outputOffset < 0 || outputOffset + inputCount > outputBuffer.Length)
throw new ArgumentOutOfRangeException("outputOffset");
if (m_state == null)
throw new ObjectDisposedException(GetType().Name);
byte[] output = new byte[64];
int bytesTransformed = 0;
while (inputCount > 0)
{
Hash(output, m_state);
m_state[8] = AddOne(m_state[8]);
if (m_state[8] == 0)
{
// NOTE: stopping at 2^70 bytes per nonce is user's responsibility
m_state[9] = AddOne(m_state[9]);
}
int blockSize = Math.Min(64, inputCount);
for (int i = 0; i < blockSize; i++)
outputBuffer[outputOffset + i] = (byte) (inputBuffer[inputOffset + i] ^ output[i]);
bytesTransformed += blockSize;
inputCount -= 64;
outputOffset += 64;
inputOffset += 64;
}
return bytesTransformed;
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
if (inputCount < 0)
throw new ArgumentOutOfRangeException("inputCount");
byte[] output = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, output, 0);
return output;
}
public void Dispose()
{
if (m_state != null)
Array.Clear(m_state, 0, m_state.Length);
m_state = null;
}
private static uint Rotate(uint v, int c)
{
return (v << c) | (v >> (32 - c));
}
private static uint Add(uint v, uint w)
{
return unchecked(v + w);
}
private static uint AddOne(uint v)
{
return unchecked(v + 1);
}
private void Hash(byte[] output, uint[] input)
{
uint[] state = (uint[]) input.Clone();
for (int round = m_rounds; round > 0; round -= 2)
{
state[4] ^= Rotate(Add(state[0], state[12]), 7);
state[8] ^= Rotate(Add(state[4], state[0]), 9);
state[12] ^= Rotate(Add(state[8], state[4]), 13);
state[0] ^= Rotate(Add(state[12], state[8]), 18);
state[9] ^= Rotate(Add(state[5], state[1]), 7);
state[13] ^= Rotate(Add(state[9], state[5]), 9);
state[1] ^= Rotate(Add(state[13], state[9]), 13);
state[5] ^= Rotate(Add(state[1], state[13]), 18);
state[14] ^= Rotate(Add(state[10], state[6]), 7);
state[2] ^= Rotate(Add(state[14], state[10]), 9);
state[6] ^= Rotate(Add(state[2], state[14]), 13);
state[10] ^= Rotate(Add(state[6], state[2]), 18);
state[3] ^= Rotate(Add(state[15], state[11]), 7);
state[7] ^= Rotate(Add(state[3], state[15]), 9);
state[11] ^= Rotate(Add(state[7], state[3]), 13);
state[15] ^= Rotate(Add(state[11], state[7]), 18);
state[1] ^= Rotate(Add(state[0], state[3]), 7);
state[2] ^= Rotate(Add(state[1], state[0]), 9);
state[3] ^= Rotate(Add(state[2], state[1]), 13);
state[0] ^= Rotate(Add(state[3], state[2]), 18);
state[6] ^= Rotate(Add(state[5], state[4]), 7);
state[7] ^= Rotate(Add(state[6], state[5]), 9);
state[4] ^= Rotate(Add(state[7], state[6]), 13);
state[5] ^= Rotate(Add(state[4], state[7]), 18);
state[11] ^= Rotate(Add(state[10], state[9]), 7);
state[8] ^= Rotate(Add(state[11], state[10]), 9);
state[9] ^= Rotate(Add(state[8], state[11]), 13);
state[10] ^= Rotate(Add(state[9], state[8]), 18);
state[12] ^= Rotate(Add(state[15], state[14]), 7);
state[13] ^= Rotate(Add(state[12], state[15]), 9);
state[14] ^= Rotate(Add(state[13], state[12]), 13);
state[15] ^= Rotate(Add(state[14], state[13]), 18);
}
for (int index = 0; index < 16; index++)
ToBytes(Add(state[index], input[index]), output, 4 * index);
}
private void Initialize(byte[] key, byte[] iv)
{
m_state = new uint[16];
m_state[1] = ToUInt32(key, 0);
m_state[2] = ToUInt32(key, 4);
m_state[3] = ToUInt32(key, 8);
m_state[4] = ToUInt32(key, 12);
byte[] constants = key.Length == 32 ? c_sigma : c_tau;
int keyIndex = key.Length - 16;
m_state[11] = ToUInt32(key, keyIndex + 0);
m_state[12] = ToUInt32(key, keyIndex + 4);
m_state[13] = ToUInt32(key, keyIndex + 8);
m_state[14] = ToUInt32(key, keyIndex + 12);
m_state[0] = ToUInt32(constants, 0);
m_state[5] = ToUInt32(constants, 4);
m_state[10] = ToUInt32(constants, 8);
m_state[15] = ToUInt32(constants, 12);
m_state[6] = ToUInt32(iv, 0);
m_state[7] = ToUInt32(iv, 4);
m_state[8] = 0;
m_state[9] = 0;
}
private static uint ToUInt32(byte[] input, int inputOffset)
{
return unchecked((uint) (((input[inputOffset] | (input[inputOffset + 1] << 8)) | (input[inputOffset + 2] << 16)) | (input[inputOffset + 3] << 24)));
}
private static void ToBytes(uint input, byte[] output, int outputOffset)
{
unchecked
{
output[outputOffset] = (byte) input;
output[outputOffset + 1] = (byte) (input >> 8);
output[outputOffset + 2] = (byte) (input >> 16);
output[outputOffset + 3] = (byte) (input >> 24);
}
}
static readonly byte[] c_sigma = Encoding.ASCII.GetBytes("expand 32-byte k");
static readonly byte[] c_tau = Encoding.ASCII.GetBytes("expand 16-byte k");
uint[] m_state;
readonly int m_rounds;
}
}
}
"@
if (-not ([System.Management.Automation.PSTypeName]'Logos.Utility.Security.Cryptography.Salsa20').Type)
{
Add-Type -TypeDefinition $source -Language CSharp -ErrorAction Stop
}
$salsa20 = New-Object Logos.Utility.Security.Cryptography.Salsa20
$salsa20.GenerateKey()
$salsa20.GenerateIV()
$enc = $salsa20.CreateEncryptor()
$dec = $salsa20.CreateDecryptor()
$plain = [System.Text.Encoding]::ASCII.GetBytes("Hello World")
[Byte[]]$encrypted = (1..$plain.Length) |% {0}
[Byte[]]$decrypted = (1..$plain.Length) |% {0}
$enc.TransformBlock($plain, 0, $plain.Length, $encrypted, 0) | Out-Null
$dec.TransformBlock($encrypted, 0, $encrypted.Length, $decrypted, 0) | Out-Null
[Convert]::ToBase64String($encrypted)
[System.Text.Encoding]::ASCII.GetString($decrypted)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment