php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #51146 mcrypt doesn't do OFB mode correctly
Submitted: 2010-02-25 18:01 UTC Modified: 2017-10-24 06:46 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: zelnaga at gmail dot com Assigned:
Status: Analyzed Package: mcrypt related
PHP Version: 5.3.1 OS: Windows XP
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: zelnaga at gmail dot com
New email:
PHP Version: OS:

 

 [2010-02-25 18:01 UTC] zelnaga at gmail dot com
Description:
------------
Correct me if I'm wrong, but shouldn't an ECB decryption of an OFB encrypted string of null bytes produce a string whose first eight bytes (assuming that that's the block size) are equal to the IV?  Certainly that's the impression I get from wikipedia.org's discussion of OFB.

http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29



Reproduce code:
---------------
<?php
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_OFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', 'bbbbbbbb');
$ciphertext = mcrypt_generic($td, "\0\0\0\0\0\0\0\0");

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo urlencode(mdecrypt_generic($td, $ciphertext));
?>

Expected result:
----------------
bbbbbbbb

Actual result:
--------------
5%FBdq%C7Y7%13

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-02-25 18:18 UTC] zelnaga at gmail dot com
mcrypt also seems to be implementing OFB and CFB modes identically.  Although the first block produced by either mode should be the same, subsequent blocks should be different. ie. in CFB, the second block is XOR'd with the previous ciphertext, reencrypted with the key, whereas in OFB, the second block is XOR'd with that which the previous text was previously XOR'd with.

Example code:

<?php
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_OFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', 'bbbbbbbb');
echo urlencode(mcrypt_generic($td, str_repeat("\0", 16))) . "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', 'bbbbbbbb');
echo urlencode(mcrypt_generic($td, str_repeat("\0", 16)));
?>
 [2010-02-25 19:23 UTC] pajoye@php.net
It looks like a libmcrypt problem, if it is a bug. Can you try using the mcrypt cmd line tools? If it fails and you see it as a bug, please report a bug to the mcrypt project. Let us know how it went.
 [2010-02-26 03:28 UTC] zelnaga at gmail dot com
Filing a bug report is going to be a little difficult giving that, near as I can tell, the command line version of mcrypt randomly generates IV's.  My first example requires the IV's be of a known value and my second example requires encrypting the same string with two different modes and with the same IV.

Also, to be honest, I don't know at all how to intreprete the data the command line version of mcrypt is giving me, anyway.  I do the following:

mcrypt --algorithm des --mode ecb --no-openpgp test.txt --verbose --bare

And I get a 100 byte file.  Given that the source file was 16 bytes ("-" repeated sixteen times), that's a bit odd.  Curious to see what the remaining 84 bytes are, I do the following:

<?php
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
mcrypt_generic_init($td, 'test', "\0\0\0\0\0\0\0\0");
echo mdecrypt_generic($td, file_get_contents('test.txt.nc'));
?>

And that doesn't produce anything even remotely resembling the source text.

A while ago, there was a bug report filed on the mcrypt PHP extension (49561) where someone reproduced the problem in C, using the mcrypt libraries, and filed the bug report themselves.  Can't that be done here?  I don't have the ability to compile PHP or PHP extensions such as mcrypt and if no one reports the bug to the mcrypt developers than both PHP and mcrypt will have this bug.

Of course, then again, given that bug # 49561 hasn't even been touched by the mcrypt developers, it seems safe to assume that any bug report that's filed - by me or anyone else - will be ignored.  If mcrypt has been abandoned by its developers when does PHP abandon mcrypt?
 [2010-02-26 10:54 UTC] derick@php.net
As far as I know, the IV is also used for the first round, so I am not sure if your statement holds up.
 [2010-02-26 16:16 UTC] zelnaga at gmail dot com
As far as I know, the IV is also used for the first round, so I am not
sure if your statement holds up.

Ummm...  the IV - as defined in mcrypt_generic_init - is only used in the first round.  Per wikipedia, the first block against which the plaintext is XOR'd is the IV encrypted with the key.  That's true in both CFB and OFB modes of operation.  The difference between CFB and OFB is what subsequent blocks encrypt for the keystream.  So, per that, the first block should be the same.  And as for my first bug report...

