You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中BigInteger数组与byte数组互转及解密还原问题

问题

我用下面的代码把BigInteger数组转成了byte数组:

var ciphertext = new BigInteger[ciphertextBlocks];
...
byte[] ciphertextBytes = ciphertext.SelectMany(c => c.ToByteArray()).ToArray(); 

现在想还原出原始的BigInteger数组,但转换时每个BigInteger的长度信息丢失了,看起来没法实现。

背景

我知道这可能是个XY问题,我正在实现一个Merkle-Hellman背包密码系统的简易加解密程序。
我清楚不推荐自己实现加密算法,这只是用于学习目的。

因为加密输出通常用byte[]表示,所以我在Encrypt()方法里把生成的密文(BigInteger[])转成了byte[]。但在Decrypt()方法里,我需要访问原始的BigInteger[]数组来逐个解密每个BigInteger,有没有办法做到?

如果是long这类固定大小的基础类型,我可以这么做:

long[] cipherTextInt = new long[ciphertext.Length / sizeof(long)];
Buffer.BlockCopy(ciphertext, 0, cipherTextInt, 0, ciphertext.Length);

但BigInteger的大小是不固定的,该怎么处理?

附完整的KnapsackCryptosystem类代码:

public class KnapsackCryptosystem
{
    const int BitsPerByte = 8;
    
    public PublicKeyInfo PublicKey { get; }

    private PrivateKeyInfo PrivateKey { get; }

    public byte[] Encrypt(byte[] plaintext)
    {

        var plaintextBits = new BitArray(plaintext);
        var sequence = PublicKey.Sequence;
        
        // Calculate number of ciphertext blocks:
        // Dividing the length of the sequence by the bit length of the plaintext gives
        // the number of ciphertext blocks. If the plaintext bit length is not a multiple of 
        // the sequence length (remainder != 0), an extra block is required.
        var (quotient, remainder) = Math.DivRem(plaintextBits.Count, sequence.Count);
        var ciphertextBlocks = quotient + (remainder == 0 ? 0 : 1);

        // Create a BigInteger array to hold the ciphertext integers
        var ciphertext = new BigInteger[ciphertextBlocks];

        // Apply the sequence to each plaintext block
        for (int i = 0; i < ciphertextBlocks; i++)
        {
            for (int j = 0; j < sequence.Count; j++)
            {
                // Calculate the end index of the current block
                int endIndex = (i + 1) * sequence.Count;
                // Calculate the index of the jth bit from the end; 
                int bitIndex = end - j - 1;

                try
                {
                    var bit = plaintextBits[bitIndex];
                    //Console.WriteLine($"j={j} : idx={rIndex} : bit={(bit ? 1 : 0)} | ");
                    Console.WriteLine($"j={j} : idx={index} : bit={(bit ? 1 : 0)} : ");
                    if (bit)
                    {
                        // Add the jth element to the ith ciphertext block if the bit is set
                        Console.WriteLine($"+{sequence[j]} .");
                        ciphertext[i] += sequence[j];
                    }
                }
                catch (ArgumentOutOfRangeException)
                {
                    // thrown at the last block when the sequence length exceeds the last plaintext block
                    Console.WriteLine(nameof(ArgumentOutOfRangeException) + " " + index);
                    break;
                }
            }
            Console.WriteLine();
        }
        Console.WriteLine("Ciphertext Elements:");
        Array.ForEach(ciphertext, x => Console.WriteLine(x));

        // Convert the BigInteger[] array to byte[]
        var ciphertextBytes = ciphertext.SelectMany(c => c.ToByteArray()).ToArray(); 
        return ciphertextBytes;
    }


