3

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; } 
2
  • Could you also post the link for of the C# code? If this code is really using the same IV for each encryption even only for the same data, it is insecure. If C# and PHP code is communicating answer of your updated question is easy. IV's are not secret and transferred with the ciphertext and usually appended. Also, is there any specific reason that you are not using an authenticated encryption like AES-GCM which will provide not only confidentiality but also integrity and authentication. Commented Sep 16, 2019 at 14:54
  • iv 8 ? do you mean the ascii character 8? or do you mean a uint8 8? little-endian uint16 8? or a big endian uint16 8? or a little-endian uint32 8? or a big-endian uint32 8? or a little-endian uint64 8? or a big-endian uint64 8? or what do you mean by "8" ? Commented Sep 21, 2019 at 17:21

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Since an algorithmic IV is itself inherits a degree of compromisation risk, using some sort of salt (a fixed size, random-byte in the beginning, middle or the end, or spread out) with the original payload will mitigate the aforementioned problem. The IV should be as cryptographically random as possible.
@Cunning The easiest method is using a counter and it is recommended by NIST and it is usual practice. You just need to store the current value++ of the counter and use it in the next encryption.
you need a different IV for each encryption under the same key - exceptions apply, such as when porting an algorithm from language X to language Y, to make sure the port is bugfree, check that the same data with the same key and the same IV (see? valid use-case for IV-reuse!) produce the exact same cipertext in both languages~

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.