<?php
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_OFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', 'bbbbbbbb');
echo urlencode(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', 'bbbbbbbb');
echo urlencode(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo urlencode(mcrypt_generic($td, 'bbbbbbbb'));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo urlencode(mcrypt_generic($td, 'bbbbbbbb'));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', 'ctr', '');
mcrypt_generic_init($td, 'aaaaaaaa', 'bbbbbbbb');
echo urlencode(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));
?>

All of those should produce the same ciphertext.  As it stands, only ecb, cbc and ctr produce the same ciphertext.  ofb and cfb produce the same thing as each other (and, for the first block, they should, as I already mentioned), however, they're not producing the same thing as any of the other modes when, in fact, they should be.
 [2010-04-13 23:36 UTC] zelnaga at gmail dot com
I was comparing mcrypt against openssl_encrypt() and...  well, either OpenSSL is wrong or mcrypt is wrong:

<?php
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_OFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo bin2hex(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CFB, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo bin2hex(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));

echo "\r\n";

echo bin2hex(openssl_encrypt("\0\0\0\0\0\0\0\0", 'DES-OFB', 'aaaaaaaa', true)) . "\r\n";
echo bin2hex(openssl_encrypt("\0\0\0\0\0\0\0\0", 'DES-CFB', 'aaaaaaaa', true)) . "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo bin2hex(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo bin2hex(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));

echo "\r\n";

$td = mcrypt_module_open(MCRYPT_DES, '', 'ctr', '');
mcrypt_generic_init($td, 'aaaaaaaa', "\0\0\0\0\0\0\0\0");
echo bin2hex(mcrypt_generic($td, "\0\0\0\0\0\0\0\0"));
?>

ie. mcrypt, in CTR, CBC and ECB modes equal OpenSSL in OFB and CFB modes but not mcrypt in OFB and CFB modes.  In other words, OpenSSL's OFB != mcrypt's OFB and they should.
 [2010-06-01 12:22 UTC] me at haravikk dot com
You're using the wrong OFB mode, you need to use MCRYPT_MODE_NOFB.
MCRYPT_MODE_OFB is per-byte, while MCRYPT_MODE_NOFB is per-block and gives the 
result you were expecting.
 [2010-06-19 15:40 UTC] zelnaga at gmail dot com
What does it even mean to do OFB at the byte-level?  Per <http://en.wikipedia.org/wiki/File:Ofb_encryption.png>, in OFB, you encrypt the IV with the key with the chosen block cipher algorithm and then XOR that against the plaintext to get the ciphertext.   How do you do that at the "byte level"?  Do you do substrings or something?
 [2010-06-19 15:42 UTC] zelnaga at gmail dot com
Also, there's still the matter of CFB.  So NOFB is what most everything else refers to as OFB but CFB'a wrong, as well, and it has no NCFB counter part.
 [2011-01-05 21:14 UTC] me at haravikk dot com
To answer your first question, performing the encryption per-byte is kind of odd, I haven't done much 
testing but I don't think it actually consumes the entire input vector, only the first byte, just think of 
it as running with an 8-bit wide state, rather than the more typical 128-bit wide state.
I've never had the time to confirm that this is the exact case, though, but I believe it's compatible with 
other cryptography solutions that can operate per-byte.


For your second point; sorry I should have pointed out that there is no predefined constant for 
MCRYPT_MODE_NCFB, you can however just enter it manually as 'ncfb' like so:

mcrypt_module_open('rijndael-128', '', 'ncfb', '');


So if there's a failing on the PHP side it's that we need an MCRYPT_MODE_NCFB, and that MCRYPT_MODE_CFB 
and MCRYPT_MODE_OFB should be properly documented so people don't keep using them expecting different 
results!
 [2011-01-29 23:20 UTC] lemkemch at t-online dot de
I can't believe what I was reading about ncfb above.  That was the solution to decrypt my OpenOffice files.  I had it working in Java, with openssl on the command line but I couldn't get it to work with php no matter what I tried.  I had no idea that such a mode exists.  So please, document this and define a constant for it.
 [2013-11-25 19:30 UTC] ywarnier at beeznest dot org
This addition might come as very low quality as I am still trying to understand AES128 and CFB, but I think it is important to have the additional information one can gather. I hope it helps.

I was stuck trying to get mcrypt to cipher a string with AES128 in CFB mode, to be used by the corresponding code in ASP.NET. I first thought ASP was the erroneous part, but apparently not.

This is my code snippet (note that padding is made with PKCS7 method). I use an IV identical to my key, which is OK considering the key is 16 bytes long:

$password = 'not ciphered';
$key = '-+*%$({..})$%*+-';
$blockSize = 16;
$padding = $blockSize - (strlen($password)%$blockSize);
$password .= str_repeat(chr($padding),$padding);
$cipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $password,  MCRYPT_MODE_CFB, $key);
$arr = preg_split('//', $cipher, -1, PREG_SPLIT_NO_EMPTY);
foreach ($arr as $char) {
    echo ord($char).',';
}

