157 lines
5.6 KiB
C#
157 lines
5.6 KiB
C#
using OpenSSL.Core;
|
|
using OpenSSL.Crypto;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace shadowsocks_csharp.Encrypt
|
|
{
|
|
public class OpensslEncryptor
|
|
: EncryptorBase, IDisposable
|
|
{
|
|
public OpensslEncryptor(string method, string password)
|
|
: base(method, password)
|
|
{
|
|
InitKey(Method, Password);
|
|
}
|
|
|
|
public override byte[] Encrypt(byte[] buf, int length)
|
|
{
|
|
if (_encryptCtx == IntPtr.Zero)
|
|
{
|
|
int ivLen = _cipher.IVLength;
|
|
byte[] iv = new byte[ivLen];
|
|
Native.RAND_bytes(iv, iv.Length);
|
|
InitCipher(ref _encryptCtx, iv, true);
|
|
int outLen = length + _cipher.BlockSize;
|
|
byte[] cipherText = new byte[outLen];
|
|
Native.EVP_CipherUpdate(_encryptCtx, cipherText, out outLen, buf, length);
|
|
byte[] result = new byte[outLen + ivLen];
|
|
Buffer.BlockCopy(iv, 0, result, 0, ivLen);
|
|
Buffer.BlockCopy(cipherText, 0, result, ivLen, outLen);
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
int outLen = length + _cipher.BlockSize;
|
|
byte[] cipherText = new byte[outLen];
|
|
Native.EVP_CipherUpdate(_encryptCtx, cipherText, out outLen, buf, length);
|
|
byte[] result = new byte[outLen];
|
|
Buffer.BlockCopy(cipherText, 0, result, 0, outLen);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public override byte[] Decrypt(byte[] buf, int length)
|
|
{
|
|
if (_decryptCtx == IntPtr.Zero)
|
|
{
|
|
int ivLen = _cipher.IVLength;
|
|
byte[] iv = new byte[ivLen];
|
|
Buffer.BlockCopy(buf, 0, iv, 0, ivLen);
|
|
InitCipher(ref _decryptCtx, iv, false);
|
|
int outLen = length + _cipher.BlockSize;
|
|
outLen -= ivLen;
|
|
byte[] cipherText = new byte[outLen];
|
|
byte[] subset = new byte[length - ivLen];
|
|
Buffer.BlockCopy(buf, ivLen, subset, 0, length - ivLen);
|
|
Native.EVP_CipherUpdate(_decryptCtx, cipherText, out outLen, subset, length - ivLen);
|
|
byte[] result = new byte[outLen];
|
|
Buffer.BlockCopy(cipherText, 0, result, 0, outLen);
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
int outLen = length + _cipher.BlockSize;
|
|
byte[] cipherText = new byte[outLen];
|
|
Native.EVP_CipherUpdate(_decryptCtx, cipherText, out outLen, buf, length);
|
|
byte[] result = new byte[outLen];
|
|
Buffer.BlockCopy(cipherText, 0, result, 0, outLen);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<string, byte[]> CachedKeys = new Dictionary<string, byte[]>();
|
|
private static readonly Dictionary<string, Cipher> CachedCiphers = new Dictionary<string, Cipher>();
|
|
private byte[] _key;
|
|
private Cipher _cipher;
|
|
private IntPtr _encryptCtx;
|
|
private IntPtr _decryptCtx;
|
|
|
|
private void InitKey(string password, string method)
|
|
{
|
|
string k = method + ":" + password;
|
|
if (CachedKeys.ContainsKey(k))
|
|
{
|
|
_key = CachedKeys[k];
|
|
_cipher = CachedCiphers[k];
|
|
return;
|
|
}
|
|
_cipher = Cipher.CreateByName(method);
|
|
if (_cipher == null)
|
|
{
|
|
throw new NullReferenceException();
|
|
}
|
|
byte[] passbuf = Encoding.UTF8.GetBytes(password);
|
|
_key = new byte[_cipher.KeyLength];
|
|
byte[] iv = new byte[_cipher.IVLength];
|
|
Native.EVP_BytesToKey(_cipher.Handle, MessageDigest.MD5.Handle, null, passbuf, passbuf.Length, 1, _key, iv);
|
|
CachedKeys[k] = _key;
|
|
CachedCiphers[k] = _cipher;
|
|
}
|
|
|
|
private void InitCipher(ref IntPtr ctx, byte[] iv, bool isCipher)
|
|
{
|
|
ctx = Native.OPENSSL_malloc(Marshal.SizeOf(typeof(CipherContext.EVP_CIPHER_CTX)));
|
|
int enc = isCipher ? 1 : 0;
|
|
Native.EVP_CIPHER_CTX_init(ctx);
|
|
Native.ExpectSuccess(Native.EVP_CipherInit_ex(ctx, _cipher.Handle, IntPtr.Zero, null, null, enc));
|
|
Native.ExpectSuccess(Native.EVP_CIPHER_CTX_set_key_length(ctx, _key.Length));
|
|
Native.ExpectSuccess(Native.EVP_CIPHER_CTX_set_padding(ctx, 1));
|
|
Native.ExpectSuccess(Native.EVP_CipherInit_ex(ctx, _cipher.Handle, IntPtr.Zero, _key, iv, enc));
|
|
}
|
|
|
|
#region IDisposable
|
|
private bool _disposed;
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
~OpensslEncryptor()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
|
|
}
|
|
|
|
if (_encryptCtx != IntPtr.Zero)
|
|
{
|
|
Native.EVP_CIPHER_CTX_cleanup(_encryptCtx);
|
|
Native.OPENSSL_free(_encryptCtx);
|
|
_encryptCtx = IntPtr.Zero;
|
|
}
|
|
if (_decryptCtx != IntPtr.Zero)
|
|
{
|
|
Native.EVP_CIPHER_CTX_cleanup(_decryptCtx);
|
|
Native.OPENSSL_free(_decryptCtx);
|
|
_decryptCtx = IntPtr.Zero;
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|