using System.Security.Cryptography;
using NewLife.Collections;
namespace NewLife.Security;
/// <summary>CBC块密码模式</summary>
/// <remarks>
/// 密码块链 (CBC) 模式引入了反馈。 每个纯文本块在加密前,通过按位“异或”操作与前一个块的密码文本结合。
/// 这样确保了即使纯文本包含许多相同的块,这些块中的每一个也会加密为不同的密码文本块。
/// 在加密块之前,初始化向量通过按位“异或”操作与第一个纯文本块结合。
/// 如果密码文本块中有一个位出错,相应的纯文本块也将出错。
/// 此外,后面的块中与原出错位的位置相同的位也将出错。
/// </remarks>
public sealed class CbcTransform : ICryptoTransform
{
#region 属性
private readonly ICryptoTransform _transform;
private readonly Boolean _encryptMode;
private readonly Byte[] _iv;
private readonly Byte[] _lastBlock;
/// <summary>获取一个值,该值指示是否可以转换多个块。</summary>
public Boolean CanTransformMultipleBlocks => true;
/// <summary>获取一个值,该值指示是否可重复使用当前转换。</summary>
public Boolean CanReuseTransform => _transform.CanReuseTransform;
/// <summary>获取输入块大小。</summary>
public Int32 InputBlockSize => _transform.InputBlockSize;
/// <summary>获取输出块大小。</summary>
public Int32 OutputBlockSize => _transform.OutputBlockSize;
#endregion
#region 构造
/// <summary>实例化</summary>
/// <param name="transform"></param>
/// <param name="iv"></param>
/// <param name="encryptMode"></param>
/// <exception cref="CryptographicException"></exception>
public CbcTransform(ICryptoTransform transform, Byte[]? iv, Boolean encryptMode)
{
_transform = transform;
_encryptMode = encryptMode;
_lastBlock = new Byte[transform.InputBlockSize];
if (transform.InputBlockSize != transform.OutputBlockSize) throw new CryptographicException();
if (iv == null || iv.Length != transform.InputBlockSize) throw new CryptographicException("IV length mismatch");
iv.AsSpan(0, transform.InputBlockSize).CopyTo(_lastBlock);
_iv = (Byte[])_lastBlock.Clone();
}
/// <summary>销毁</summary>
public void Dispose() => _transform.Dispose();
#endregion
/// <summary>转换输入字节数组的指定区域,并将所得到的转换复制到输出字节数组的指定区域。</summary>
/// <param name="inputBuffer"></param>
/// <param name="inputOffset"></param>
/// <param name="inputCount"></param>
/// <param name="outputBuffer"></param>
/// <param name="outputOffset"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public Int32 TransformBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[] outputBuffer, Int32 outputOffset)
{
if (inputCount % InputBlockSize != 0)
throw new ArgumentOutOfRangeException(nameof(inputCount));
var blocks = inputCount / InputBlockSize;
while (blocks > 0)
{
TransformOneBlock(inputBuffer, inputOffset, outputBuffer, outputOffset, false);
blocks -= 1;
inputOffset += InputBlockSize;
outputOffset += OutputBlockSize;
}
return inputCount;
}
private void TransformOneBlock(Byte[] inputBuffer, Int32 inputOffset, Byte[] outputBuffer, Int32 outputOffset, Boolean signalFinalBlock)
{
using var imm = Pool.Rent(InputBlockSize);
var immSpan = imm.Span;
inputBuffer.AsSpan(inputOffset, InputBlockSize).CopyTo(immSpan);
if (_encryptMode)
{
var lastSpan = _lastBlock.AsSpan();
for (var i = 0; i < InputBlockSize; i++)
immSpan[i] ^= lastSpan[i];
}
if (signalFinalBlock)
{
var lastBlock = _transform.TransformFinalBlock(imm, 0, InputBlockSize);
lastBlock.AsSpan(0, InputBlockSize).CopyTo(outputBuffer.AsSpan(outputOffset));
}
else
_transform.TransformBlock(imm, 0, InputBlockSize, outputBuffer, outputOffset);
if (!_encryptMode)
{
var outSpan = outputBuffer.AsSpan(outputOffset, InputBlockSize);
var lastSpan = _lastBlock.AsSpan();
for (var i = 0; i < InputBlockSize; i++)
outSpan[i] ^= lastSpan[i];
immSpan.CopyTo(lastSpan);
}
else
outputBuffer.AsSpan(outputOffset, InputBlockSize).CopyTo(_lastBlock.AsSpan());
}
/// <summary>转换指定字节数组的指定区域。</summary>
/// <param name="inputBuffer"></param>
/// <param name="inputOffset"></param>
/// <param name="inputCount"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public Byte[] TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
{
if (inputCount == 0) return [];
var blocks = inputCount / InputBlockSize;
var output = new Byte[blocks * OutputBlockSize];
if (blocks > 1)
TransformBlock(inputBuffer, inputOffset, inputCount - InputBlockSize, output, 0);
if (blocks >= 1)
TransformOneBlock(inputBuffer, inputOffset + inputCount - InputBlockSize, output, output.Length - InputBlockSize, true);
else
output = _transform.TransformFinalBlock(inputBuffer, inputOffset, inputCount);
_iv.AsSpan().CopyTo(_lastBlock);
return output;
}
}
|