    public byte[] Decrypt(byte[] ciphertext)
    {
        var sequence = PrivateKey.Sequence;
        int wInverse = ModularArithmetic.MultiplicativeInverse(PrivateKey.W, PrivateKey.M);
        Console.WriteLine($"wInverse: {wInverse}");

        // RESTORE BigInteger[] FROM byte[] ciphertext. How to achieve this?
        // cipherTextInts is the restored BigInteger array containing the ciphertext for each block
        var ciphertextInts = BACK_TO_BIG_INTEGER_ARRAY(ciphertext);
        var plaintextBits = new BitArray(<UNKNOWN_LENGTH>);


        for (int i = 0; i < ciphertextInts.Length; i++)
        {
            var ciphertextElement = (wInverse * ciphertextInts[i]) % PrivateKey.M;

            // Solve the subset sum problem
            for (int j = PrivateKey.Sequence.Count - 1; j >= 0; j--)
            {
                if (PrivateKey.Sequence[j] <= ciphertextElement)
                {
                    // Calculate the end index of the current block
                    int endIndex = (i + 1) * sequence.Count;
                    // Calculate the index of the jth bit from the end; 
                    int bitIndex = end - j - 1;

                    plaintextBits[bitIndex] = true;
                    ciphertextElement -= PrivateKey.Sequence[j];
                    //Console.WriteLine($"    Minus {PrivateKey.Sequence[j]}. element: {ciphertextElement}");
                }
            }
        }

        // Copy BitArray data to byte[] array and return
        var (quotient1, remainder1) = Math.DivRem(plaintextBits.Length, BitsPerByte);
        byte[] bytes = new byte[quotient1 + (remainder1 == 0 ? 0 : 1)];
        plaintextBits.CopyTo(bytes, 0);
        return bytes;
    }


    public static KnapsackCryptosystem Create(
        IReadOnlyList<int> sequence, int m, int w)
    {
        if (sequence.Count == 0)
        {
            throw new ArgumentException(
                "Sequence must be non-empty.", 
                nameof(sequence));
        }
        int sum = sequence[0];
        for (int i = 1; i < sequence.Count; i++)
        {
            if (sequence[i] <= sum)
            {
                throw new ArgumentException(
                    "Not a superincreasing sequence.", 
                    nameof(sequence));
            }
            sum += sequence[i];
        }
        if (m <= sum)
        {
            throw new ArgumentOutOfRangeException(
                nameof(m), 
                "m must be greater than the sum of the sequence.");
        }

        if (MathHelpers.Gcd(m, w) != 1)
        {
            throw new ArgumentException(
                "w must be coprime to m.", 
                nameof(w));
        }

        IReadOnlyList<int> publicSequence = GeneratePublicSequence(sequence, m, w);

        return new KnapsackCryptosystem(
            new PublicKeyInfo(publicSequence),
            new PrivateKeyInfo(sequence, m, w)
        );
    }

    private static IReadOnlyList<int> GeneratePublicSequence(
        IReadOnlyList<int> sequence, int m, int w) => (
            from item in sequence
            select (w * item) % m)
            .ToList()
            .AsReadOnly();    // Multiply each item in the sequence by w and mod by m


    public readonly record struct PublicKeyInfo(IReadOnlyList<int> Sequence);

    // M is the modulus greater than sum of public sequence
    // W is the integer coprime to M
    private readonly record struct PrivateKeyInfo(IReadOnlyList<int> Sequence, int M, int W);

    private KnapsackCryptosystem(PublicKeyInfo publicKey, PrivateKeyInfo privateKey)
    {
        PublicKey = publicKey;
        PrivateKey = privateKey;
    }
}
解决方法

针对你的问题,有两种可行的思路,结合Merkle-Hellman的特性,推荐第二种更贴合场景的方案:

方案一:加密时附加每个BigInteger的长度信息

在将BigInteger[]转成byte[]时,先记录每个BigInteger对应的字节数组长度,再拼接长度数据和字节内容,解密时按长度拆分还原。

修改Encrypt方法的转换逻辑

// 替换原有的转换代码
var ciphertextByteArrays = ciphertext.Select(c => c.ToByteArray()).ToList();
// 计算总字节数:每个元素的长度用4字节int存储,加上所有BigInteger的字节数据
int totalLength = ciphertextByteArrays.Count * sizeof(int);
foreach (var bytes in ciphertextByteArrays)
{
    totalLength += bytes.Length;
}
byte[] ciphertextBytes = new byte[totalLength];
int offset = 0;

