我有一个在 php 7.2 上运行的应用程序,我需要使用以下条件加密一个字符串:

  • 密码:NCFB
  • 输出编码:Base64
  • 初始化向量 (IV) = 8

  • 我已经知道我应该得到的输出,但是我的脚本返回不同的字符串,我认为是因为 IV(openssl_random_pseude_bytes),我无法真正理解它的逻辑。我在加密方面没有太多经验,所以我无法弄清楚。
    $string = 'my-string';
    $cipher = 'BF-CFB';
    $key = 'my-secret-key';
    $ivlen = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivlen);
    
    $encrypted = base64_encode(openssl_encrypt($string, $cipher, $key, OPENSSL_RAW_DATA, $iv));
    

    示例

    此加密的目标是 API 访问,并且提供了一个用 C# 编写的示例用于加密方法。问题是该脚本每次都生成相同的字符串,这与我的不同。我必须构建我的脚本,以便获得与提供的官方示例相同的结果(这是一个代码片段:)
    public new int Encrypt(
        byte[] dataIn,
        int posIn,
        byte[] dataOut,
        int posOut,
        int count)
    {
        int end = posIn + count;
    ​
        byte[] iv = this.iv;
    ​
        int ivBytesLeft = this.ivBytesLeft;
        int ivPos = iv.Length - ivBytesLeft;
    ​
        // consume what's left in the IV buffer, but make sure to keep the new
        // ciphertext in a round-robin fashion (since it represents the new IV)
        if (ivBytesLeft >= count)
        {
            // what we have is enough to deal with the request
            for (; posIn < end; posIn++, posOut++, ivPos++)
            {
                iv[ivPos] = dataOut[posOut] = (byte)(dataIn[posIn] ^ iv[ivPos]);
            }
            this.ivBytesLeft = iv.Length - ivPos;
            return count;
        }
        for (; ivPos < BLOCK_SIZE; posIn++, posOut++, ivPos++)
        {
            iv[ivPos] = dataOut[posOut] = (byte)(dataIn[posIn] ^ iv[ivPos]);
        }
        count -= ivBytesLeft;
    ​
        uint[] sbox1 = this.sbox1;
        uint[] sbox2 = this.sbox2;
        uint[] sbox3 = this.sbox3;
        uint[] sbox4 = this.sbox4;
    ​
        uint[] pbox = this.pbox;
    ​
        uint pbox00 = pbox[0];
        uint pbox01 = pbox[1];
        uint pbox02 = pbox[2];
        uint pbox03 = pbox[3];
        uint pbox04 = pbox[4];
        uint pbox05 = pbox[5];
        uint pbox06 = pbox[6];
        uint pbox07 = pbox[7];
        uint pbox08 = pbox[8];
        uint pbox09 = pbox[9];
        uint pbox10 = pbox[10];
        uint pbox11 = pbox[11];
        uint pbox12 = pbox[12];
        uint pbox13 = pbox[13];
        uint pbox14 = pbox[14];
        uint pbox15 = pbox[15];
        uint pbox16 = pbox[16];
        uint pbox17 = pbox[17];
    ​
        // now load the current IV into 32bit integers for speed
        uint hi = (((uint)iv[0]) << 24) |
                    (((uint)iv[1]) << 16) |
                    (((uint)iv[2]) << 8) |
                            iv[3];
    ​
        uint lo = (((uint)iv[4]) << 24) |
                    (((uint)iv[5]) << 16) |
                    (((uint)iv[6]) << 8) |
                            iv[7];
    ​
        // we deal with the even part first
        int rest = count % BLOCK_SIZE;
        end -= rest;
    ​
        for (; ; )
        {
            // need to create new IV material no matter what
            hi ^= pbox00;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox01;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox02;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox03;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox04;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox05;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox06;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox07;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox08;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox09;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox10;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox11;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox12;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox13;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox14;
            lo ^= (((sbox1[(int)(hi >> 24)] + sbox2[(int)((hi >> 16) & 0x0ff)]) ^ sbox3[(int)((hi >> 8) & 0x0ff)]) + sbox4[(int)(hi & 0x0ff)]) ^ pbox15;
            hi ^= (((sbox1[(int)(lo >> 24)] + sbox2[(int)((lo >> 16) & 0x0ff)]) ^ sbox3[(int)((lo >> 8) & 0x0ff)]) + sbox4[(int)(lo & 0x0ff)]) ^ pbox16;
    ​
            uint swap = lo ^ pbox17;
            lo = hi;
            hi = swap;
    ​
            if (posIn >= end)
            {
                // exit right in the middle so we always have new IV material for the rest below
                break;
            }
    ​
            hi ^= (((uint)dataIn[posIn]) << 24) |
                    (((uint)dataIn[posIn + 1]) << 16) |
                    (((uint)dataIn[posIn + 2]) << 8) |
                            dataIn[posIn + 3];
    ​
            lo ^= (((uint)dataIn[posIn + 4]) << 24) |
                    (((uint)dataIn[posIn + 5]) << 16) |
                    (((uint)dataIn[posIn + 6]) << 8) |
                            dataIn[posIn + 7];
    ​
            posIn += 8;
    ​
            // now stream out the whole block
            dataOut[posOut] = (byte)(hi >> 24);
            dataOut[posOut + 1] = (byte)(hi >> 16);
            dataOut[posOut + 2] = (byte)(hi >> 8);
            dataOut[posOut + 3] = (byte)hi;
    ​
            dataOut[posOut + 4] = (byte)(lo >> 24);
            dataOut[posOut + 5] = (byte)(lo >> 16);
            dataOut[posOut + 6] = (byte)(lo >> 8);
            dataOut[posOut + 7] = (byte)lo;
    ​
            posOut += 8;
        }
    ​
        // store back the new IV
        iv[0] = (byte)(hi >> 24);
        iv[1] = (byte)(hi >> 16);
        iv[2] = (byte)(hi >> 8);
        iv[3] = (byte)hi;
        iv[4] = (byte)(lo >> 24);
        iv[5] = (byte)(lo >> 16);
        iv[6] = (byte)(lo >> 8);
        iv[7] = (byte)lo;
    ​
        // emit the rest
        for (int i = 0; i < rest; i++)
        {
            iv[i] = dataOut[posOut + i] = (byte)(dataIn[posIn + i] ^ iv[i]);
        }
    ​
        this.ivBytesLeft = iv.Length - rest;
    ​
        return count;
    }
    

    最佳答案

    这就是您的 PHP 代码所期望的。 CFB 模式将分组密码转换为流密码。由于 semantical security (或 randomized encryption ),对于相同 key 下的每个加密,您需要不同的 IV。否则,一旦攻击者注意到 IV 被重用,攻击者就可以像在 One-Time-Pad 中一样使用两次 time-pad 攻击。

    您应该始终新鲜生成 IV。

    $iv = openssl_random_pseudo_bytes($ivlen);
    

    注意: 仍然存在一个问题,如果 key 使用过多,您可能会为同一个 key 生成两次相同的 IV。 IV 重用的最简单缓解方法是使用增量 IV 或使用 LFSR 生成 IV,这是常见的做法。如果您要为每个加密更改 key ,那么 IV 重用不是问题,但是,更改 IV 比更改 key 更容易。

    更新: 我通过查看评论找到了你的 C# source code
    // consume what's left in the IV buffer, but make sure to keep the new
    

    这段代码的作者说
    /// Useful if you don't want to deal with padding of blocks (in comparsion to CBC), however
    /// a safe initialization vector (IV) is still needed.
    

    此代码目前使用不安全。

    您可以使用
    SetIV(value, 0);
    

    函数使用来自 PHP 加密的 value 初始化 IV。

    关于c# - PHP 使用河豚加密字符串,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57883645/

    10-16 07:27