【问题标题】:Java BC SicBlockCipher direct output equivalent in c#Java BC SicBlockCipher 直接输出等效于 c#
【发布时间】:2018-12-19 13:30:36
【问题描述】:

我正在用 C# 实现一些东西,对此我有一个单独的规范并且对我需要做什么有相当清晰的理解,但同时作为参考,我有一个 Java 实现,并希望遵循 Java 实现这个案子尽量接近。

代码涉及加密流,Java源码为here 相关行在这里:

  private final StreamCipher enc;
...
  BlockCipher cipher;
  enc = new SICBlockCipher(cipher = new AESEngine());
  enc.init(true, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[cipher.getBlockSize()]));
...
...
byte[] ptype = RLP.encodeInt((int) frame.type); //Result can be a single byte long
...
...
enc.processBytes(ptype, 0, ptype.length, buff, 0);
out.write(buff, 0, ptype.length); //encrypt and write a single byte from the SICBlockCipher stream

上面的 Java BouncyCastle SicBlockCipher 是一个 StreamCipher,允许处理小于 Aes 块大小的单个或少量字节。

在 c# BouncyCastle 中,SicBlockCipher 仅提供 ProcessBlock,而 BufferedBlockCipher 似乎没有提供使用 ProcessBytes 保证输出的方法。

我需要用 C# BouncyCastle 库做什么才能实现等效功能?