The generated $cipher is the following sequence of (16) bytes:
179,90,167,188,65,82,212,108,133,15,161,49,142,222,207,167

However, the correct sequence of bytes should be as defined below, and can easily be generated using phpseclib:

    $password = 'not ciphered';
    $key = '-+*%$({..})$%*+-';
    $blockSize = 16;
    $padding = $blockSize - (strlen($password)%$blockSize);
    $password .= str_repeat(chr($padding),$padding);
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
    $cipher->setKeyLength(128);
    $cipher->setKey($key);
    $cipher->setIV($key);
    $cipheredPass = $cipher->encrypt($password);
    $arr = preg_split('//', $cipheredPass, -1, PREG_SPLIT_NO_EMPTY);
    foreach ($arr as $char) {
        echo ord($char).',';
    }

This last code generates the following sequence of (16) bytes:
179,15,83,71,111,104,118,215,159,221,153,228,153,148,69,164

Now, as I said, I'm still wrapping my brains around the whole AES128 (and in particular CFB), but this last result is also what you get in ASP (and apparently in C, C++, etc).

There's a guy who apparently understands the problem better and put me in the right direction here: http://stackoverflow.com/a/4054017/1406662
I found a few other references linking to mcrypt, but not specifically with CFB (for some reason people seen to try CBC and NOFB first).
Apparently, the problem comes down to the default "feedback" size, which would not be set properly in either PHP or mcrypt.

Also, somehow, phpseclib got it right, and it's MIT-licensed, so maybe a source of inspiration?
http://phpseclib.sourceforge.net/index.html

The problem might effectively come from mcrypt, and I'd be happy to report there if confirmed and if you point me in the right direction as to where that is done most efficiently.
 [2013-11-25 19:38 UTC] ywarnier at beeznest dot org
Sorry, I forgot to mention (if that has any incidence on this report) I use PHP 5.5.3 (mod_php5) with the mcrypt module for 5.4.6.
How is that even possible, I don't know, but that's what my dear Ubuntu is telling me... moving back to Debian soon, I swear :-p
 [2016-12-14 23:47 UTC] leigh@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: leigh
 [2016-12-14 23:47 UTC] leigh@php.net
Closing because it's not PHP implementing the encryption modes, it's libmcrypt which is unmaintained.

The assumption that encrypting in OFB and decrypting in ECB should yield the IV at the start of the plaintext (where the original plaintext is entirely null bytes) is correct. If mcrypt does not do this, it is a problem with the third party library, the PHP module is just a wrapper.
 [2017-01-10 10:15 UTC] leigh@php.net
-Status: Closed +Status: Wont fix
 [2017-01-10 12:06 UTC] php at haravikk dot me
I'm sorry but I believe this has been closed prematurely; as I have pointed out, mcrypt is in fact operating correctly, the issue here is that the behaviour of its CFB and OFB modes is misleading as it is per-byte, rather than per-state size block.

The solution is to use the MCRYPT_MODE_NOFB constant, or to request ncfb mode via string, as the n signifies that these are scaled to the size of the state/key, so 128, 192 or 256 bits.

As I stated in my earlier comment, the problem is in fact on the PHP side in so far as there is no MCRYPT_MODE_NCFB constant, and that the documentation for the CFB and OFB constants do not clarify that these are per-byte modes of operation. I believe that these are therefore issues with PHP's libmcrypt wrapper, not libmcrypt itself, and should be resolvable (especially after nearly seven years!)
 [2017-01-10 14:28 UTC] leigh@php.net
Ok, so what this boils down to is:

1) A documentation issue that mcrypt's implementation of OFB and CFB (as represented by MODE_OFB and MODE_CFB) operate in 8-bit mode, and that NCFB operates on full blocks.

2) A feature request to wrap mcrypt's MODE_NCFB constant.
 [2017-01-10 16:30 UTC] nikic@php.net
-Status: Wont fix +Status: Re-Opened -Type: Bug +Type: Documentation Problem
 [2017-01-10 16:30 UTC] nikic@php.net
Reclassifying as documentation problem for part 1). As the mcrypt extension has been killed, we won't be adding new constants -- the documentation should state that the string should be passed directly for this.
 [2017-10-24 05:20 UTC] kalle@php.net
-Status: Re-Opened +Status: Assigned
 [2017-10-24 06:46 UTC] kalle@php.net
-Status: Assigned +Status: Analyzed -Assigned To: leigh +Assigned To:
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC