<code>

Problem/Motivation

https://wiki.php.net/rfc/mcrypt-viking-funeral
In PHP 7.1, all mcrypt_* functions will raise an E_DEPRECATED notice.

Proposed resolution

  • Replace use of mcrypt in TfaBasePlugin::encrypt() and TfaBasePlugin::decrypt() methods with openssl, where available.
  • Figure out an upgrade path.
CommentFileSizeAuthor
#83 tfa-n2820710-83.patch7.88 KBjcnventura
#83 interdiff_80_83.txt902 bytesjcnventura
#83 interdiff_54_83.txt8.57 KBjcnventura
#80 tfa-n2820710-80.patch7.88 KBjcnventura
#80 interdiff_74_80.txt6.36 KBjcnventura
#74 tfa-n2820710-74.patch7.97 KBDamienMcKenna
#74 tfa-n2820710-74.interdiff.txt5 KBDamienMcKenna
#56 Screen Shot 2019-09-17 at 3.24.30 PM.png22.74 KBrobpowell
#54 tfa-2820710-54.patch6.25 KBnullkernel
#54 interdiff_53-54.txt325 bytesnullkernel
#53 tfa-n2820710-53.patch6.27 KBDamienMcKenna
#53 tfa-n2820710-53.interdiff.txt397 bytesDamienMcKenna
#51 tfa-n2820710-51.patch6.27 KBDamienMcKenna
#51 tfa-n2820710-51.interdiff.txt518 bytesDamienMcKenna
#43 interdiff_29-42.txt3.12 KBpjcdawkins
#42 interdiff_39-42.txt939 bytespjcdawkins
#42 tfa-2820710-42.patch6.22 KBpjcdawkins
#39 interdiff_29-39.txt3.14 KBpjcdawkins
#39 tfa-2820710-39.patch6.23 KBpjcdawkins
#29 interdiff_27-29.txt1.57 KBnullkernel
#29 tfa-2820710-29.patch8.06 KBnullkernel
#27 interdiff_22-27.txt4.62 KBnullkernel
#27 tfa-2820710-27.patch7.93 KBnullkernel
#24 interdiff-23.txt906 bytesandyrigby
#24 tfa-2820710-23.patch10.5 KBandyrigby
#22 tfa-2820710-22.patch10.5 KBnullkernel
#22 interdiff_20-22.txt376 bytesnullkernel
#20 tfa-2820710-20.patch10.5 KBnullkernel
#20 interdiff_18-20.txt3.83 KBnullkernel
#18 tfa-2820710-18.patch7.67 KBnullkernel
#18 interdiff_16_18.txt0 bytesnullkernel
#16 tfa-2820710-16.patch7.67 KBnullkernel
#16 interdiff_7-16.txt4.68 KBnullkernel
#7 tfa-2820710-7.patch4.75 KBpjcdawkins
#4 tfa-2820710-4.patch4.7 KBpjcdawkins
#3 tfa-2820710-3.patch0 bytespjcdawkins
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

pjcdawkins created an issue. See original summary.

pjcdawkins’s picture

Title: Remove mcrypt requirement for future PHP compatibility » Remove mcrypt requirement for PHP 7.1+ compatibility

7.1 is now out; this isn't about the future anymore

pjcdawkins’s picture

Something like this, perhaps. It selects OpenSSL if available, falling back to mcrypt. Maybe horrible. But a start.

pjcdawkins’s picture

The last submitted patch, 3: tfa-2820710-3.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 4: tfa-2820710-4.patch, failed testing.

pjcdawkins’s picture

Status: Needs work » Needs review
FileSize
4.75 KB

The last test failed for a PHP 5.3 (???) compatibility issue

pjcdawkins’s picture

Title: Remove mcrypt requirement for PHP 7.1+ compatibility » MCrypt is required, but it is deprecated in PHP 7.1+
Category: Task » Bug report
pjcdawkins’s picture

webservant316’s picture

question, can I move to PHP71 even though this deprecated error still exists?

pjcdawkins’s picture

@webservant316: yes, but you might want to set your error_reporting level to exclude E_DEPRECATED, via ini settings or via the function.

webservant316’s picture

thanks

Neo13’s picture

Status: Needs review » Needs work

The patch doesn't work for me. It always uses decryptWithMCrypt even when the mcrypt extension is not loaded. I think the problem could be here:

    $version_prefix = self::CRYPT_VERSION . '|';
    $version_match = strpos($data, $version_prefix) === 0;

Could you please look into that?

pjcdawkins’s picture

The intent is that it will decryptWithMcrypt() only for data that was encrypted with the old scheme, and even then only if the mcrypt extension is loaded (if the extension isn't loaded, it will fail to decrypt). Decryption of newly encrypted data will use openssl.

Anyway, there are probably more modern ways to do this since the patch 10 months ago...

freezypop’s picture

Has libsodium been considered?

nullkernel’s picture

Status: Needs work » Needs review
FileSize
4.68 KB
7.67 KB

I was able to produce a patch which uses OpenSSL to decrypt data that was originally encrypted with MCrypt.

Libsodium may be worth considering, I lean towards not using it (but don't have a strong opinion). Presumably, data encrypted by OpenSSL can be decrypted by Sodium down the road if there is a desire to make the switch. I used OpenSSL because:

  1. The existing patch uses it
  2. OpenSSL is readily available for PHP 5 and 7 implementations.
  3. The documentation on php.net for Sodium is incomplete.
  4. I am unfamiliar with Sodium.

This patch will decrypt with OpenSSL (instead of MCrypt) if both extensions are available. There is no particular reason why, other than it addresses #13 and may aid in testing/verification. Depending on compatibility with obsolete versions of PHP (e.g. I haven't tested 5.3), it might be worth using MCrypt over OpenSSL when decrypting legacy data.

The MCrypt extension was removed from PHP 7.2. This patch should make the TFA plugin compatible with PHP 7.2 while remaining backwards-compatible with older versions of PHP, as well as sites which upgrade from an older version (and already have encrypted data).

It may or may not be not be a lot of work to re-implement my patch with Sodium - I don't know.

Status: Needs review » Needs work

The last submitted patch, 16: tfa-2820710-16.patch, failed testing. View results

nullkernel’s picture

Status: Needs work » Needs review
FileSize
0 bytes
7.67 KB

The patch I uploaded for #16 is malformed - the "interdiff" utility won't even work with it. I guess I should have known better than to manually edit it before upload. I've re-generated the patch to try again.

diff tfa-2820710-16.patch tfa-2820710-18.patch

20c20
< index bb9ab82..88f1760 100644
---
> index bb9ab82..a063360 100644
116c116
< @@ -551,6 +609,64 @@ abstract class TfaBasePlugin {
---
> @@ -551,6 +609,65 @@ abstract class TfaBasePlugin {
andyrigby’s picture

Hi,

We're having trouble with this patch where random users are not prompted to enter their 2FA details on login. They are logged straight in.

The error displayed is:

Warning: openssl_decrypt(): IV passed is only 13 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0 in TfaBasePlugin->decrypt() (line 581 of /var/www/vm7/web/sites/all/modules/contrib/tfa/tfa.inc

The number 13 varies.

I believe it's down to the iv or formatting of the iv with the data before it's stored.

I believe the iv is being generated with a pipe symbol ('|') and then stored so you end up with


$data = "1|iv|here|data here";
list(, $iv, $data) = explode('|', $data, 3);
$iv == 'iv';
$data == 'here|data here';

Where you should have:


$data = "1|iv here|data here";
list(, $iv, $data) = explode('|', $data, 3);
$iv == 'iv here';
$data == 'data here';

Quick back of a napkin script shows drupal_random_bytes() returns the pipe symbol in ~6% of cases for me.

Perhaps the iv needs to be base64 encoded before storing, something like that. Or perhaps a quick solution would be retry drupal_random_bytes() until one is generated without a pipe symbol?

nullkernel’s picture

Thanks for the feedback. I don't think base64 encoding the IV is the right way forward, since existing encryptions don't use it. I've updated my patch to ensure that the IV length is always 16. I've done this by creating additional methods that parse the encrypted storage string by using a regex. Please review / give it a try. I haven't tested it so hopefully everything works :).

Status: Needs review » Needs work

The last submitted patch, 20: tfa-2820710-20.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

nullkernel’s picture

Status: Needs work » Needs review
FileSize
376 bytes
10.5 KB

Sorry, my patch had the wrong regex.. updated it.

Status: Needs review » Needs work

The last submitted patch, 22: tfa-2820710-22.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

andyrigby’s picture

Thanks for taking a look. I'm getting a similar error (but different):

Warning: openssl_decrypt(): IV passed is 32 bytes long which is longer than the 16 expected by selected cipher, truncating in TfaBasePlugin->decrypt() (line 658 of /var/www/bbi.vm7/web/sites/all/modules/contrib/tfa/tfa.inc).

I think the getIvFromEncryptedStorageText() and getDataToDecryptFromEncryptedStorageText() are looking in the wrong positions in the array.

getIvFromEncryptedStorageText() should be looking in $matches[2] and getDataToDecryptFromEncryptedStorageText() in $matches[3] (there is no $matches[4])

Changing this seems to resolve it for me, at least the error disappears and I am prompted for the verification code now.

nagba’s picture

Regexp does not always work.
I tested with the following snippet using drush:
$iv = drupal_random_bytes(16); $data = drupal_random_bytes(32); $encrypted = "1|" . $iv . "|" . $data; var_dump($encrypted); preg_match("/^\d+\|(.{16})\|(.*)\$/m", $encrypted, $matches); var_dump($matches);
and occasionally it got me no matches.

The following code I did not see fail so far:
$iv = drupal_random_bytes(16); $data = drupal_random_bytes(32); $encrypted = "1|" . $iv . "|" . $data; var_dump($encrypted); $version_cutoff = strpos($encrypted, "|"); $decoded_iv = substr($encrypted, $version_cutoff + 1, 16); var_dump($decoded_iv); $decoded_data = substr($encrypted, $version_cutoff + 18); var_dump($decoded_data);

andyrigby’s picture

Good spot, yes I see the same. I believe it's down to drupal_random_bytes() returning newline characters, and by default '.' in the regex does not include them. Adding the 's' modifier e.g. '/^(\d+\|){0,1}(.{16})\|(.*)$/s' and I can't get it to fail after ~1000 attempts.

Having said that, using substr is likely to be quicker though anyway? Only edge case I can think of is *if* http://php.net/manual/en/mbstring.overload.php is in use, it might return the wrong number of chars.

nullkernel’s picture

Status: Needs work » Needs review
FileSize
7.93 KB
4.62 KB

I'm thinking we don't need a nonstandard data format that requires a nontrivial regex. I've rerolled the patch using JSON encoding to simplify. If the JSON parser doesn't return something that matches expectations, then use a decryptLegacyData method. It also makes it easy to extend the format to store additional data fields (should the need arise in the future).

Status: Needs review » Needs work

The last submitted patch, 27: tfa-2820710-27.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

nullkernel’s picture

I've modified my patch to use base64 encoding, since I can't json_encode the IV/ciphertext as-is.

nullkernel’s picture

Status: Needs work » Needs review
collinhaines’s picture

#29 worked for me with PHP 7.1.20

malaynayak’s picture

patch #29 worked for me with PHP 7.2.

monstrfolk’s picture

Worked for me also

malaynayak’s picture

Status: Needs review » Reviewed & tested by the community
jonas139’s picture

I can also confirm that #29 works with PHP 7.1.

Thanks!

nikolabintev’s picture

How do we handle already encrypted seeds with mcrypt?

nikolabintev’s picture

pjcdawkins’s picture

@nikolabintev the patch is intended to detect data encrypted via the mcrypt method, and decrypt it, with either openssl or mcrypt depending what's available

pjcdawkins’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
6.23 KB
3.14 KB

The patch in #29 missed removing the IV from the start of the $data variable (when doing legacy decryption with OpenSSL).

narkoff’s picture

Issue summary: View changes

Patch #39 is generating the following error:

Warning: substr() expects parameter 3 to be integer, string given in TfaBasePlugin->decryptLegacyDataWithOpenSSL() (line 640 of /srv/bindings/dba848bb571549fca85a165e4be9c31f/code/sites/all/modules/tfa/tfa.inc).

Invalid application code. Please try again.

Patch #29 works perfectly, however.

hoegrammer’s picture

Hi, I've tried patch #29 and it is working (by which I mean that I can set up and use 2FA; I don't have the skill to assess it for security in regard to how it does encryption).

Tested with Drupal 7.63 and PHP 7.2

pjcdawkins’s picture

My patch in #39 wrongly changed the decryption algo.

This one works according to a test script.

pjcdawkins’s picture

FileSize
3.12 KB

Interdiff between 29 and 42 - removing the IV before decryption avoids all the looping

narkoff’s picture

Patch #42 works great. Thank you @pjcdawkins.

Manuel Garcia’s picture

Issue tags: +PHP 7.2
Manuel Garcia’s picture

klausi’s picture

Priority: Normal » Critical

Since the module completely breaks on PHP 7.2 this is critical.

DamienMcKenna’s picture

DamienMcKenna’s picture

DamienMcKenna’s picture

FileSize
518 bytes
6.27 KB

FYI the ?: syntax requires PHP 5.3, so I adjusted that part to be compatible with PHP 5.2. Otherwise, this patch is identical to #42.

nullkernel’s picture

I believe the patch in #51 will not work identically to #42, based on the interdiff. I wrote some quick-and-dirty code to check my hesitation.

function withTernary($value = null) {
  $value = $value ?: "assigned by function";
  return $value;
}
echo "withTernary called null: " . withTernary(null) . PHP_EOL;
echo "withTernary called already set: " . withTernary("assigned by caller") . PHP_EOL;


function withoutTernary($value = null) {
  if (!is_null($value)) {
    $value = "assigned by function";
  }
  return $value;
}
echo "withoutTernary called null: " . withoutTernary(null) . PHP_EOL;
echo "withoutTernary called already set: " . withoutTernary("assigned by caller") . PHP_EOL;
$ php testternary.php
withTernary called null: assigned by function
withTernary called already set: assigned by caller
withoutTernary called null: 
withoutTernary called already set: assigned by function

$ php --version
PHP 7.2.19-0ubuntu0.18.04.1 (cli) (built: Jun  4 2019 14:48:12) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.19-0ubuntu0.18.04.1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans
DamienMcKenna’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
397 bytes
6.27 KB

So would a !empty() be better?

nullkernel’s picture

I agree empty is a little better to use in this situation. However, the result with #53 is the same as #51 (and different from #42).

$ php testternary2.php 
withTernary called null: assigned by function
withTernary called already set: assigned by caller
withoutTernary called null: 
withoutTernary called already set: assigned by function

It looks like the negation is throwing off the result. If you delete the "!" in the if statement, the result becomes consistent with #42:

withTernary called null: assigned by function
withTernary called already set: assigned by caller
withoutTernary called null: assigned by function
withoutTernary called already set: assigned by caller

I've attached an updated patch and interdiff (removing the "!" from #53's if statement).

pjcdawkins’s picture

Status: Needs review » Reviewed & tested by the community

Yes #53 and #51 inverted the logic. #54 is fine.

(PHP 5.2 was end-of-life in January 2011, before this module even had an initial commit...)

robpowell’s picture

bump!

Acquia is forcing php7.2 on October the first. At that time, mcrypt will no longer be available. It would be great to see this merged in with a release version before then.

I tested in Acquia with PHP 7.2 enabled. We are also using tfa_duo. I confirmed that I was able to use TFA to login and that the status message on admin/reports/status no longer displayed warning about mcrypt.

Jon Pollard’s picture

Hi, I've just upgraded my server to php 7.2 - and moved the tfa module from the release version to the dev version in order to apply this patch. I'm now getting an alert that I need to update to the beta3 version... Am I correct in thinking that this patch will only work with the dev version? Is the dev version secure?

pjcdawkins’s picture

Hi @Jon Pollard - by convention and practicality, patches are always intended to apply to the latest -dev version. By coincidence, they may also apply to the latest stable version. As it happens, with the tfa module, 7.x-2.0, 7.x-2.0-beta3, and 7.x-2.x-dev are all exactly the same at the time of writing - the module has not been changed since this commit on 10 December 2015:
https://git.drupalcode.org/project/tfa/commit/94b4c0a52f58fe0f5641746d95...

So you're are free to apply the patch to any of those versions, there is no difference. Security-wise there isn't really much that can be said.

Jon Pollard’s picture

thanks @pjcdawkins. I've patched the 2.0 version and all looks good! The only concern really is why the dev version was being flagged as needing a security update - not a big issue, but I thought you should be aware.

Rory--’s picture

I'm trying the patch at #54 with the TFA Basic authenticators, and getting this error upon trying to validate with Authy:

Error: Undefined class constant 'CRYPT_VERSION' in TfaBasePlugin->encrypt() (line 520 of www/html/docroot/sites/all/modules/tfa/tfa.inc).

Am I missing something? Is #54 an insufficient fix without using prior patches in this thread?

yookoala’s picture

Error: Undefined class constant 'CRYPT_VERSION' in TfaBasePlugin->encrypt() (line 520 of www/html/docroot/sites/all/modules/tfa/tfa.inc).

@Rory: The tfa module of version 2.0 does not have line 520 at all. You must be using a wrong version of the module for patching.

I've just try patching tfa version 2.0 with the patch and it works fine.

yookoala’s picture

Is there anything blocking the merge? PHP 7.4 has been out and PHP 7.1 has just dropped out of support. I think its critical to have the module patch up as soon as possible. Is there anything that needs help here?

firewaller’s picture

+1

DamienMcKenna’s picture

For anyone who needs it, you can manually add mcrypt onto PHP 7.x using this pecl package: https://pecl.php.net/package/mcrypt

senthilnz’s picture

Same problem with PHP 7.2, will there be an update soon?

Manuel Garcia’s picture

radimklaska’s picture

Just tested #54 and all seems to be working properly on environment with PHP 7.3 without mcrypt.

jcnventura’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/tfa.inc
    @@ -502,12 +504,41 @@ abstract class TfaBasePlugin {
    +  protected function encryptWithMCrypt($text, $iv = null) {
    ...
    +    if (empty($iv)) {
    +      $iv = drupal_random_bytes(mcrypt_enc_get_iv_size($td));
    

    I don't see the need for this $iv parameter. The code only calls this function once, and when it does, it only passes the first parameter.

  2. +++ b/tfa.inc
    @@ -528,10 +559,42 @@ abstract class TfaBasePlugin {
    +    $is_legacy = TRUE;
    ...
    +    if (!empty($crypto_data['version']) && !empty($crypto_data['iv_base64']) && !empty($crypto_data['ciphertext_base64'])) {
    +      $is_legacy = FALSE;
    +    }
    +    // Backwards compatibility with the old MCrypt scheme.
    +    if ($is_legacy === TRUE) {
    +      if (extension_loaded('openssl')) {
    +        return $this->decryptLegacyDataWithOpenSSL($data);
    +      }
    +      if (extension_loaded('mcrypt')) {
    +        return $this->decryptLegacyDataWithMCrypt($data);
    +      }
    +      return FALSE;
    +    }
    

    This is so convoluted.

    Why not simply negate the complete if conditions, and move the content of the $is_legacy block inside the if? Way more easy to read the code.

  3. +++ b/tfa.inc
    @@ -551,6 +614,36 @@ abstract class TfaBasePlugin {
    +    // Using 3 instead of the constant OPENSSL_NO_PADDING, for PHP 5.3.
    +    $options = 3;
    +    $decrypted_text = openssl_decrypt($data, 'AES-256-CBC', $key, $options, $iv);
    

    No need to set the $options variable. Simply set 4th parameter to 3.

jcnventura’s picture

And some PHPDoc nitpicks:

  1. +++ b/tfa.inc
    @@ -502,12 +504,41 @@ abstract class TfaBasePlugin {
    +   * @param string $text
    +   *
    +   * @return string
    +   */
    +  protected function encryptWithMCrypt($text, $iv = null) {
    

    Missing the parameter and return descriptions.

  2. +++ b/tfa.inc
    @@ -528,10 +559,42 @@ abstract class TfaBasePlugin {
    +   * @param string $data
    +   *
    +   * @return string|boolean
    +   *   The plaintext, or FALSE on failure.
    +   */
    +  protected function decryptLegacyDataWithMCrypt($data) {
    

    Missing the parameter description.

  3. +++ b/tfa.inc
    @@ -551,6 +614,36 @@ abstract class TfaBasePlugin {
    +   * @param string $data
    +   *
    +   * @return string|boolean
    +   *   The plaintext, or FALSE on failure.
    +   */
    +  protected function decryptLegacyDataWithOpenSSL($data) {
    

    Missing the parameter description.

jcnventura’s picture

Was the patch in #54 ever tested against legacy mcrypt data? I'm adapting this code to the Drupal 8 module, and reduced the decryptLegacyDataWithOpenSsl() function to:

  private function decryptLegacyDataWithOpenSsl($text, $key) {
    $key = substr($key, 0, 32);
    $text = base64_decode($text);

    return openssl_decrypt($text, 'aes-128-cbc', $key, OPENSSL_NO_PADDING);
  }
Kevin Morse’s picture

Status: Needs work » Reviewed & tested by the community

#54 works fine on legacy mcrypt data. I have patched sites running PHP 7.3 where Google Authenticator was setup while running the un-patched version and the users can still login.

While your fixes might clean the code up, the code in patch 54 has been reviewed and works fine as it has been tested by many in the community. Therefore changing back to "Reviewed and tested by the community"

I would rather have working code that is a little ugly and missing some documentation than code that doesn't work. I have servers running PHP 7.4 now and we are still stuck patching to get PHP 7.1 to work...

jcnventura’s picture

Status: Reviewed & tested by the community » Needs work

#68 to #70 are important. #70 is critical. The code to decode an existing Mcrypt(ed) TFA seed simply doesn't work.

BTW, I'm maintaining this module.. Don't set it back to RTBC in the hope a maintainer will then commit it faster. A maintainer has reviewed it, and found that it needs work.

DamienMcKenna’s picture

Assigned: Unassigned » DamienMcKenna

Working on the suggested improvements.

DamienMcKenna’s picture

Status: Needs work » Needs review
FileSize
5 KB
7.97 KB

Improvements per #68, #69.

DamienMcKenna’s picture

I think the extra error handling in decryptLegacyDataWithOpenSsl($text, $key) is important for backwards compatibility?

jcnventura’s picture

Status: Needs review » Needs work

$decrypted_text = openssl_decrypt($data, 'AES-256-CBC', $key, 3, $iv);

This line doesn't decrypt data encrypted with 'rijndael-128', at least not in Drupal 8. It does work if it is like this:

openssl_decrypt($text, 'aes-128-cbc', $key, 3);

DamienMcKenna’s picture

Assigned: DamienMcKenna » Unassigned

Does it matter of the $method passed to openssl_decrypt() is uppercase or lowercase, the PHP documentation wasn't clear about it.

Is there a downside to not passing the $iv?

jcnventura’s picture

I think the problem is the 256/128, not the case.

I'm trying the code with some data I have in an archive of a D7 site to figure out why this code seems to have been validated, even if it clearly didn't work on my D8 tests.

jcnventura’s picture

OK.. Indeed, it must be 'aes-256-cbc'. Something must be really different between the way that D7's tfa encrypts stuff, and D8's. I'm surprised by that, but this answers my question in #70.

  1. +++ b/tfa.inc
    @@ -551,6 +614,37 @@ protected function decrypt($data) {
    +    if ($decrypted_text === FALSE) {
    +      return FALSE;
    +    }
    

    This is not necessary, strpos(FALSE, '|') is FALSE. And this function returns strings, not boolean, so it should return an empty string ("").

  2. +++ b/tfa.inc
    @@ -551,6 +614,37 @@ protected function decrypt($data) {
    +    if (strpos($decrypted_text, '|') !== FALSE) {
    +      list($length, $padded_data) = explode('|', $decrypted_text, 2);
    +      $decrypted_text = substr($padded_data, 0, $length);
    +    }
    +
    +    return $decrypted_text;
    

    The original code returns an empty string if $decrypted_text does not contain a '|'. This is returning the full decrypted string. Move the return inside the if, and add a else return "".

jcnventura’s picture

Title: MCrypt is required, but it is deprecated in PHP 7.1+ » Mcrypt is required, but it is deprecated in PHP 7.1+
Status: Needs work » Needs review
FileSize
6.36 KB
7.88 KB

OK. Doing the changes from #79, and adapting all decrypt functions to document that an empty string is returned on failure, as per the existing code.

Also, it seems that since OpenSSL 1.1.1, openssl_get_cipher_methods() returns only lowercased versions of the cipher methods, so changing that. Also, Mcrypt's name is not MCrypt (see https://www.php.net/manual/en/book.mcrypt.php)

jcnventura’s picture

Issue summary: View changes

I'll probably commit this soon, and create a 2.1-rc1 release. It would be highly appreciated if this could be moved back to RTBC before that :)

jcnventura’s picture

+++ b/tfa.install
@@ -27,13 +27,19 @@ function tfa_uninstall() {
+        $description = t('The TFA module recommends the PHP OpenSSL extension to be installed on the web server.');
...
+        $description = t('The TFA module requires either the PHP OpenSSL or Mcrypt extensions to be installed on the web server.');

Must be $t()

jcnventura’s picture

FileSize
8.57 KB
902 bytes
7.88 KB

Adding an interdiff to #54, to be easier to review for those that have been using it during this time.

joegl’s picture

Patch in #83 applied cleanly to 7.x-2.x-dev but fails for 7.x-2.0 (not unexpected).

joegl’s picture

I have a local development install working cleanly with the patch in #83.

jcnventura’s picture

Status: Needs review » Reviewed & tested by the community

I've analysed the interdiff between #54 and #83 several times now. Other than the MCrypt->Mcrypt refactors, and changes to the code comments, the only meaningful change is that the decrypt functions now return an empty string instead of a FALSE. As the existing code behaves this way, this improves compatibility with modules providing TFA plugins.

Setting to RTBC as per #55.

  • jcnventura authored 267dca5 on 7.x-2.x
    Issue #2820710 by nullkernel, pjcdawkins, DamienMcKenna, jcnventura,...
jcnventura’s picture

Status: Reviewed & tested by the community » Fixed
nullkernel’s picture

Status: Fixed » Needs review

I'm setting this issue to "Needs Review" due to warnings @edvanleeuwen reported in a different issue: https://www.drupal.org/project/tfa_basic/issues/2916011

Deprecated function: Function mcrypt_enc_get_iv_size() is deprecated in TfaBasePlugin->decryptLegacyDataWithMcrypt()
Deprecated function: Function mcrypt_module_open() is deprecated in TfaBasePlugin->decryptLegacyDataWithMcrypt()

The patch I originally created used the openssl extension primarily, and only would make use of mcrypt as a last resort. Same with #54.

Was the patch in #54 ever tested against legacy mcrypt data?

On my site, from what I recall, yes. Or maybe it was the patch I provided in #16. It's been a long time.

The code to decode an existing Mcrypt(ed) TFA seed simply doesn't work.

Are you sure that #54 that didn't work for your use case? Or were you testing this use case after modifying the code? It was reported that legacy data was decrypting.

I've analysed the interdiff between #54 and #83 several times now. Other than the MCrypt->Mcrypt refactors, and changes to the code comments, the only meaningful change is that the decrypt functions now return an empty string instead of a FALSE.

From what I see of interdiff_54_83.txt, this comment doesn't mention the change to make mcrypt be preferred over openssl. My intent was for mcrypt to only be used as a last resort.

DamienMcKenna’s picture

Status: Needs review » Fixed

@nullkernel: The TFA_Basic module also needs a patch to resolves its reliance upon mcrypt when using the Google Authenticator submodule.

jcnventura’s picture

@nullkernel, the mcrypt is only used as 'preferred' for decryption if it is installed. OpenSSL is still the preferred way of doing new encryptions. It is a matter of making sure that the code is as compatible as can be with before: if mcrypt is installed, it gets used for decryption, same as before, with less risk of a decryption problem occurring in case OpenSSL does something slightly different. I understand this will make some warnings show up, probably only in PHP 7.0-7.1 (both of them already EOL).

As I said in #79, I then tested #54 and it does work with 'aes-256-cbc', and not with 'aes-128-cbc'. At issue here was that the code in D8 only worked with 128, and I thought the data was being encrypted here the same way. I was wrong, as demonstrated by my tests.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

Alina Basarabeanu’s picture

I do not think that this was fixed as when running the PHP Compatibility using Drupal core 7.91, PHP 8.0.21 and TFA dev version I get the following:
FILE: tfa/tfa.inc
-------------------------------------------------------------------------------------------------------------------------------------------------------
FOUND 29 ERRORS AFFECTING 15 LINES
-------------------------------------------------------------------------------------------------------------------------------------------------------
647 | ERROR | Function mcrypt_module_open() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
647 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
648 | ERROR | Function mcrypt_enc_get_iv_size() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
648 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
650 | ERROR | Function mcrypt_enc_get_key_size() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
650 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
652 | ERROR | Function mcrypt_generic_init() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
652 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
656 | ERROR | Function mcrypt_generic() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
656 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
658 | ERROR | Function mcrypt_generic_deinit() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
658 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
659 | ERROR | Function mcrypt_module_close() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
659 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
704 | ERROR | Function mcrypt_module_open() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
704 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
705 | ERROR | Function mcrypt_enc_get_iv_size() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
705 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
707 | ERROR | Function mcrypt_enc_get_iv_size() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
707 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
708 | ERROR | Function mcrypt_enc_get_key_size() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
708 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
710 | ERROR | Function mcrypt_generic_init() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
710 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
712 | ERROR | Function mdecrypt_generic() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
717 | ERROR | Function mcrypt_generic_deinit() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
717 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead
718 | ERROR | Function mcrypt_module_close() is deprecated since PHP 7.1 and removed since PHP 7.2; Use OpenSSL instead
718 | ERROR | Extension 'mcrypt' is deprecated since PHP 7.1 and removed since PHP 7.2; Use openssl (preferred) or pecl/mcrypt once available instead

jcnventura’s picture

Added an annotation so that PHPCompatibility checks no longer provide false-positive warnings for the code using the optional mcrypt extension.

See https://git.drupalcode.org/project/tfa/-/commit/312cc7c4d742bcbb0c8f2909...

ezilo’s picture

It it normal that I am still getting the following error with Two-factor Authentication (TFA) 7.x-2.2 on Drupal 7.92 and PHP 7.4.32 ?

Warning: substr() expects parameter 3 to be int, string given in TfaBasePlugin->decryptLegacyDataWithOpenSSL() (line 752 of /sites/all/modules/tfa/tfa.inc).

Not sure if it is relevant, but I have only detected this with the admin user (of course admin's TFA was enabled before mcrypt was deprecated) but maybe some other users are affected too (not sure as it does not seem very easy to replicate it since this probably happens only with users with an old encryption key). If it is only the admin who is in this situation, it is obviously not a problem as I suppose that it can be easily fixed by generating new verification codes. But so far I have not done so in order to be capable of tracking the issue just in case I become aware that some user is still affected besides the admin.

jcnventura’s picture

Hi @ezilo,

Since this issue is closed, can you please create a new one and copy+paste your message above into it?

Your problem makes sense, and can be trivially fixed with casting the 3rd parameter to a number.

ezilo’s picture

Sure @jcnventura ,

New issue opened:
https://www.drupal.org/project/tfa/issues/3320552

mchaplin’s picture

#83 patch doesn't work for me.

patching file tfa.admin.inc
Reversed (or previously applied) patch detected! Assume -R? [n]

Probably have to remove it from the 4 sites we host that use it.

DamienMcKenna’s picture

@mchaplin: that means the fix is already included in the version you downloaded, so you don't need to apply it.