Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP encrypt string using blowfish

I have an application running on php 7.2 and I need to encrypt a string using the following criteria:

  • Cipher: NCFB
  • Output encoding: Base64
  • Initialization Vector (IV) = 8

I already know the output I should get, but my script returns different strings everything, I think because of the IV ( openssl_random_pseude_bytes), and I can't really understand the logic of it. I am not so experienced with encrypting so I can't figure this out.

$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));

Example

The goal of this encryption is for a API access, and there is a provided example written in C# for the encryption method. The thing is that that script generates the same string every time unlike mine. I must build my script so I get same results like the official example provided ( here is a code snippet: )

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;
}
like image 712
John Doe Avatar asked May 31 '26 23:05

John Doe


1 Answers

That is what expected with your PHP code. CFB mode turns a block cipher into a stream cipher. Due to the semantical security ( or randomized encryption), you need a different IV for each encryption under the same key. Otherwise, an attacker can use two-time-pad attack as in One-Time-Pad once the attacker notices that the IV re-used.

You should always generate the IV freshly.

$iv = openssl_random_pseudo_bytes($ivlen);

Note: There is a still problem that you may generate the same IV twice for the same key if the key is used too much. The easiest mitigation from IV-reuse is using incremental IV or generating the IV's by using an LFSR this is common practice. If you are changing the key for each encryption then IV-reuse is not a problem, however, changing the IV is easier than changing the key.

Update: I've found your C# source code by just looking the comment

// consume what's left in the IV buffer, but make sure to keep the new

The author of this code says that

/// 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.

This code currently insecure to use.

You can use

SetIV(value, 0);

function to init the IV with the value coming from the PHP encryption.

like image 144
kelalaka Avatar answered Jun 02 '26 11:06

kelalaka



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!