// 逐个写入长度和字节数据
foreach (var bytes in ciphertextByteArrays)
{
    // 写入当前BigInteger的字节长度
    byte[] lengthBytes = BitConverter.GetBytes(bytes.Length);
    Buffer.BlockCopy(lengthBytes, 0, ciphertextBytes, offset, lengthBytes.Length);
    offset += lengthBytes.Length;
    // 写入BigInteger的字节内容
    Buffer.BlockCopy(bytes, 0, ciphertextBytes, offset, bytes.Length);
    offset += bytes.Length;
}
return ciphertextBytes;

修改Decrypt方法的还原逻辑

// 替换原有的BACK_TO_BIG_INTEGER_ARRAY部分
List<BigInteger> ciphertextInts = new List<BigInteger>();
int offset = 0;

while (offset < ciphertext.Length)
{
    // 读取当前BigInteger的字节长度
    byte[] lengthBytes = new byte[sizeof(int)];
    Buffer.BlockCopy(ciphertext, offset, lengthBytes, 0, lengthBytes.Length);
    offset += lengthBytes.Length;
    int byteLength = BitConverter.ToInt32(lengthBytes, 0);
    
    // 读取对应长度的字节数组并转换为BigInteger
    byte[] bigIntBytes = new byte[byteLength];
    Buffer.BlockCopy(ciphertext, offset, bigIntBytes, 0, byteLength);
    offset += byteLength;
    ciphertextInts.Add(new BigInteger(bigIntBytes));
}

// 补充明文比特长度的读取(解决<UNKNOWN_LENGTH>问题)
// 需在Encrypt最开头写入plaintextBits.Count,示例:
// byte[] bitLengthBytes = BitConverter.GetBytes(plaintextBits.Count);
// 插入到ciphertextBytes的最前面,这里对应读取:
// int plaintextBitLength = BitConverter.ToInt32(ciphertext, 0);
// offset += sizeof(int); // 上面的循环从offset=sizeof(int)开始
// var plaintextBits = new BitArray(plaintextBitLength);

方案二:利用Merkle-Hellman的特性固定每个BigInteger的字节长度

Merkle-Hellman中,每个密文元素是公钥序列的子集和,其最大值不会超过公钥序列的总和,而公钥序列的元素均小于m(私钥中的模数),因此每个密文BigInteger的字节长度有固定上限。我们可以统一用这个上限长度来存储每个密文元素,解密时直接按固定长度拆分。

修改Encrypt方法的转换逻辑

// 计算单个密文元素的最大字节长度
int sumPrivate = PrivateKey.Sequence.Sum();
// 密文元素最大值小于m,计算m所需的字节数,加1是BigInteger的符号位
int maxByteLength = (BitLength(PrivateKey.M) + 7) / 8 + 1;

byte[] ciphertextBytes = new byte[ciphertext.Length * maxByteLength];
int offset = 0;

foreach (var c in ciphertext)
{
    byte[] bytes = c.ToByteArray();
    // 将字节数组复制到固定长度的位置,不足部分在末尾补0(小端字节序,高位在数组末尾)
    Buffer.BlockCopy(bytes, 0, ciphertextBytes, offset, bytes.Length);
    if (bytes.Length < maxByteLength)
    {
        Array.Fill(ciphertextBytes, (byte)0, offset + bytes.Length, maxByteLength - bytes.Length);
    }
    offset += maxByteLength;
}
return ciphertextBytes;

// 辅助方法:计算整数的二进制位数
private int BitLength(int value)
{
    if (value == 0) return 1;
    return 32 - BitOperations.LeadingZeroCount((uint)value);
}

修改Decrypt方法的还原逻辑

// 计算固定的字节长度
int maxByteLength = (BitLength(PrivateKey.M) + 7) / 8 + 1;
int blockCount = ciphertext.Length / maxByteLength;
BigInteger[] ciphertextInts = new BigInteger[blockCount];

for (int i = 0; i < blockCount; i++)
{
    int offset = i * maxByteLength;
    byte[] bigIntBytes = new byte[maxByteLength];
    Buffer.BlockCopy(ciphertext, offset, bigIntBytes, 0, maxByteLength);
    ciphertextInts[i] = new BigInteger(bigIntBytes);
}

// 解决<UNKNOWN_LENGTH>问题:用blockCount * sequence.Count初始化,解密后截断多余位即可
var plaintextBits = new BitArray(blockCount * sequence.Count);

内容的提问来源于stack exchange,提问作者Amal K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 13:20:28