@@ -1,54 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Security.Cryptography; | |||||
namespace Shadowsocks.Net.Crypto.AEAD | |||||
{ | |||||
public class AEADAesGcmNativeCrypto : AEADCrypto | |||||
{ | |||||
public AEADAesGcmNativeCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm)}, | |||||
}; | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
AesGcm aes; | |||||
public override void InitCipher(byte[] salt, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(salt, isEncrypt); | |||||
aes = new AesGcm(sessionKey); | |||||
} | |||||
public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
aes.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen)); | |||||
return plain.Length + tagLen; | |||||
} | |||||
public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
int clen = cipher.Length - tagLen; | |||||
ReadOnlySpan<byte> ciphertxt = cipher.Slice(0, clen); | |||||
ReadOnlySpan<byte> tag = cipher.Slice(clen); | |||||
aes.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen)); | |||||
return clen; | |||||
} | |||||
} | |||||
} |
@@ -1,75 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto.Engines; | |||||
using Org.BouncyCastle.Crypto.Modes; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using Shadowsocks.Net.Crypto.Extensions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Shadowsocks.Net.Crypto.AEAD | |||||
{ | |||||
public class AEADBouncyCastleCrypto : AEADCrypto | |||||
{ | |||||
IAeadCipher aead; | |||||
bool enc; | |||||
public AEADBouncyCastleCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
aead = cipherFamily switch | |||||
{ | |||||
CipherFamily.AesGcm => new GcmBlockCipher(new AesEngine()), | |||||
CipherFamily.Chacha20Poly1305 => new ChaCha20Poly1305(), | |||||
CipherFamily.XChacha20Poly1305 => new XChaCha20Poly1305(), | |||||
_ => throw new NotSupportedException(), | |||||
}; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm)}, | |||||
{"aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm)}, | |||||
{"chacha20-ietf-poly1305", new CipherInfo("chacha20-ietf-poly1305",32, 32, 12, 16, CipherFamily.Chacha20Poly1305)}, | |||||
{"xchacha20-ietf-poly1305", new CipherInfo("xchacha20-ietf-poly1305",32, 32, 24, 16, CipherFamily.XChacha20Poly1305)}, | |||||
}; | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
public override void InitCipher(byte[] salt, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(salt, isEncrypt); | |||||
enc = isEncrypt; | |||||
} | |||||
public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
return CipherUpdate(cipher, plain); | |||||
} | |||||
public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
return CipherUpdate(plain, cipher); | |||||
} | |||||
private int CipherUpdate(ReadOnlySpan<byte> i, Span<byte> o) | |||||
{ | |||||
byte[] t = new byte[o.Length]; | |||||
byte[] n = new byte[nonce.Length]; | |||||
nonce.CopyTo(n, 0); | |||||
aead.Init(enc, new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, n)); | |||||
int r = aead.ProcessBytes(i.ToArray(), 0, i.Length, t, 0); | |||||
r += aead.DoFinal(t, r); | |||||
t.CopyTo(o); | |||||
return r; | |||||
} | |||||
} | |||||
} |
@@ -1,3 +1,4 @@ | |||||
using CryptoBase; | |||||
using Shadowsocks.Net.Crypto.Exception; | using Shadowsocks.Net.Crypto.Exception; | ||||
using Shadowsocks.Net.Crypto.Stream; | using Shadowsocks.Net.Crypto.Stream; | ||||
using Splat; | using Splat; | ||||
@@ -5,6 +6,7 @@ using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Net; | using System.Net; | ||||
using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||
using System.Security.Cryptography; | |||||
using System.Text; | using System.Text; | ||||
namespace Shadowsocks.Net.Crypto.AEAD | namespace Shadowsocks.Net.Crypto.AEAD | ||||
@@ -90,7 +92,7 @@ namespace Shadowsocks.Net.Crypto.AEAD | |||||
this.salt = new byte[saltLen]; | this.salt = new byte[saltLen]; | ||||
Array.Copy(salt, this.salt, saltLen); | Array.Copy(salt, this.salt, saltLen); | ||||
CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey, 0); | |||||
HKDF.DeriveKey(HashAlgorithmName.SHA1, masterKey, sessionKey, salt, InfoBytes); | |||||
this.Log().Debug($"salt {instanceId}", salt, saltLen); | this.Log().Debug($"salt {instanceId}", salt, saltLen); | ||||
this.Log().Debug($"sessionkey {instanceId}", sessionKey, keyLen); | this.Log().Debug($"sessionkey {instanceId}", sessionKey, keyLen); | ||||
@@ -272,9 +274,9 @@ namespace Shadowsocks.Net.Crypto.AEAD | |||||
byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length)); | byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length)); | ||||
int cipherLenSize = CipherEncrypt(lenbuf, cipher); | int cipherLenSize = CipherEncrypt(lenbuf, cipher); | ||||
CryptoUtils.SodiumIncrement(nonce); | |||||
nonce.Increment(); | |||||
int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize)); | int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize)); | ||||
CryptoUtils.SodiumIncrement(nonce); | |||||
nonce.Increment(); | |||||
return cipherLenSize + cipherDataSize; | return cipherLenSize + cipherDataSize; | ||||
} | } | ||||
@@ -299,11 +301,11 @@ namespace Shadowsocks.Net.Crypto.AEAD | |||||
this.Log().Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}"); | this.Log().Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}"); | ||||
return 0; | return 0; | ||||
} | } | ||||
CryptoUtils.SodiumIncrement(nonce); | |||||
nonce.Increment(); | |||||
// we have enough data to decrypt one chunk | // we have enough data to decrypt one chunk | ||||
// drop chunk len and its tag from buffer | // drop chunk len and its tag from buffer | ||||
int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen)); | int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen)); | ||||
CryptoUtils.SodiumIncrement(nonce); | |||||
nonce.Increment(); | |||||
this.Log().Debug($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}"); | this.Log().Debug($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}"); | ||||
return len; | return len; | ||||
} | } | ||||
@@ -0,0 +1,72 @@ | |||||
#nullable enable | |||||
using CryptoBase; | |||||
using CryptoBase.Abstractions.SymmetricCryptos; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Shadowsocks.Net.Crypto.AEAD | |||||
{ | |||||
public class AEADCryptoBaseCrypto : AEADCrypto | |||||
{ | |||||
private IAEADCrypto? _crypto; | |||||
public AEADCryptoBaseCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new() | |||||
{ | |||||
{ "aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm) }, | |||||
{ "aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm) }, | |||||
{ "aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm) }, | |||||
{ "chacha20-ietf-poly1305", new CipherInfo("chacha20-ietf-poly1305", 32, 32, 12, 16, CipherFamily.Chacha20Poly1305) }, | |||||
{ "xchacha20-ietf-poly1305", new CipherInfo("xchacha20-ietf-poly1305", 32, 32, 24, 16, CipherFamily.XChacha20Poly1305) }, | |||||
}; | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
public override void InitCipher(byte[] salt, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(salt, isEncrypt); | |||||
_crypto?.Dispose(); | |||||
_crypto = cipherFamily switch | |||||
{ | |||||
CipherFamily.AesGcm => AEADCryptoCreate.AesGcm(sessionKey), | |||||
CipherFamily.Chacha20Poly1305 => AEADCryptoCreate.ChaCha20Poly1305(sessionKey), | |||||
CipherFamily.XChacha20Poly1305 => AEADCryptoCreate.XChaCha20Poly1305(sessionKey), | |||||
_ => throw new NotSupportedException() | |||||
}; | |||||
} | |||||
public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
_crypto!.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen)); | |||||
return plain.Length + tagLen; | |||||
} | |||||
public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
var clen = cipher.Length - tagLen; | |||||
var ciphertxt = cipher.Slice(0, clen); | |||||
var tag = cipher.Slice(clen); | |||||
_crypto!.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen)); | |||||
return clen; | |||||
} | |||||
public override void Dispose() | |||||
{ | |||||
_crypto?.Dispose(); | |||||
} | |||||
} | |||||
} |
@@ -44,5 +44,7 @@ namespace Shadowsocks.Net.Crypto | |||||
public abstract int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher); | public abstract int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher); | ||||
public int AddressBufferLength { get; set; } = -1; | public int AddressBufferLength { get; set; } = -1; | ||||
public abstract void Dispose(); | |||||
} | } | ||||
} | } |
@@ -26,46 +26,22 @@ namespace Shadowsocks.Net.Crypto | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeCrypto)); | _registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeCrypto)); | ||||
} | } | ||||
} | } | ||||
foreach (var method in StreamRc4NativeCrypto.SupportedCiphers()) | |||||
{ | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeCrypto)); | |||||
} | |||||
} | |||||
foreach (var method in StreamAesCfbBouncyCastleCrypto.SupportedCiphers()) | |||||
{ | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamAesCfbBouncyCastleCrypto)); | |||||
} | |||||
} | |||||
foreach (var method in StreamChachaBouncyCastleCrypto.SupportedCiphers()) | |||||
{ | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamChachaBouncyCastleCrypto)); | |||||
} | |||||
} | |||||
foreach (var method in AEADAesGcmNativeCrypto.SupportedCiphers()) | |||||
foreach (var method in StreamCryptoBaseCrypto.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | if (!_registeredEncryptors.ContainsKey(method.Key)) | ||||
{ | { | ||||
ciphers.Add(method.Key, method.Value); | ciphers.Add(method.Key, method.Value); | ||||
_registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeCrypto)); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamCryptoBaseCrypto)); | |||||
} | } | ||||
} | } | ||||
foreach (var method in AEADBouncyCastleCrypto.SupportedCiphers()) | |||||
foreach (var method in AEADCryptoBaseCrypto.SupportedCiphers()) | |||||
{ | { | ||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | if (!_registeredEncryptors.ContainsKey(method.Key)) | ||||
{ | { | ||||
ciphers.Add(method.Key, method.Value); | ciphers.Add(method.Key, method.Value); | ||||
_registeredEncryptors.Add(method.Key, typeof(AEADBouncyCastleCrypto)); | |||||
_registeredEncryptors.Add(method.Key, typeof(AEADCryptoBaseCrypto)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -1,62 +1,14 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
using Org.BouncyCastle.Crypto.Digests; | |||||
using Org.BouncyCastle.Crypto.Generators; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using System; | |||||
using System.Security.Cryptography; | |||||
using System.Threading; | |||||
using CryptoBase.Digests.MD5; | |||||
namespace Shadowsocks.Net.Crypto | namespace Shadowsocks.Net.Crypto | ||||
{ | { | ||||
public static class CryptoUtils | public static class CryptoUtils | ||||
{ | { | ||||
private static readonly ThreadLocal<MD5> Md5Hasher = new ThreadLocal<MD5>(System.Security.Cryptography.MD5.Create); | |||||
public static byte[] MD5(byte[] b) | public static byte[] MD5(byte[] b) | ||||
{ | { | ||||
var hash = new byte[CryptoBase.MD5Length]; | var hash = new byte[CryptoBase.MD5Length]; | ||||
Md5Hasher.Value.TryComputeHash(b, hash, out _); | |||||
return hash; | |||||
} | |||||
// currently useless, just keep api same | |||||
public static Span<byte> MD5(Span<byte> span) | |||||
{ | |||||
Span<byte> hash = new byte[CryptoBase.MD5Length]; | |||||
Md5Hasher.Value.TryComputeHash(span, hash, out _); | |||||
MD5Utils.Default(b, hash); | |||||
return hash; | return hash; | ||||
} | } | ||||
public static byte[] HKDF(int keylen, byte[] master, byte[] salt, byte[] info) | |||||
{ | |||||
byte[] ret = new byte[keylen]; | |||||
IDigest degist = new Sha1Digest(); | |||||
HkdfParameters parameters = new HkdfParameters(master, salt, info); | |||||
HkdfBytesGenerator hkdf = new HkdfBytesGenerator(degist); | |||||
hkdf.Init(parameters); | |||||
hkdf.GenerateBytes(ret, 0, keylen); | |||||
return ret; | |||||
} | |||||
// currently useless, just keep api same, again | |||||
public static Span<byte> HKDF(int keylen, Span<byte> master, Span<byte> salt, Span<byte> info) | |||||
{ | |||||
byte[] ret = new byte[keylen]; | |||||
IDigest degist = new Sha1Digest(); | |||||
HkdfParameters parameters = new HkdfParameters(master.ToArray(), salt.ToArray(), info.ToArray()); | |||||
HkdfBytesGenerator hkdf = new HkdfBytesGenerator(degist); | |||||
hkdf.Init(parameters); | |||||
hkdf.GenerateBytes(ret, 0, keylen); | |||||
return ret.AsSpan(); | |||||
} | |||||
public static void SodiumIncrement(Span<byte> salt) | |||||
{ | |||||
for (var i = 0; i < salt.Length; ++i) | |||||
{ | |||||
if (++salt[i] != 0) | |||||
{ | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,23 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
internal static class Check | |||||
{ | |||||
internal static void DataLength(byte[] buf, int off, int len, string msg) | |||||
{ | |||||
if (off > buf.Length - len) | |||||
{ | |||||
throw new DataLengthException(msg); | |||||
} | |||||
} | |||||
internal static void OutputLength(byte[] buf, int off, int len, string msg) | |||||
{ | |||||
if (off > buf.Length - len) | |||||
{ | |||||
throw new OutputLengthException(msg); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -1,264 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using System; | |||||
using System.IO; | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
/** | |||||
* implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. | |||||
*/ | |||||
public class MyCfbBlockCipher | |||||
: IBlockCipher | |||||
{ | |||||
private byte[] IV; | |||||
private byte[] cfbV; | |||||
private byte[] cfbOutV; | |||||
private byte[] buff; | |||||
private int _offset; | |||||
private bool encrypting; | |||||
private readonly int blockSize; | |||||
private readonly IBlockCipher cipher; | |||||
/** | |||||
* Basic constructor. | |||||
* | |||||
* @param cipher the block cipher to be used as the basis of the | |||||
* feedback mode. | |||||
* @param blockSize the block size in bits (note: a multiple of 8) | |||||
*/ | |||||
public MyCfbBlockCipher( | |||||
IBlockCipher cipher, | |||||
int bitBlockSize) | |||||
{ | |||||
this.cipher = cipher; | |||||
blockSize = bitBlockSize / 8; | |||||
IV = new byte[cipher.GetBlockSize()]; | |||||
cfbV = new byte[cipher.GetBlockSize()]; | |||||
cfbOutV = new byte[cipher.GetBlockSize()]; | |||||
buff = new byte[cipher.GetBlockSize()]; | |||||
_offset = 0; | |||||
} | |||||
/** | |||||
* return the underlying block cipher that we are wrapping. | |||||
* | |||||
* @return the underlying block cipher that we are wrapping. | |||||
*/ | |||||
public IBlockCipher GetUnderlyingCipher() | |||||
{ | |||||
return cipher; | |||||
} | |||||
/** | |||||
* Initialise the cipher and, possibly, the initialisation vector (IV). | |||||
* If an IV isn't passed as part of the parameter, the IV will be all zeros. | |||||
* An IV which is too short is handled in FIPS compliant fashion. | |||||
* | |||||
* @param forEncryption if true the cipher is initialised for | |||||
* encryption, if false for decryption. | |||||
* @param param the key and other data required by the cipher. | |||||
* @exception ArgumentException if the parameters argument is | |||||
* inappropriate. | |||||
*/ | |||||
public void Init( | |||||
bool forEncryption, | |||||
ICipherParameters parameters) | |||||
{ | |||||
encrypting = forEncryption; | |||||
if (parameters is ParametersWithIV ivParam) | |||||
{ | |||||
var iv = ivParam.GetIV(); | |||||
var diff = IV.Length - iv.Length; | |||||
Array.Copy(iv, 0, IV, diff, iv.Length); | |||||
Array.Clear(IV, 0, diff); | |||||
parameters = ivParam.Parameters; | |||||
} | |||||
Reset(); | |||||
// if it's null, key is to be reused. | |||||
if (parameters != null) | |||||
{ | |||||
cipher.Init(true, parameters); | |||||
} | |||||
} | |||||
/** | |||||
* return the algorithm name and mode. | |||||
* | |||||
* @return the name of the underlying algorithm followed by "/CFB" | |||||
* and the block size in bits. | |||||
*/ | |||||
public string AlgorithmName => $@"{cipher.AlgorithmName}/CFB{blockSize * 8}"; | |||||
public bool IsPartialBlockOkay => true; | |||||
/** | |||||
* return the block size we are operating at. | |||||
* | |||||
* @return the block size we are operating at (in bytes). | |||||
*/ | |||||
public int GetBlockSize() | |||||
{ | |||||
return blockSize; | |||||
} | |||||
/** | |||||
* Process one block of input from the array in and write it to | |||||
* the out array. | |||||
* | |||||
* @param in the array containing the input data. | |||||
* @param inOff offset into the in array the data starts at. | |||||
* @param out the array the output data will be copied into. | |||||
* @param outOff the offset into the out array the output will start at. | |||||
* @exception DataLengthException if there isn't enough data in in, or | |||||
* space in out. | |||||
* @exception InvalidOperationException if the cipher isn't initialised. | |||||
* @return the number of bytes processed and produced. | |||||
*/ | |||||
private int ProcessBlock( | |||||
byte[] input, | |||||
int inOff, | |||||
byte[] output, | |||||
int outOff, | |||||
bool change) | |||||
{ | |||||
return encrypting | |||||
? EncryptBlock(input, inOff, output, outOff, change) | |||||
: DecryptBlock(input, inOff, output, outOff, change); | |||||
} | |||||
public int ProcessBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff) | |||||
{ | |||||
using var m = new MemoryStream(inBuf, inOff, inBuf.Length); | |||||
var tmp = new byte[blockSize]; | |||||
var o = new byte[outBuf.Length - outOff + blockSize * 8]; | |||||
using var outStream = new MemoryStream(o); | |||||
var ptr = _offset; | |||||
int read; | |||||
while ((read = m.Read(buff, _offset, buff.Length - _offset)) > 0) | |||||
{ | |||||
if (read + _offset < buff.Length) | |||||
{ | |||||
var len = ProcessBlock(buff, 0, tmp, 0, false); | |||||
outStream.Write(tmp, 0, len); | |||||
_offset += read; | |||||
break; | |||||
} | |||||
else | |||||
{ | |||||
var len = ProcessBlock(buff, 0, tmp, 0, true); | |||||
outStream.Write(tmp, 0, len); | |||||
_offset = 0; | |||||
} | |||||
} | |||||
outStream.Seek(ptr, SeekOrigin.Begin); | |||||
var res = inBuf.Length; | |||||
outStream.Read(outBuf, outOff, res); | |||||
return res; | |||||
} | |||||
/** | |||||
* Do the appropriate processing for CFB mode encryption. | |||||
* | |||||
* @param in the array containing the data to be encrypted. | |||||
* @param inOff offset into the in array the data starts at. | |||||
* @param out the array the encrypted data will be copied into. | |||||
* @param outOff the offset into the out array the output will start at. | |||||
* @exception DataLengthException if there isn't enough data in in, or | |||||
* space in out. | |||||
* @exception InvalidOperationException if the cipher isn't initialised. | |||||
* @return the number of bytes processed and produced. | |||||
*/ | |||||
public int EncryptBlock( | |||||
byte[] input, | |||||
int inOff, | |||||
byte[] outBytes, | |||||
int outOff, | |||||
bool change) | |||||
{ | |||||
if (inOff + blockSize > input.Length) | |||||
{ | |||||
throw new DataLengthException("input buffer too short"); | |||||
} | |||||
if (outOff + blockSize > outBytes.Length) | |||||
{ | |||||
throw new DataLengthException("output buffer too short"); | |||||
} | |||||
cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); | |||||
// | |||||
// XOR the cfbV with the plaintext producing the ciphertext | |||||
// | |||||
for (var i = 0; i < blockSize; i++) | |||||
{ | |||||
outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); | |||||
} | |||||
// | |||||
// change over the input block. | |||||
// | |||||
if (change) | |||||
{ | |||||
Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); | |||||
Array.Copy(outBytes, outOff, cfbV, cfbV.Length - blockSize, blockSize); | |||||
} | |||||
return blockSize; | |||||
} | |||||
/** | |||||
* Do the appropriate processing for CFB mode decryption. | |||||
* | |||||
* @param in the array containing the data to be decrypted. | |||||
* @param inOff offset into the in array the data starts at. | |||||
* @param out the array the encrypted data will be copied into. | |||||
* @param outOff the offset into the out array the output will start at. | |||||
* @exception DataLengthException if there isn't enough data in in, or | |||||
* space in out. | |||||
* @exception InvalidOperationException if the cipher isn't initialised. | |||||
* @return the number of bytes processed and produced. | |||||
*/ | |||||
public int DecryptBlock( | |||||
byte[] input, | |||||
int inOff, | |||||
byte[] outBytes, | |||||
int outOff, | |||||
bool change) | |||||
{ | |||||
if (inOff + blockSize > input.Length) | |||||
{ | |||||
throw new DataLengthException("input buffer too short"); | |||||
} | |||||
if (outOff + blockSize > outBytes.Length) | |||||
{ | |||||
throw new DataLengthException("output buffer too short"); | |||||
} | |||||
cipher.ProcessBlock(cfbV, 0, cfbOutV, 0); | |||||
// | |||||
// change over the input block. | |||||
// | |||||
if (change) | |||||
{ | |||||
Array.Copy(cfbV, blockSize, cfbV, 0, cfbV.Length - blockSize); | |||||
Array.Copy(input, inOff, cfbV, cfbV.Length - blockSize, blockSize); | |||||
} | |||||
// | |||||
// XOR the cfbV with the ciphertext producing the plaintext | |||||
// | |||||
for (var i = 0; i < blockSize; i++) | |||||
{ | |||||
outBytes[outOff + i] = (byte)(cfbOutV[i] ^ input[inOff + i]); | |||||
} | |||||
return blockSize; | |||||
} | |||||
/** | |||||
* reset the chaining vector back to the IV and reset the underlying | |||||
* cipher. | |||||
*/ | |||||
public void Reset() | |||||
{ | |||||
Array.Copy(IV, 0, cfbV, 0, IV.Length); | |||||
_offset = 0; | |||||
cipher.Reset(); | |||||
} | |||||
} | |||||
} |
@@ -1,183 +0,0 @@ | |||||
using System; | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
/// <summary> | |||||
/// Implementation of Daniel J. Bernstein's ChaCha stream cipher. | |||||
/// </summary> | |||||
public class MyChaChaEngine : MySalsa20Engine | |||||
{ | |||||
/// <summary> | |||||
/// Creates a ChaCha engine with a specific number of rounds. | |||||
/// </summary> | |||||
/// <param name="rounds">the number of rounds (must be an even number).</param> | |||||
protected MyChaChaEngine(int rounds) : base(rounds) { } | |||||
public override string AlgorithmName => $@"ChaCha{rounds}"; | |||||
protected override void AdvanceCounter() | |||||
{ | |||||
if (++engineState[12] == 0) | |||||
{ | |||||
++engineState[13]; | |||||
} | |||||
} | |||||
protected override void ResetCounter() | |||||
{ | |||||
engineState[12] = engineState[13] = 0; | |||||
} | |||||
protected override void SetKey(byte[] keyBytes, byte[] ivBytes) | |||||
{ | |||||
if (keyBytes != null) | |||||
{ | |||||
if (keyBytes.Length != 16 && keyBytes.Length != 32) | |||||
{ | |||||
throw new ArgumentException($@"{AlgorithmName} requires 128 bit or 256 bit key"); | |||||
} | |||||
PackTauOrSigma(keyBytes.Length, engineState, 0); | |||||
// Key | |||||
Pack.LE_To_UInt32(keyBytes, 0, engineState, 4, 4); | |||||
Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 8, 4); | |||||
} | |||||
// IV | |||||
Pack.LE_To_UInt32(ivBytes, 0, engineState, 14, 2); | |||||
} | |||||
protected override void GenerateKeyStream(byte[] output) | |||||
{ | |||||
ChachaCore(rounds, engineState, x); | |||||
Pack.UInt32_To_LE(x, output, 0); | |||||
} | |||||
/// <summary> | |||||
/// ChaCha function. | |||||
/// </summary> | |||||
/// <param name="rounds">The number of ChaCha rounds to execute</param> | |||||
/// <param name="input">The input words.</param> | |||||
/// <param name="x">The ChaCha state to modify.</param> | |||||
private static void ChachaCore(int rounds, uint[] input, uint[] x) | |||||
{ | |||||
if (input.Length != 16) | |||||
{ | |||||
throw new ArgumentException(); | |||||
} | |||||
if (x.Length != 16) | |||||
{ | |||||
throw new ArgumentException(); | |||||
} | |||||
if (rounds % 2 != 0) | |||||
{ | |||||
throw new ArgumentException(@"Number of rounds must be even"); | |||||
} | |||||
var x00 = input[0]; | |||||
var x01 = input[1]; | |||||
var x02 = input[2]; | |||||
var x03 = input[3]; | |||||
var x04 = input[4]; | |||||
var x05 = input[5]; | |||||
var x06 = input[6]; | |||||
var x07 = input[7]; | |||||
var x08 = input[8]; | |||||
var x09 = input[9]; | |||||
var x10 = input[10]; | |||||
var x11 = input[11]; | |||||
var x12 = input[12]; | |||||
var x13 = input[13]; | |||||
var x14 = input[14]; | |||||
var x15 = input[15]; | |||||
for (var i = rounds; i > 0; i -= 2) | |||||
{ | |||||
x00 += x04; | |||||
x12 = R(x12 ^ x00, 16); | |||||
x08 += x12; | |||||
x04 = R(x04 ^ x08, 12); | |||||
x00 += x04; | |||||
x12 = R(x12 ^ x00, 8); | |||||
x08 += x12; | |||||
x04 = R(x04 ^ x08, 7); | |||||
x01 += x05; | |||||
x13 = R(x13 ^ x01, 16); | |||||
x09 += x13; | |||||
x05 = R(x05 ^ x09, 12); | |||||
x01 += x05; | |||||
x13 = R(x13 ^ x01, 8); | |||||
x09 += x13; | |||||
x05 = R(x05 ^ x09, 7); | |||||
x02 += x06; | |||||
x14 = R(x14 ^ x02, 16); | |||||
x10 += x14; | |||||
x06 = R(x06 ^ x10, 12); | |||||
x02 += x06; | |||||
x14 = R(x14 ^ x02, 8); | |||||
x10 += x14; | |||||
x06 = R(x06 ^ x10, 7); | |||||
x03 += x07; | |||||
x15 = R(x15 ^ x03, 16); | |||||
x11 += x15; | |||||
x07 = R(x07 ^ x11, 12); | |||||
x03 += x07; | |||||
x15 = R(x15 ^ x03, 8); | |||||
x11 += x15; | |||||
x07 = R(x07 ^ x11, 7); | |||||
x00 += x05; | |||||
x15 = R(x15 ^ x00, 16); | |||||
x10 += x15; | |||||
x05 = R(x05 ^ x10, 12); | |||||
x00 += x05; | |||||
x15 = R(x15 ^ x00, 8); | |||||
x10 += x15; | |||||
x05 = R(x05 ^ x10, 7); | |||||
x01 += x06; | |||||
x12 = R(x12 ^ x01, 16); | |||||
x11 += x12; | |||||
x06 = R(x06 ^ x11, 12); | |||||
x01 += x06; | |||||
x12 = R(x12 ^ x01, 8); | |||||
x11 += x12; | |||||
x06 = R(x06 ^ x11, 7); | |||||
x02 += x07; | |||||
x13 = R(x13 ^ x02, 16); | |||||
x08 += x13; | |||||
x07 = R(x07 ^ x08, 12); | |||||
x02 += x07; | |||||
x13 = R(x13 ^ x02, 8); | |||||
x08 += x13; | |||||
x07 = R(x07 ^ x08, 7); | |||||
x03 += x04; | |||||
x14 = R(x14 ^ x03, 16); | |||||
x09 += x14; | |||||
x04 = R(x04 ^ x09, 12); | |||||
x03 += x04; | |||||
x14 = R(x14 ^ x03, 8); | |||||
x09 += x14; | |||||
x04 = R(x04 ^ x09, 7); | |||||
} | |||||
x[0] = x00 + input[0]; | |||||
x[1] = x01 + input[1]; | |||||
x[2] = x02 + input[2]; | |||||
x[3] = x03 + input[3]; | |||||
x[4] = x04 + input[4]; | |||||
x[5] = x05 + input[5]; | |||||
x[6] = x06 + input[6]; | |||||
x[7] = x07 + input[7]; | |||||
x[8] = x08 + input[8]; | |||||
x[9] = x09 + input[9]; | |||||
x[10] = x10 + input[10]; | |||||
x[11] = x11 + input[11]; | |||||
x[12] = x12 + input[12]; | |||||
x[13] = x13 + input[13]; | |||||
x[14] = x14 + input[14]; | |||||
x[15] = x15 + input[15]; | |||||
} | |||||
} | |||||
} |
@@ -1,363 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using Org.BouncyCastle.Utilities; | |||||
using System; | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
/// <summary> | |||||
/// Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 | |||||
/// </summary> | |||||
public class MySalsa20Engine : IStreamCipher | |||||
{ | |||||
public static readonly int DefaultRounds = 20; | |||||
/** Constants */ | |||||
private const int StateSize = 16; // 16, 32 bit ints = 64 bytes | |||||
private static readonly uint[] TauSigma = Pack.LE_To_UInt32(Strings.ToAsciiByteArray("expand 16-byte k" + "expand 32-byte k"), 0, 8); | |||||
internal static void PackTauOrSigma(int keyLength, uint[] state, int stateOffset) | |||||
{ | |||||
var tsOff = (keyLength - 16) / 4; | |||||
state[stateOffset] = TauSigma[tsOff]; | |||||
state[stateOffset + 1] = TauSigma[tsOff + 1]; | |||||
state[stateOffset + 2] = TauSigma[tsOff + 2]; | |||||
state[stateOffset + 3] = TauSigma[tsOff + 3]; | |||||
} | |||||
protected int rounds; | |||||
/* | |||||
* variables to hold the state of the engine | |||||
* during encryption and decryption | |||||
*/ | |||||
private int index = 0; | |||||
internal uint[] engineState = new uint[StateSize]; // state | |||||
internal uint[] x = new uint[StateSize]; // internal buffer | |||||
private byte[] keyStream = new byte[StateSize * 4]; // expanded state, 64 bytes | |||||
private bool initialised = false; | |||||
/* | |||||
* internal counter | |||||
*/ | |||||
private uint cW0, cW1, cW2; | |||||
/// <summary> | |||||
/// Creates a Salsa20 engine with a specific number of rounds. | |||||
/// </summary> | |||||
/// <param name="rounds">the number of rounds (must be an even number).</param> | |||||
protected MySalsa20Engine(int rounds) | |||||
{ | |||||
if (rounds <= 0 || (rounds & 1) != 0) | |||||
{ | |||||
throw new ArgumentException("'rounds' must be a positive, even number"); | |||||
} | |||||
this.rounds = rounds; | |||||
} | |||||
public virtual void Init( | |||||
bool forEncryption, | |||||
ICipherParameters parameters) | |||||
{ | |||||
/* | |||||
* Salsa20 encryption and decryption is completely | |||||
* symmetrical, so the 'forEncryption' is | |||||
* irrelevant. (Like 90% of stream ciphers) | |||||
*/ | |||||
var ivParams = parameters as ParametersWithIV; | |||||
if (ivParams == null) | |||||
{ | |||||
throw new ArgumentException(AlgorithmName + " Init requires an IV", "parameters"); | |||||
} | |||||
var iv = ivParams.GetIV(); | |||||
if (iv == null || iv.Length != NonceSize) | |||||
{ | |||||
throw new ArgumentException(AlgorithmName + " requires exactly " + NonceSize + " bytes of IV"); | |||||
} | |||||
var keyParam = ivParams.Parameters; | |||||
if (keyParam == null) | |||||
{ | |||||
if (!initialised) | |||||
{ | |||||
throw new InvalidOperationException(AlgorithmName + " KeyParameter can not be null for first initialisation"); | |||||
} | |||||
SetKey(null, iv); | |||||
} | |||||
else if (keyParam is KeyParameter keyParameter) | |||||
{ | |||||
SetKey(keyParameter.GetKey(), iv); | |||||
} | |||||
else | |||||
{ | |||||
throw new ArgumentException(AlgorithmName + " Init parameters must contain a KeyParameter (or null for re-init)"); | |||||
} | |||||
Reset(); | |||||
initialised = true; | |||||
} | |||||
protected virtual int NonceSize => 8; | |||||
public virtual string AlgorithmName | |||||
{ | |||||
get | |||||
{ | |||||
var name = @"Salsa20"; | |||||
if (rounds != DefaultRounds) | |||||
{ | |||||
name += $"/{rounds}"; | |||||
} | |||||
return name; | |||||
} | |||||
} | |||||
public virtual byte ReturnByte( | |||||
byte input) | |||||
{ | |||||
if (LimitExceeded()) | |||||
{ | |||||
throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV"); | |||||
} | |||||
if (index == 0) | |||||
{ | |||||
GenerateKeyStream(keyStream); | |||||
AdvanceCounter(); | |||||
} | |||||
var output = (byte)(keyStream[index] ^ input); | |||||
index = (index + 1) & 63; | |||||
return output; | |||||
} | |||||
protected virtual void AdvanceCounter() | |||||
{ | |||||
if (++engineState[8] == 0) | |||||
{ | |||||
++engineState[9]; | |||||
} | |||||
} | |||||
public virtual void ProcessBytes( | |||||
byte[] inBytes, | |||||
int inOff, | |||||
int len, | |||||
byte[] outBytes, | |||||
int outOff) | |||||
{ | |||||
if (!initialised) | |||||
{ | |||||
throw new InvalidOperationException(AlgorithmName + " not initialised"); | |||||
} | |||||
Check.DataLength(inBytes, inOff, len, "input buffer too short"); | |||||
Check.OutputLength(outBytes, outOff, len, "output buffer too short"); | |||||
if (LimitExceeded((uint)len)) | |||||
{ | |||||
throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV"); | |||||
} | |||||
for (var i = 0; i < len; i++) | |||||
{ | |||||
if (index == 0) | |||||
{ | |||||
GenerateKeyStream(keyStream); | |||||
AdvanceCounter(); | |||||
} | |||||
outBytes[i + outOff] = (byte)(keyStream[index] ^ inBytes[i + inOff]); | |||||
index = (index + 1) & 63; | |||||
} | |||||
} | |||||
public virtual void Reset() | |||||
{ | |||||
index = 0; | |||||
ResetLimitCounter(); | |||||
ResetCounter(); | |||||
} | |||||
protected virtual void ResetCounter() | |||||
{ | |||||
engineState[8] = engineState[9] = 0; | |||||
} | |||||
protected virtual void SetKey(byte[] keyBytes, byte[] ivBytes) | |||||
{ | |||||
if (keyBytes != null) | |||||
{ | |||||
if ((keyBytes.Length != 16) && (keyBytes.Length != 32)) | |||||
{ | |||||
throw new ArgumentException(AlgorithmName + " requires 128 bit or 256 bit key"); | |||||
} | |||||
var tsOff = (keyBytes.Length - 16) / 4; | |||||
engineState[0] = TauSigma[tsOff]; | |||||
engineState[5] = TauSigma[tsOff + 1]; | |||||
engineState[10] = TauSigma[tsOff + 2]; | |||||
engineState[15] = TauSigma[tsOff + 3]; | |||||
// Key | |||||
Pack.LE_To_UInt32(keyBytes, 0, engineState, 1, 4); | |||||
Pack.LE_To_UInt32(keyBytes, keyBytes.Length - 16, engineState, 11, 4); | |||||
} | |||||
// IV | |||||
Pack.LE_To_UInt32(ivBytes, 0, engineState, 6, 2); | |||||
} | |||||
protected virtual void GenerateKeyStream(byte[] output) | |||||
{ | |||||
SalsaCore(rounds, engineState, x); | |||||
Pack.UInt32_To_LE(x, output, 0); | |||||
} | |||||
internal static void SalsaCore(int rounds, uint[] input, uint[] x) | |||||
{ | |||||
if (input.Length != 16) | |||||
{ | |||||
throw new ArgumentException(); | |||||
} | |||||
if (x.Length != 16) | |||||
{ | |||||
throw new ArgumentException(); | |||||
} | |||||
if (rounds % 2 != 0) | |||||
{ | |||||
throw new ArgumentException("Number of rounds must be even"); | |||||
} | |||||
var x00 = input[0]; | |||||
var x01 = input[1]; | |||||
var x02 = input[2]; | |||||
var x03 = input[3]; | |||||
var x04 = input[4]; | |||||
var x05 = input[5]; | |||||
var x06 = input[6]; | |||||
var x07 = input[7]; | |||||
var x08 = input[8]; | |||||
var x09 = input[9]; | |||||
var x10 = input[10]; | |||||
var x11 = input[11]; | |||||
var x12 = input[12]; | |||||
var x13 = input[13]; | |||||
var x14 = input[14]; | |||||
var x15 = input[15]; | |||||
for (var i = rounds; i > 0; i -= 2) | |||||
{ | |||||
x04 ^= R((x00 + x12), 7); | |||||
x08 ^= R((x04 + x00), 9); | |||||
x12 ^= R((x08 + x04), 13); | |||||
x00 ^= R((x12 + x08), 18); | |||||
x09 ^= R((x05 + x01), 7); | |||||
x13 ^= R((x09 + x05), 9); | |||||
x01 ^= R((x13 + x09), 13); | |||||
x05 ^= R((x01 + x13), 18); | |||||
x14 ^= R((x10 + x06), 7); | |||||
x02 ^= R((x14 + x10), 9); | |||||
x06 ^= R((x02 + x14), 13); | |||||
x10 ^= R((x06 + x02), 18); | |||||
x03 ^= R((x15 + x11), 7); | |||||
x07 ^= R((x03 + x15), 9); | |||||
x11 ^= R((x07 + x03), 13); | |||||
x15 ^= R((x11 + x07), 18); | |||||
x01 ^= R((x00 + x03), 7); | |||||
x02 ^= R((x01 + x00), 9); | |||||
x03 ^= R((x02 + x01), 13); | |||||
x00 ^= R((x03 + x02), 18); | |||||
x06 ^= R((x05 + x04), 7); | |||||
x07 ^= R((x06 + x05), 9); | |||||
x04 ^= R((x07 + x06), 13); | |||||
x05 ^= R((x04 + x07), 18); | |||||
x11 ^= R((x10 + x09), 7); | |||||
x08 ^= R((x11 + x10), 9); | |||||
x09 ^= R((x08 + x11), 13); | |||||
x10 ^= R((x09 + x08), 18); | |||||
x12 ^= R((x15 + x14), 7); | |||||
x13 ^= R((x12 + x15), 9); | |||||
x14 ^= R((x13 + x12), 13); | |||||
x15 ^= R((x14 + x13), 18); | |||||
} | |||||
x[0] = x00 + input[0]; | |||||
x[1] = x01 + input[1]; | |||||
x[2] = x02 + input[2]; | |||||
x[3] = x03 + input[3]; | |||||
x[4] = x04 + input[4]; | |||||
x[5] = x05 + input[5]; | |||||
x[6] = x06 + input[6]; | |||||
x[7] = x07 + input[7]; | |||||
x[8] = x08 + input[8]; | |||||
x[9] = x09 + input[9]; | |||||
x[10] = x10 + input[10]; | |||||
x[11] = x11 + input[11]; | |||||
x[12] = x12 + input[12]; | |||||
x[13] = x13 + input[13]; | |||||
x[14] = x14 + input[14]; | |||||
x[15] = x15 + input[15]; | |||||
} | |||||
/** | |||||
* Rotate left | |||||
* | |||||
* @param x value to rotate | |||||
* @param y amount to rotate x | |||||
* | |||||
* @return rotated x | |||||
*/ | |||||
internal static uint R(uint x, int y) | |||||
{ | |||||
return (x << y) | (x >> (32 - y)); | |||||
} | |||||
private void ResetLimitCounter() | |||||
{ | |||||
cW0 = 0; | |||||
cW1 = 0; | |||||
cW2 = 0; | |||||
} | |||||
private bool LimitExceeded() | |||||
{ | |||||
if (++cW0 == 0) | |||||
{ | |||||
if (++cW1 == 0) | |||||
{ | |||||
return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
/* | |||||
* this relies on the fact len will always be positive. | |||||
*/ | |||||
private bool LimitExceeded( | |||||
uint len) | |||||
{ | |||||
var old = cW0; | |||||
cW0 += len; | |||||
if (cW0 < old) | |||||
{ | |||||
if (++cW1 == 0) | |||||
{ | |||||
return (++cW2 & 0x20) != 0; // 2^(32 + 32 + 6) | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
} | |||||
} |
@@ -1,56 +0,0 @@ | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
internal static class Pack | |||||
{ | |||||
private static void UInt32_To_LE(uint n, byte[] bs, int off) | |||||
{ | |||||
bs[off] = (byte)n; | |||||
bs[off + 1] = (byte)(n >> 8); | |||||
bs[off + 2] = (byte)(n >> 16); | |||||
bs[off + 3] = (byte)(n >> 24); | |||||
} | |||||
internal static void UInt32_To_LE(uint[] ns, byte[] bs, int off) | |||||
{ | |||||
foreach (var nsb in ns) | |||||
{ | |||||
UInt32_To_LE(nsb, bs, off); | |||||
off += 4; | |||||
} | |||||
} | |||||
private static uint LE_To_UInt32(byte[] bs, int off) | |||||
{ | |||||
return bs[off] | |||||
| (uint)bs[off + 1] << 8 | |||||
| (uint)bs[off + 2] << 16 | |||||
| (uint)bs[off + 3] << 24; | |||||
} | |||||
internal static void LE_To_UInt32(byte[] bs, int bOff, uint[] ns, int nOff, int count) | |||||
{ | |||||
for (var i = 0; i < count; ++i) | |||||
{ | |||||
ns[nOff + i] = LE_To_UInt32(bs, bOff); | |||||
bOff += 4; | |||||
} | |||||
} | |||||
internal static uint[] LE_To_UInt32(byte[] bs, int off, int count) | |||||
{ | |||||
var ns = new uint[count]; | |||||
for (var i = 0; i < ns.Length; ++i) | |||||
{ | |||||
ns[i] = LE_To_UInt32(bs, off); | |||||
off += 4; | |||||
} | |||||
return ns; | |||||
} | |||||
internal static void UInt64_To_LE(ulong n, byte[] bs, int off) | |||||
{ | |||||
UInt32_To_LE((uint)n, bs, off); | |||||
UInt32_To_LE((uint)(n >> 32), bs, off + 4); | |||||
} | |||||
} | |||||
} |
@@ -1,98 +0,0 @@ | |||||
using Org.BouncyCastle.Utilities; | |||||
using System; | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
public class XChaCha20Engine : MyChaChaEngine | |||||
{ | |||||
public XChaCha20Engine() : base(20) | |||||
{ | |||||
} | |||||
protected override int NonceSize => 24; | |||||
public override string AlgorithmName => @"XChaCha20"; | |||||
private static readonly uint[] Sigma = Pack.LE_To_UInt32(Strings.ToAsciiByteArray("expand 32-byte k"), 0, 4); | |||||
protected override void SetKey(byte[] keyBytes, byte[] ivBytes) | |||||
{ | |||||
base.SetKey(keyBytes, ivBytes); | |||||
if (keyBytes == null || keyBytes.Length != 32) | |||||
{ | |||||
throw new ArgumentException($@"{AlgorithmName} requires a 256 bit key"); | |||||
} | |||||
if (ivBytes == null || ivBytes.Length != NonceSize) | |||||
{ | |||||
throw new ArgumentException($@"{AlgorithmName} requires a 192 bit nonce"); | |||||
} | |||||
var nonceInt = Pack.LE_To_UInt32(ivBytes, 0, 6); | |||||
var chachaKey = HChaCha20Internal(keyBytes, nonceInt); | |||||
SetSigma(engineState); | |||||
SetKey(engineState, chachaKey); | |||||
engineState[12] = 1; // Counter | |||||
engineState[13] = 0; | |||||
engineState[14] = nonceInt[4]; | |||||
engineState[15] = nonceInt[5]; | |||||
} | |||||
private static uint[] HChaCha20Internal(byte[] key, uint[] nonceInt) | |||||
{ | |||||
var x = new uint[16]; | |||||
var intKey = Pack.LE_To_UInt32(key, 0, 8); | |||||
SetSigma(x); | |||||
SetKey(x, intKey); | |||||
SetIntNonce(x, nonceInt); | |||||
DoubleRound(x); | |||||
Array.Copy(x, 12, x, 4, 4); | |||||
return x; | |||||
} | |||||
private static void SetSigma(uint[] state) | |||||
{ | |||||
Array.Copy(Sigma, 0, state, 0, Sigma.Length); | |||||
} | |||||
private static void SetKey(uint[] state, uint[] key) | |||||
{ | |||||
Array.Copy(key, 0, state, 4, 8); | |||||
} | |||||
private static void SetIntNonce(uint[] state, uint[] nonce) | |||||
{ | |||||
Array.Copy(nonce, 0, state, 12, 4); | |||||
} | |||||
private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d) | |||||
{ | |||||
x[a] += x[b]; | |||||
x[d] = R(x[d] ^ x[a], 16); | |||||
x[c] += x[d]; | |||||
x[b] = R(x[b] ^ x[c], 12); | |||||
x[a] += x[b]; | |||||
x[d] = R(x[d] ^ x[a], 8); | |||||
x[c] += x[d]; | |||||
x[b] = R(x[b] ^ x[c], 7); | |||||
} | |||||
private static void DoubleRound(uint[] state) | |||||
{ | |||||
for (var i = 0; i < 10; ++i) | |||||
{ | |||||
QuarterRound(state, 0, 4, 8, 12); | |||||
QuarterRound(state, 1, 5, 9, 13); | |||||
QuarterRound(state, 2, 6, 10, 14); | |||||
QuarterRound(state, 3, 7, 11, 15); | |||||
QuarterRound(state, 0, 5, 10, 15); | |||||
QuarterRound(state, 1, 6, 11, 12); | |||||
QuarterRound(state, 2, 7, 8, 13); | |||||
QuarterRound(state, 3, 4, 9, 14); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -1,588 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
using Org.BouncyCastle.Crypto.Macs; | |||||
using Org.BouncyCastle.Crypto.Modes; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using Org.BouncyCastle.Utilities; | |||||
using System; | |||||
namespace Shadowsocks.Net.Crypto.Extensions | |||||
{ | |||||
public sealed class XChaCha20Poly1305 : IAeadCipher | |||||
{ | |||||
private enum State | |||||
{ | |||||
Uninitialized = 0, | |||||
EncInit = 1, | |||||
EncAad = 2, | |||||
EncData = 3, | |||||
EncFinal = 4, | |||||
DecInit = 5, | |||||
DecAad = 6, | |||||
DecData = 7, | |||||
DecFinal = 8, | |||||
} | |||||
private const int BufSize = 64; | |||||
private const int KeySize = 32; | |||||
private const int NonceSize = 24; | |||||
private const int MacSize = 16; | |||||
private static readonly byte[] Zeroes = new byte[MacSize - 1]; | |||||
private const ulong AadLimit = ulong.MaxValue; | |||||
private const ulong DataLimit = ((1UL << 32) - 1) * 64; | |||||
private readonly XChaCha20Engine mChacha20; | |||||
private readonly IMac mPoly1305; | |||||
private readonly byte[] mKey = new byte[KeySize]; | |||||
private readonly byte[] mNonce = new byte[NonceSize]; | |||||
private readonly byte[] mBuf = new byte[BufSize + MacSize]; | |||||
private readonly byte[] mMac = new byte[MacSize]; | |||||
private byte[] mInitialAad; | |||||
private ulong mAadCount; | |||||
private ulong mDataCount; | |||||
private State mState = State.Uninitialized; | |||||
private int mBufPos; | |||||
public XChaCha20Poly1305() : this(new Poly1305()) | |||||
{ | |||||
} | |||||
public XChaCha20Poly1305(IMac poly1305) | |||||
{ | |||||
if (null == poly1305) | |||||
{ | |||||
throw new ArgumentNullException(nameof(poly1305)); | |||||
} | |||||
if (MacSize != poly1305.GetMacSize()) | |||||
{ | |||||
throw new ArgumentException("must be a 128-bit MAC", nameof(poly1305)); | |||||
} | |||||
mChacha20 = new XChaCha20Engine(); | |||||
mPoly1305 = poly1305; | |||||
} | |||||
public string AlgorithmName => @"XChaCha20Poly1305"; | |||||
public void Init(bool forEncryption, ICipherParameters parameters) | |||||
{ | |||||
KeyParameter initKeyParam; | |||||
byte[] initNonce; | |||||
ICipherParameters chacha20Params; | |||||
if (parameters is AeadParameters aeadParams) | |||||
{ | |||||
var macSizeBits = aeadParams.MacSize; | |||||
if (MacSize * 8 != macSizeBits) | |||||
{ | |||||
throw new ArgumentException("Invalid value for MAC size: " + macSizeBits); | |||||
} | |||||
initKeyParam = aeadParams.Key; | |||||
initNonce = aeadParams.GetNonce(); | |||||
chacha20Params = new ParametersWithIV(initKeyParam, initNonce); | |||||
mInitialAad = aeadParams.GetAssociatedText(); | |||||
} | |||||
else if (parameters is ParametersWithIV ivParams) | |||||
{ | |||||
initKeyParam = (KeyParameter)ivParams.Parameters; | |||||
initNonce = ivParams.GetIV(); | |||||
chacha20Params = ivParams; | |||||
mInitialAad = null; | |||||
} | |||||
else | |||||
{ | |||||
throw new ArgumentException("invalid parameters passed to ChaCha20Poly1305", nameof(parameters)); | |||||
} | |||||
// Validate key | |||||
if (null == initKeyParam) | |||||
{ | |||||
if (State.Uninitialized == mState) | |||||
{ | |||||
throw new ArgumentException("Key must be specified in initial init"); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (KeySize != initKeyParam.GetKey().Length) | |||||
{ | |||||
throw new ArgumentException("Key must be 256 bits"); | |||||
} | |||||
} | |||||
// Validate nonce | |||||
if (null == initNonce || NonceSize != initNonce.Length) | |||||
{ | |||||
throw new ArgumentException("Nonce must be 192 bits"); | |||||
} | |||||
// Check for encryption with reused nonce | |||||
if (State.Uninitialized != mState && forEncryption && Arrays.AreEqual(mNonce, initNonce)) | |||||
{ | |||||
if (null == initKeyParam || Arrays.AreEqual(mKey, initKeyParam.GetKey())) | |||||
{ | |||||
throw new ArgumentException("cannot reuse nonce for ChaCha20Poly1305 encryption"); | |||||
} | |||||
} | |||||
if (null != initKeyParam) | |||||
{ | |||||
Array.Copy(initKeyParam.GetKey(), 0, mKey, 0, KeySize); | |||||
} | |||||
Array.Copy(initNonce, 0, mNonce, 0, NonceSize); | |||||
mChacha20.Init(true, chacha20Params); | |||||
mState = forEncryption ? State.EncInit : State.DecInit; | |||||
Reset(true, false); | |||||
} | |||||
public int GetOutputSize(int len) | |||||
{ | |||||
var total = Math.Max(0, len) + mBufPos; | |||||
switch (mState) | |||||
{ | |||||
case State.DecInit: | |||||
case State.DecAad: | |||||
case State.DecData: | |||||
return Math.Max(0, total - MacSize); | |||||
case State.EncInit: | |||||
case State.EncAad: | |||||
case State.EncData: | |||||
return total + MacSize; | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
} | |||||
public int GetUpdateOutputSize(int len) | |||||
{ | |||||
var total = Math.Max(0, len) + mBufPos; | |||||
switch (mState) | |||||
{ | |||||
case State.DecInit: | |||||
case State.DecAad: | |||||
case State.DecData: | |||||
total = Math.Max(0, total - MacSize); | |||||
break; | |||||
case State.EncInit: | |||||
case State.EncAad: | |||||
case State.EncData: | |||||
break; | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
return total - total % BufSize; | |||||
} | |||||
public void ProcessAadByte(byte input) | |||||
{ | |||||
CheckAad(); | |||||
mAadCount = IncrementCount(mAadCount, 1, AadLimit); | |||||
mPoly1305.Update(input); | |||||
} | |||||
public void ProcessAadBytes(byte[] inBytes, int inOff, int len) | |||||
{ | |||||
if (null == inBytes) | |||||
{ | |||||
throw new ArgumentNullException(nameof(inBytes)); | |||||
} | |||||
if (inOff < 0) | |||||
{ | |||||
throw new ArgumentException("cannot be negative", nameof(inOff)); | |||||
} | |||||
if (len < 0) | |||||
{ | |||||
throw new ArgumentException("cannot be negative", nameof(len)); | |||||
} | |||||
Check.DataLength(inBytes, inOff, len, "input buffer too short"); | |||||
CheckAad(); | |||||
if (len > 0) | |||||
{ | |||||
mAadCount = IncrementCount(mAadCount, (uint)len, AadLimit); | |||||
mPoly1305.BlockUpdate(inBytes, inOff, len); | |||||
} | |||||
} | |||||
public int ProcessByte(byte input, byte[] outBytes, int outOff) | |||||
{ | |||||
CheckData(); | |||||
switch (mState) | |||||
{ | |||||
case State.DecData: | |||||
{ | |||||
mBuf[mBufPos] = input; | |||||
if (++mBufPos == mBuf.Length) | |||||
{ | |||||
mPoly1305.BlockUpdate(mBuf, 0, BufSize); | |||||
ProcessData(mBuf, 0, BufSize, outBytes, outOff); | |||||
Array.Copy(mBuf, BufSize, mBuf, 0, MacSize); | |||||
mBufPos = MacSize; | |||||
return BufSize; | |||||
} | |||||
return 0; | |||||
} | |||||
case State.EncData: | |||||
{ | |||||
mBuf[mBufPos] = input; | |||||
if (++mBufPos == BufSize) | |||||
{ | |||||
ProcessData(mBuf, 0, BufSize, outBytes, outOff); | |||||
mPoly1305.BlockUpdate(outBytes, outOff, BufSize); | |||||
mBufPos = 0; | |||||
return BufSize; | |||||
} | |||||
return 0; | |||||
} | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
} | |||||
public int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff) | |||||
{ | |||||
if (null == inBytes) | |||||
{ | |||||
throw new ArgumentNullException(nameof(inBytes)); | |||||
} | |||||
if (null == outBytes) | |||||
{ | |||||
throw new ArgumentNullException(nameof(outBytes)); | |||||
} | |||||
if (inOff < 0) | |||||
{ | |||||
throw new ArgumentException("cannot be negative", nameof(inOff)); | |||||
} | |||||
if (len < 0) | |||||
{ | |||||
throw new ArgumentException("cannot be negative", nameof(len)); | |||||
} | |||||
Check.DataLength(inBytes, inOff, len, "input buffer too short"); | |||||
if (outOff < 0) | |||||
{ | |||||
throw new ArgumentException("cannot be negative", nameof(outOff)); | |||||
} | |||||
CheckData(); | |||||
var resultLen = 0; | |||||
switch (mState) | |||||
{ | |||||
case State.DecData: | |||||
{ | |||||
for (var i = 0; i < len; ++i) | |||||
{ | |||||
mBuf[mBufPos] = inBytes[inOff + i]; | |||||
if (++mBufPos == mBuf.Length) | |||||
{ | |||||
mPoly1305.BlockUpdate(mBuf, 0, BufSize); | |||||
ProcessData(mBuf, 0, BufSize, outBytes, outOff + resultLen); | |||||
Array.Copy(mBuf, BufSize, mBuf, 0, MacSize); | |||||
mBufPos = MacSize; | |||||
resultLen += BufSize; | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case State.EncData: | |||||
{ | |||||
if (mBufPos != 0) | |||||
{ | |||||
while (len > 0) | |||||
{ | |||||
--len; | |||||
mBuf[mBufPos] = inBytes[inOff++]; | |||||
if (++mBufPos == BufSize) | |||||
{ | |||||
ProcessData(mBuf, 0, BufSize, outBytes, outOff); | |||||
mPoly1305.BlockUpdate(outBytes, outOff, BufSize); | |||||
mBufPos = 0; | |||||
resultLen = BufSize; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
while (len >= BufSize) | |||||
{ | |||||
ProcessData(inBytes, inOff, BufSize, outBytes, outOff + resultLen); | |||||
mPoly1305.BlockUpdate(outBytes, outOff + resultLen, BufSize); | |||||
inOff += BufSize; | |||||
len -= BufSize; | |||||
resultLen += BufSize; | |||||
} | |||||
if (len > 0) | |||||
{ | |||||
Array.Copy(inBytes, inOff, mBuf, 0, len); | |||||
mBufPos = len; | |||||
} | |||||
break; | |||||
} | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
return resultLen; | |||||
} | |||||
public int DoFinal(byte[] outBytes, int outOff) | |||||
{ | |||||
if (null == outBytes) | |||||
{ | |||||
throw new ArgumentNullException(nameof(outBytes)); | |||||
} | |||||
if (outOff < 0) | |||||
{ | |||||
throw new ArgumentException("cannot be negative", nameof(outOff)); | |||||
} | |||||
CheckData(); | |||||
Array.Clear(mMac, 0, MacSize); | |||||
int resultLen; | |||||
switch (mState) | |||||
{ | |||||
case State.DecData: | |||||
{ | |||||
if (mBufPos < MacSize) | |||||
{ | |||||
throw new InvalidCipherTextException("data too short"); | |||||
} | |||||
resultLen = mBufPos - MacSize; | |||||
Check.OutputLength(outBytes, outOff, resultLen, "output buffer too short"); | |||||
if (resultLen > 0) | |||||
{ | |||||
mPoly1305.BlockUpdate(mBuf, 0, resultLen); | |||||
ProcessData(mBuf, 0, resultLen, outBytes, outOff); | |||||
} | |||||
FinishData(State.DecFinal); | |||||
if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen)) | |||||
{ | |||||
throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed"); | |||||
} | |||||
break; | |||||
} | |||||
case State.EncData: | |||||
{ | |||||
resultLen = mBufPos + MacSize; | |||||
Check.OutputLength(outBytes, outOff, resultLen, "output buffer too short"); | |||||
if (mBufPos > 0) | |||||
{ | |||||
ProcessData(mBuf, 0, mBufPos, outBytes, outOff); | |||||
mPoly1305.BlockUpdate(outBytes, outOff, mBufPos); | |||||
} | |||||
FinishData(State.EncFinal); | |||||
Array.Copy(mMac, 0, outBytes, outOff + mBufPos, MacSize); | |||||
break; | |||||
} | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
Reset(false, true); | |||||
return resultLen; | |||||
} | |||||
public byte[] GetMac() | |||||
{ | |||||
return Arrays.Clone(mMac); | |||||
} | |||||
public void Reset() | |||||
{ | |||||
Reset(true, true); | |||||
} | |||||
private void CheckAad() | |||||
{ | |||||
switch (mState) | |||||
{ | |||||
case State.DecInit: | |||||
mState = State.DecAad; | |||||
break; | |||||
case State.EncInit: | |||||
mState = State.EncAad; | |||||
break; | |||||
case State.DecAad: | |||||
case State.EncAad: | |||||
break; | |||||
case State.EncFinal: | |||||
throw new InvalidOperationException("ChaCha20Poly1305 cannot be reused for encryption"); | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
} | |||||
private void CheckData() | |||||
{ | |||||
switch (mState) | |||||
{ | |||||
case State.DecInit: | |||||
case State.DecAad: | |||||
FinishAad(State.DecData); | |||||
break; | |||||
case State.EncInit: | |||||
case State.EncAad: | |||||
FinishAad(State.EncData); | |||||
break; | |||||
case State.DecData: | |||||
case State.EncData: | |||||
break; | |||||
case State.EncFinal: | |||||
throw new InvalidOperationException("ChaCha20Poly1305 cannot be reused for encryption"); | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
} | |||||
private void FinishAad(State nextState) | |||||
{ | |||||
PadMac(mAadCount); | |||||
mState = nextState; | |||||
} | |||||
private void FinishData(State nextState) | |||||
{ | |||||
PadMac(mDataCount); | |||||
var lengths = new byte[16]; | |||||
Pack.UInt64_To_LE(mAadCount, lengths, 0); | |||||
Pack.UInt64_To_LE(mDataCount, lengths, 8); | |||||
mPoly1305.BlockUpdate(lengths, 0, 16); | |||||
mPoly1305.DoFinal(mMac, 0); | |||||
mState = nextState; | |||||
} | |||||
private ulong IncrementCount(ulong count, uint increment, ulong limit) | |||||
{ | |||||
if (count > limit - increment) | |||||
{ | |||||
throw new InvalidOperationException("Limit exceeded"); | |||||
} | |||||
return count + increment; | |||||
} | |||||
private void InitMac() | |||||
{ | |||||
var firstBlock = new byte[64]; | |||||
try | |||||
{ | |||||
mChacha20.ProcessBytes(firstBlock, 0, 64, firstBlock, 0); | |||||
mPoly1305.Init(new KeyParameter(firstBlock, 0, 32)); | |||||
} | |||||
finally | |||||
{ | |||||
Array.Clear(firstBlock, 0, 64); | |||||
} | |||||
} | |||||
private void PadMac(ulong count) | |||||
{ | |||||
var partial = (int)count % MacSize; | |||||
if (0 != partial) | |||||
{ | |||||
mPoly1305.BlockUpdate(Zeroes, 0, MacSize - partial); | |||||
} | |||||
} | |||||
private void ProcessData(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff) | |||||
{ | |||||
Check.OutputLength(outBytes, outOff, inLen, "output buffer too short"); | |||||
mChacha20.ProcessBytes(inBytes, inOff, inLen, outBytes, outOff); | |||||
mDataCount = IncrementCount(mDataCount, (uint)inLen, DataLimit); | |||||
} | |||||
private void Reset(bool clearMac, bool resetCipher) | |||||
{ | |||||
Array.Clear(mBuf, 0, mBuf.Length); | |||||
if (clearMac) | |||||
{ | |||||
Array.Clear(mMac, 0, mMac.Length); | |||||
} | |||||
mAadCount = 0UL; | |||||
mDataCount = 0UL; | |||||
mBufPos = 0; | |||||
switch (mState) | |||||
{ | |||||
case State.DecInit: | |||||
case State.EncInit: | |||||
break; | |||||
case State.DecAad: | |||||
case State.DecData: | |||||
case State.DecFinal: | |||||
mState = State.DecInit; | |||||
break; | |||||
case State.EncAad: | |||||
case State.EncData: | |||||
case State.EncFinal: | |||||
mState = State.EncFinal; | |||||
return; | |||||
default: | |||||
throw new InvalidOperationException(); | |||||
} | |||||
if (resetCipher) | |||||
{ | |||||
mChacha20.Reset(); | |||||
} | |||||
InitMac(); | |||||
if (null != mInitialAad) | |||||
{ | |||||
ProcessAadBytes(mInitialAad, 0, mInitialAad.Length); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -2,7 +2,7 @@ using System; | |||||
namespace Shadowsocks.Net.Crypto | namespace Shadowsocks.Net.Crypto | ||||
{ | { | ||||
public interface ICrypto | |||||
public interface ICrypto : IDisposable | |||||
{ | { | ||||
int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher); | int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher); | ||||
int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher); | int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher); | ||||
@@ -1,63 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto.Engines; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using Shadowsocks.Net.Crypto.Extensions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Runtime.CompilerServices; | |||||
namespace Shadowsocks.Net.Crypto.Stream | |||||
{ | |||||
public class StreamAesCfbBouncyCastleCrypto : StreamCrypto | |||||
{ | |||||
private readonly MyCfbBlockCipher b; | |||||
public StreamAesCfbBouncyCastleCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
b = new MyCfbBlockCipher(new AesEngine(), 128); | |||||
} | |||||
protected override void InitCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(iv, isEncrypt); | |||||
b.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); | |||||
} | |||||
protected override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
return CipherUpdate(plain, cipher); | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
return CipherUpdate(cipher, plain); | |||||
} | |||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output) | |||||
{ | |||||
var i = input.ToArray(); | |||||
var o = new byte[i.Length]; | |||||
var res = b.ProcessBlock(i, 0, o, 0); | |||||
o.CopyTo(output); | |||||
return res; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"aes-128-cfb",new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||||
{"aes-192-cfb",new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||||
{"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -1,59 +0,0 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
using Org.BouncyCastle.Crypto.Engines; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Shadowsocks.Net.Crypto.Stream | |||||
{ | |||||
public class StreamChachaBouncyCastleCrypto : StreamCrypto | |||||
{ | |||||
private readonly BufferedCipherBase _encryptor; | |||||
public StreamChachaBouncyCastleCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
_encryptor = new BufferedStreamCipher(new ChaCha7539Engine()); | |||||
} | |||||
protected override void InitCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(iv, isEncrypt); | |||||
_encryptor.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); | |||||
} | |||||
protected override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
return CipherUpdate(plain, cipher); | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
return CipherUpdate(cipher, plain); | |||||
} | |||||
protected virtual int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output) | |||||
{ | |||||
var i = input.ToArray(); | |||||
var o = new byte[_encryptor.GetOutputSize(i.Length)]; | |||||
var res = _encryptor.ProcessBytes(i, 0, i.Length, o, 0); | |||||
o.CopyTo(output); | |||||
return res; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -0,0 +1,89 @@ | |||||
#nullable enable | |||||
using CryptoBase; | |||||
using CryptoBase.Abstractions.SymmetricCryptos; | |||||
using CryptoBase.Digests.MD5; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Runtime.CompilerServices; | |||||
namespace Shadowsocks.Net.Crypto.Stream | |||||
{ | |||||
public class StreamCryptoBaseCrypto : StreamCrypto | |||||
{ | |||||
private IStreamCrypto? _crypto; | |||||
public StreamCryptoBaseCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
protected override void InitCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(iv, isEncrypt); | |||||
_crypto?.Dispose(); | |||||
if (cipherFamily == CipherFamily.Rc4Md5) | |||||
{ | |||||
Span<byte> temp = stackalloc byte[keyLen + ivLen]; | |||||
var realKey = new byte[MD5Length]; | |||||
key.CopyTo(temp); | |||||
iv.CopyTo(temp.Slice(keyLen)); | |||||
MD5Utils.Fast440(temp, realKey); | |||||
_crypto = StreamCryptoCreate.Rc4(realKey); | |||||
return; | |||||
} | |||||
_crypto = cipherFamily switch | |||||
{ | |||||
CipherFamily.AesCfb => StreamCryptoCreate.AesCfb(isEncrypt, key, iv), | |||||
CipherFamily.Chacha20 => StreamCryptoCreate.ChaCha20(key, iv), | |||||
CipherFamily.Rc4 => StreamCryptoCreate.Rc4(key), | |||||
_ => throw new NotSupportedException() | |||||
}; | |||||
} | |||||
protected override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
return CipherUpdate(plain, cipher); | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
return CipherUpdate(cipher, plain); | |||||
} | |||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output) | |||||
{ | |||||
_crypto!.Update(input, output); | |||||
return input.Length; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new() | |||||
{ | |||||
{ "aes-128-cfb", new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) }, | |||||
{ "aes-192-cfb", new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) }, | |||||
{ "aes-256-cfb", new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) }, | |||||
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) }, | |||||
{ "rc4", new CipherInfo("rc4", 16, 0, CipherFamily.Rc4) }, | |||||
{ "rc4-md5", new CipherInfo("rc4-md5", 16, 16, CipherFamily.Rc4Md5) }, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
public override void Dispose() | |||||
{ | |||||
_crypto?.Dispose(); | |||||
} | |||||
} | |||||
} |
@@ -39,5 +39,7 @@ namespace Shadowsocks.Net.Crypto.Stream | |||||
return _ciphers; | return _ciphers; | ||||
} | } | ||||
#endregion | #endregion | ||||
public override void Dispose() { } | |||||
} | } | ||||
} | } |
@@ -1,121 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Runtime.CompilerServices; | |||||
namespace Shadowsocks.Net.Crypto.Stream | |||||
{ | |||||
public class StreamRc4NativeCrypto : StreamCrypto | |||||
{ | |||||
byte[] realkey = new byte[256]; | |||||
byte[] sbox = new byte[256]; | |||||
public StreamRc4NativeCrypto(string method, string password) : base(method, password) | |||||
{ | |||||
} | |||||
protected override void InitCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
base.InitCipher(iv, isEncrypt); | |||||
// rc4-md5 is rc4 with md5 based session key | |||||
if (cipherFamily == CipherFamily.Rc4Md5) | |||||
{ | |||||
byte[] temp = new byte[keyLen + ivLen]; | |||||
Array.Copy(key, 0, temp, 0, keyLen); | |||||
Array.Copy(iv, 0, temp, keyLen, ivLen); | |||||
realkey = CryptoUtils.MD5(temp); | |||||
} | |||||
else | |||||
{ | |||||
realkey = key; | |||||
} | |||||
sbox = SBox(realkey); | |||||
} | |||||
protected override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher) | |||||
{ | |||||
return CipherUpdate(plain, cipher); | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher) | |||||
{ | |||||
return CipherUpdate(cipher, plain); | |||||
} | |||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
private int CipherUpdate(ReadOnlySpan<byte> i, Span<byte> o) | |||||
{ | |||||
// don't know why we need third array, but it works... | |||||
Span<byte> t = new byte[i.Length]; | |||||
i.CopyTo(t); | |||||
RC4(sbox, t, t.Length); | |||||
t.CopyTo(o); | |||||
return t.Length; | |||||
} | |||||
#region Cipher Info | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
// original RC4 doesn't use IV | |||||
{ "rc4", new CipherInfo("rc4", 16, 0, CipherFamily.Rc4) }, | |||||
{ "rc4-md5", new CipherInfo("rc4-md5", 16, 16, CipherFamily.Rc4Md5) }, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> GetCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
#region RC4 | |||||
int index1 = 0; | |||||
int index2 = 0; | |||||
private byte[] SBox(byte[] key) | |||||
{ | |||||
byte[] s = new byte[256]; | |||||
for (int i = 0; i < 256; i++) | |||||
{ | |||||
s[i] = (byte)i; | |||||
} | |||||
for (int i = 0, j = 0; i < 256; i++) | |||||
{ | |||||
j = (j + key[i % key.Length] + s[i]) & 255; | |||||
Swap(s, i, j); | |||||
} | |||||
return s; | |||||
} | |||||
private void RC4(Span<byte> s, Span<byte> data, int length) | |||||
{ | |||||
for (int n = 0; n < length; n++) | |||||
{ | |||||
byte b = data[n]; | |||||
index1 = (index1 + 1) & 255; | |||||
index2 = (index2 + s[index1]) & 255; | |||||
Swap(s, index1, index2); | |||||
data[n] = (byte)(b ^ s[(s[index1] + s[index2]) & 255]); | |||||
} | |||||
} | |||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||||
private static void Swap(Span<byte> s, int i, int j) | |||||
{ | |||||
byte c = s[i]; | |||||
s[i] = s[j]; | |||||
s[j] = c; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -5,7 +5,7 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.8" /> | |||||
<PackageReference Include="CryptoBase" Version="1.1.1" /> | |||||
<PackageReference Include="Splat" Version="9.8.1" /> | <PackageReference Include="Splat" Version="9.8.1" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -291,6 +291,9 @@ namespace Shadowsocks.Net | |||||
{ | { | ||||
_connection.Shutdown(SocketShutdown.Both); | _connection.Shutdown(SocketShutdown.Both); | ||||
_connection.Close(); | _connection.Close(); | ||||
encryptor?.Dispose(); | |||||
decryptor?.Dispose(); | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
@@ -93,7 +93,7 @@ namespace Shadowsocks.Net | |||||
public async Task SendAsync(ReadOnlyMemory<byte> data) | public async Task SendAsync(ReadOnlyMemory<byte> data) | ||||
{ | { | ||||
ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); | |||||
using ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); | |||||
using IMemoryOwner<byte> mem = pool.Rent(data.Length + 1000); | using IMemoryOwner<byte> mem = pool.Rent(data.Length + 1000); | ||||
// byte[] dataOut = new byte[slicedData.Length + 1000]; | // byte[] dataOut = new byte[slicedData.Length + 1000]; | ||||
@@ -120,7 +120,7 @@ namespace Shadowsocks.Net | |||||
using IMemoryOwner<byte> owner = pool.Rent(bytesRead + 3); | using IMemoryOwner<byte> owner = pool.Rent(bytesRead + 3); | ||||
Memory<byte> o = owner.Memory; | Memory<byte> o = owner.Memory; | ||||
ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); | |||||
using ICrypto encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password); | |||||
int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead)); | int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead)); | ||||
this.Log().Debug($"{_remoteEndPoint} {_localEndPoint} {outlen} UDP Relay down"); | this.Log().Debug($"{_remoteEndPoint} {_localEndPoint} {outlen} UDP Relay down"); | ||||
if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment<byte> data)) | if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment<byte> data)) | ||||