【问题讨论】:

    标签: java c# aes bouncycastle public-key-encryption


    【解决方案1】:

    不幸的是,SicBlockCipher 本身并未实现为流密码,因此(确实)无法直接使用此功能。

    BufferedBlockCipher 在创建时考虑了许多不同的操作模式。它缓冲输入,而对于SicBlockCipher 实现的计数器(CTR)模式,您需要缓冲加密的计数器块。

    加密的计数器块构成密钥流,然后可以与明文异或以创建密文流(或者实际上,与密文再次检索明文,加密是计数器模式的解密)。

    我知道如何做到这一点的唯一方法是创建您自己的 IBlockCipher 实现并实现上述功能。


    这里是作为流密码的计数器模式...

    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Modes;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SicStream
    {
        public class SicStreamCipher : IStreamCipher
        {
            private SicBlockCipher parent;
            private int blockSize;
    
            private byte[] zeroBlock;
    
            private byte[] blockBuffer;
            private int processed;
    
            public SicStreamCipher(SicBlockCipher parent)
            {
                this.parent = parent;
                this.blockSize = parent.GetBlockSize();
    
                this.zeroBlock = new byte[blockSize];
    
                this.blockBuffer = new byte[blockSize];
                // indicates that no bytes are available: lazy generation of counter blocks (they may not be needed)
                this.processed = blockSize;
            }
    
            public string AlgorithmName
            {
                get
                {
                    return parent.AlgorithmName;
                }
            }
    
            public void Init(bool forEncryption, ICipherParameters parameters)
            {
                parent.Init(forEncryption, parameters);
    
                Array.Clear(blockBuffer, 0, blockBuffer.Length);
                processed = blockSize;
            }
    
            public void ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
            {
                int inputProcessed = 0;
                while (inputProcessed < length)
                {
                    // NOTE can be optimized further
                    // the number of available bytes can be pre-calculated; too much branching
                    if (processed == blockSize)
                    {
                        // lazilly create a new block of key stream
                        parent.ProcessBlock(zeroBlock, 0, blockBuffer, 0);
                        processed = 0;
                    }
    
                    output[outOff + inputProcessed] = (byte)(input[inOff + inputProcessed] ^ blockBuffer[processed]);
    
                    processed++;
                    inputProcessed++;
                }
            }
    
            public void Reset()
            {
                parent.Reset();
    
                Array.Clear(blockBuffer, 0, blockBuffer.Length);
                this.processed = blockSize;
            }
    
            public byte ReturnByte(byte input)
            {
                if (processed == blockSize)
                {
                    // lazily create a new block of key stream
                    parent.ProcessBlock(zeroBlock, 0, blockBuffer, 0);
                    processed = 0;
                }
                return (byte)(input ^ blockBuffer[processed++]);
            }
        }
    }
    

    ...在这里它被包装,以便可以在使用分组密码操作模式的代码中对其进行改造...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Modes;
    
    namespace SicStream
    {
        /**
         * A class that implements an online Sic (segmented integer counter mode, or just counter (CTR) mode for short).
         * This class buffers one encrypted counter (representing the key stream) at a time.
         * The encryption of the counter is only performed when required, so that no key stream blocks are generated while they are not required.
         */
        public class StreamingSicBlockCipher : BufferedCipherBase
        {
            private SicStreamCipher parent;
            private int blockSize;
    
            public StreamingSicBlockCipher(SicBlockCipher parent)
            {
                this.parent = new SicStreamCipher(parent);
                this.blockSize = parent.GetBlockSize();
            }
    
            public override string AlgorithmName
            {
                get
                {
                    return parent.AlgorithmName;
                }
            }
    
            public override byte[] DoFinal()
            {
                // returns no bytes at all, as there is no input
                return new byte[0];
            }
    
            public override byte[] DoFinal(byte[] input, int inOff, int length)
            {
                byte[] result = ProcessBytes(input, inOff, length);
    
                Reset();
    
                return result;
            }
    
            public override int GetBlockSize()
            {
                return blockSize;
            }
    
            public override int GetOutputSize(int inputLen)
            {
                return inputLen;
            }
    
            public override int GetUpdateOutputSize(int inputLen)
            {
                return inputLen;
            }
    
            public override void Init(bool forEncryption, ICipherParameters parameters)
            {
                parent.Init(forEncryption, parameters);
            }
    
            public override byte[] ProcessByte(byte input)
            {
                return new byte[] { parent.ReturnByte(input) };
            }
    
            public override byte[] ProcessBytes(byte[] input, int inOff, int length)
            {
                byte[] result = new byte[length];
                parent.ProcessBytes(input, inOff, length, result, 0);
                return result;
            }
    
            public override void Reset()
            {
                parent.Reset();
            }
        }
    }
    

    请注意,最后一个代码效率较低,因为需要创建额外的数组。

    【讨论】:

    • 太棒了!非常感谢。为延迟道歉,刚刚回到这个问题,明天将不得不测试,因为英格兰克罗地亚的比赛即将开始。明天更新。
    • 我想知道为什么一个实现将其建模为流密码,而另一个实现为块。似乎因为流密码可以通过分组密码实现,所以在哪里放置 ctr 模式 aes 存在一些歧义。
    • 我想出了另一个变体(见下文)。我想知道从事 BC 工作的人是否应该了解并解决 Java 和 .NET 实现之间的差异。
    【解决方案2】:

    基于 Maarten Bodewes 的有用且内容丰富的答案(非常感谢!)对流式传输和分组密码有了一些顿悟(非常感谢!),我还想出了以下方法。

    .NET BC 库有一个 StreamBlockCipher 类,就像在 Java 中一样,但在它的 ctor 或初始化程序中有一个保护,即底层密码的块大小应该为 1。

    为了使用 StreamBlockCipher,我创建了一个 SicBlockCipher 的子类,它在内部缓冲密钥流块。我将其命名为 StreamableSicBlockCipher。它还没有经过测试,但如果有问题,它至少指出了另一个方向。

     public class StreamableSicBlockCipher : SicBlockCipher
    {
        private int blockSize;
        private int position = 0;
        private byte[] zeroBlock;
        private byte[] keyStreamBlock;
    
        public StreamableSicBlockCipher(IBlockCipher cipher) : base(cipher)
        {
            blockSize=cipher.GetBlockSize();
            zeroBlock = new byte[blockSize];
            keyStreamBlock = new byte[blockSize];
        }
    
    
    
        public override int GetBlockSize()
        {
            return 1;
        }
    
        public override int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
        {
            int keyStreamBlockOffset = position % blockSize;
    
            if (0==keyStreamBlockOffset)
            {
    
                var cipher = GetUnderlyingCipher();
                cipher.ProcessBlock(zeroBlock, 0, keyStreamBlock, 0);
    
                // Increment the counter
                int j = zeroBlock.Length;
                while (--j >= 0 && ++zeroBlock[j] == 0)
                {
                }
            }
    
            output[outOff] = (byte)(input[inOff] ^ keyStreamBlock[keyStreamBlockOffset]);
    
            position++;
    
            return 1;
    
        }
        public override void Reset()
        {
            base.Reset();
            this.position = 0;
    
        }
    

    然后可以使用适当的包装器调用它,如下所示:

    StreamBlockCipher EncCipher = new StreamBlockCipher(new StreamableSicBlockCipher(new AesEngine()));
    

    初始化可以使用 IBlockCipher 的实例来获取块大小。下面的示例使用空 IV 初始化,使用 'Cipher' 这是 AESEngine 的一个实例。:

     EncCipher.Init(true, new ParametersWithIV(new KeyParameter(cryptoSecret), new byte[Cipher.GetBlockSize()]));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-03
      • 2010-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-09
      相关资源
      最近更新 更多