CVE-2020-11031: Use of a Broken or Risky Cryptographic Algorithm (CWE-327)
value: In GLPI before version 9.5.0, the encryption algorithm used is insecure. The security of the data encrypted relies on the password used, if a user sets a weak/predictable password, an attacker could decrypt data. This is fixed in version 9.5.0 by using a more secure encryption library. The library chosen is sodium.
CWE description: The product uses a broken or risky cryptographic algorithm or protocol.
CVSS v3 base score: 7.5
Published: 2020-09-23T16:15Z
Repository: https://github.com/glpi-project/glpi
Language: PHP
Files changed: .gitignore, CHANGELOG.md, ajax/mailcollector.php, composer.json, composer.lock, inc/auth.class.php, inc/authldap.class.php, inc/config.class.php, inc/console/database/installcommand.class.php, inc/console/security/changekeycommand.class.php, inc/dashboard/grid.class.php, inc/glpikey.class.php, inc/glpimailer.class.php, inc/mailcollector.class.php, inc/marketplace/api/plugins.class.php, inc/rssfeed.class.php, inc/system/requirementsmanager.class.php, inc/toolbox.class.php, inc/update.class.php, install/install.php, install/update_0782_080.php, install/update_94_95.php, status.php, tests/functionnal/AuthLdap.php, tests/imap/MailCollector.php, tests/units/Toolbox.php
Commit date: 2020-05-06 09:50:25 +0200
Commit message: Merge pull request from GHSA-7xwm-4vjr-jvqh

* Deprecate GLPIKEY usage

CVE-2020-5248

Deprecate GLPIKEY usage, and replace it with key file per instance.
Add a command to generate new key, and update database.
Add plugins hooks to rgister fields or configuration entries to be
handled when updating db.

* Rely on sodium compat for encryption/decryption

New name for key file, handle migration
Add not required sodium extension
Deprecate, fill changelog, drop old keyfile
Key must be generated from dedicated method
Diff: diff --git a/.gitignore b/.gitignore
index adf2d3ac35..5866158a62 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
 /config/config_db*
+/config/glpi.key
+/config/glpicrypt.key
 /config/local_define.php
 /tests/config_db*
 /marketplace/
 /plugins/
 /files/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b5d7050ed..02cd5d05df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,10 +25,11 @@ The present file will list all changes made to the project; according to the
 
 ### Changed
 
 - PHP error_reporting and display_errors configuration directives are no longer overrided by GLPI, unless in debug mode (which forces reporting and display of all errors).
 - `scripts/migrations/racks_plugin.php` has been replaced by `glpi:migration:racks_plugin_to_core` command available using `bin/console`
+- Encryption alogithm improved using libsodium
 
 ### API changes
 
 #### Added
 
@@ -112,10 +113,12 @@ The present file will list all changes made to the project; according to the
 - `NetworkPort::cloneItem()`
 - `Notepad::cloneItem()`
 - `ProjectCost::cloneProject()`
 - `ProjectTeam::cloneProjectTask()`
 - `ProjectTask::cloneProjectTeam()`
+- Usage of `GLPIKEY` constant
+- `Toolbox::encrypt()` and `Toolbox::decrypt()` because they use the old encryption aglogithm
 
 #### Removed
 
 - Usage of string `$condition` parameter in `CommonDBTM::find()`
 - Usage of string `$condition` parameter in `Dropdown::addNewCondition()`
diff --git a/ajax/mailcollector.php b/ajax/mailcollector.php
index aca1d1bd3d..7116b69bfd 100644
--- a/ajax/mailcollector.php
+++ b/ajax/mailcollector.php
@@ -56,11 +56,11 @@ if (isset($_REQUEST['action'])) {
 
          if (isset($input["passwd"])) {
             if (empty($input["passwd"])) {
                unset($input["passwd"]);
             } else {
-               $input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]), GLPIKEY);
+               $input["passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["passwd"]));
             }
          }
 
          if (isset($input['mail_server']) && !empty($input['mail_server'])) {
             $input["host"] = Toolbox::constructMailServerConfig($input);
diff --git a/composer.json b/composer.json
index a3c6cb0e57..a8422fb190 100644
--- a/composer.json
+++ b/composer.json
@@ -34,10 +34,11 @@
         "laminas/laminas-mime": "^2.7",
         "laminas/laminas-serializer": "^2.8",
         "mexitek/phpcolors": "^0.4.0",
         "michelf/php-markdown": "^1.6",
         "monolog/monolog": "^2.0",
+        "paragonie/sodium_compat": "^1.13",
         "phpmailer/phpmailer": "^6.0",
         "psr/log": "^1.1",
         "psr/simple-cache": "^1.0",
         "ramsey/uuid": "^4.0",
         "rlanvin/php-rrule": "^2.1",
@@ -64,17 +65,19 @@
         "patchwork/jsqueeze": "^2.0",
         "php-parallel-lint/php-parallel-lint": "^1.1",
         "sensiolabs/security-checker": "^6.0"
     },
     "replace": {
+        "paragonie/random_compat": "*",
         "symfony/polyfill-ctype": "*",
         "symfony/polyfill-intl-idn": "*",
         "symfony/polyfill-mbstring": "*",
         "symfony/polyfill-php72": "*"
     },
     "suggest": {
-        "ext-ldap": "Used to provide LDAP authentication and synchronization"
+        "ext-ldap": "Used to provide LDAP authentication and synchronization",
+        "ext-sodium": "Used to provide strong encryption for sensitive data in database"
     },
     "config": {
         "optimize-autoloader": true,
         "platform": {
             "php": "7.2.0"
diff --git a/composer.lock b/composer.lock
index f8e47e9bf5..85041f3cf4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2,11 +2,11 @@
     "_readme": [
         "This file locks the dependencies of your project to a known state",
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "267af5215d3b1148d5a2bd588feec8fa",
+    "content-hash": "889748826910604c38cf291aab5b831e",
     "packages": [
         {
             "name": "blueimp/jquery-file-upload",
             "version": "v10.20.0",
             "source": {
@@ -57,10 +57,16 @@
                 "resume",
                 "selection",
                 "upload",
                 "widget"
             ],
+            "funding": [
+                {
+                    "url": "https://github.com/blueimp",
+                    "type": "github"
+                }
+            ],
             "time": "2020-05-05T08:39:01+00:00"
         },
         {
             "name": "brick/math",
             "version": "0.8.14",
@@ -809,10 +815,16 @@
             "homepage": "https://laminas.dev",
             "keywords": [
                 "laminas",
                 "mail"
             ],
+            "funding": [
+                {
+                    "url": "https://funding.communitybridge.org/projects/laminas-project",
+                    "type": "community_bridge"
+                }
+            ],
             "time": "2020-04-21T16:42:19+00:00"
         },
         {
             "name": "laminas/laminas-mime",
             "version": "2.7.4",
@@ -1177,10 +1189,16 @@
                 "ZendFramework",
                 "autoloading",
                 "laminas",
                 "zf"
             ],
+            "funding": [
+                {
+                    "url": "https://funding.communitybridge.org/projects/laminas-project",
+                    "type": "community_bridge"
+                }
+            ],
             "time": "2020-04-03T16:01:00+00:00"
         },
         {
             "name": "mexitek/phpcolors",
             "version": "v0.4",
@@ -1354,10 +1372,92 @@
                 "logging",
                 "psr-3"
             ],
             "time": "2019-12-20T14:22:59+00:00"
         },
+        {
+            "name": "paragonie/sodium_compat",
+            "version": "v1.13.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/sodium_compat.git",
+                "reference": "bbade402cbe84c69b718120911506a3aa2bae653"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bbade402cbe84c69b718120911506a3aa2bae653",
+                "reference": "bbade402cbe84c69b718120911506a3aa2bae653",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/random_compat": ">=1",
+                "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^3|^4|^5|^6|^7"
+            },
+            "suggest": {
+                "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
+                "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "autoload.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "ISC"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com"
+                },
+                {
+                    "name": "Frank Denis",
+                    "email": "jedisct1@pureftpd.org"
+                }
+            ],
+            "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
+            "keywords": [
+                "Authentication",
+                "BLAKE2b",
+                "ChaCha20",
+                "ChaCha20-Poly1305",
+                "Chapoly",
+                "Curve25519",
+                "Ed25519",
+                "EdDSA",
+                "Edwards-curve Digital Signature Algorithm",
+                "Elliptic Curve Diffie-Hellman",
+                "Poly1305",
+                "Pure-PHP cryptography",
+                "RFC 7748",
+                "RFC 8032",
+                "Salpoly",
+                "Salsa20",
+                "X25519",
+                "XChaCha20-Poly1305",
+                "XSalsa20-Poly1305",
+                "Xchacha20",
+                "Xsalsa20",
+                "aead",
+                "cryptography",
+                "ecdh",
+                "elliptic curve",
+                "elliptic curve cryptography",
+                "encryption",
+                "libsodium",
+                "php",
+                "public-key cryptography",
+                "secret-key cryptography",
+                "side-channel resistant"
+            ],
+            "time": "2020-03-20T21:48:09+00:00"
+        },
         {
             "name": "phpmailer/phpmailer",
             "version": "v6.1.5",
             "source": {
                 "type": "git",
@@ -1414,10 +1514,24 @@
                 {
                     "name": "Brent R. Matzelle"
                 }
             ],
             "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "funding": [
+                {
+                    "url": "https://marcus.bointon.com/donations/",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/Synchro",
+                    "type": "github"
+                },
+                {
+                    "url": "https://www.patreon.com/marcusbointon",
+                    "type": "patreon"
+                }
+            ],
             "time": "2020-03-14T14:23:48+00:00"
         },
         {
             "name": "psr/cache",
             "version": "1.0.1",
@@ -1836,10 +1950,16 @@
             "keywords": [
                 "guid",
                 "identifier",
                 "uuid"
             ],
+            "funding": [
+                {
+                    "url": "https://github.com/ramsey",
+                    "type": "github"
+                }
+            ],
             "time": "2020-03-29T20:13:32+00:00"
         },
         {
             "name": "rlanvin/php-rrule",
             "version": "v2.2.0",
@@ -2550,10 +2670,24 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
             "time": "2020-03-30T11:41:10+00:00"
         },
         {
             "name": "symfony/polyfill-php73",
             "version": "v1.15.0",
@@ -2608,10 +2742,24 @@
                 "compatibility",
                 "polyfill",
                 "portable",
                 "shim"
             ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
             "time": "2020-02-27T09:26:54+00:00"
         },
         {
             "name": "symfony/service-contracts",
             "version": "v1.1.8",
@@ -4170,10 +4318,24 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
             "description": "Symfony Finder Component",
             "homepage": "https://symfony.com",
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
             "time": "2020-02-14T07:42:58+00:00"
         },
         {
             "name": "symfony/http-client",
             "version": "v4.4.2",
@@ -4406,10 +4568,24 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
             "time": "2020-02-07T20:06:44+00:00"
         },
         {
             "name": "symfony/yaml",
             "version": "v4.4.5",
@@ -4465,10 +4641,24 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
             "time": "2020-02-03T10:46:43+00:00"
         }
     ],
     "aliases": [],
     "minimum-stability": "stable",
@@ -4492,7 +4682,8 @@
     "platform-dev": {
         "ext-xml": "*"
     },
     "platform-overrides": {
         "php": "7.2.0"
-    }
+    },
+    "plugin-api-version": "1.1.0"
 }
diff --git a/inc/auth.class.php b/inc/auth.class.php
index b7a3a29fc2..695da14876 100644
--- a/inc/auth.class.php
+++ b/inc/auth.class.php
@@ -742,12 +742,11 @@ class Auth extends CommonGLPI {
                $ldapservers_status = false;
                foreach ($ldapservers as $ldap_method) {
                   $ds = AuthLDAP::connectToServer($ldap_method["host"],
                                                   $ldap_method["port"],
                                                   $ldap_method["rootdn"],
-                                                  Toolbox::decrypt($ldap_method["rootdn_passwd"],
-                                                                   GLPIKEY),
+                                                  Toolbox::sodiumDecrypt($ldap_method["rootdn_passwd"]),
                                                   $ldap_method["use_tls"],
                                                   $ldap_method["deref_option"]);
 
                   if ($ds) {
                      $ldapservers_status = true;
@@ -1706,6 +1705,6 @@ class Auth extends CommonGLPI {
 
 
    static function getIcon() {
       return "fas fa-sign-in-alt";
    }
-}
\ No newline at end of file
+}
diff --git a/inc/authldap.class.php b/inc/authldap.class.php
index 917ed315ca..fa78b6d807 100644
--- a/inc/authldap.class.php
+++ b/inc/authldap.class.php
@@ -191,12 +191,11 @@ class AuthLDAP extends CommonDBTM {
 
       if (isset($input["rootdn_passwd"])) {
          if (empty($input["rootdn_passwd"])) {
             unset($input["rootdn_passwd"]);
          } else {
-            $input["rootdn_passwd"] = Toolbox::encrypt(stripslashes($input["rootdn_passwd"]),
-                                                       GLPIKEY);
+            $input["rootdn_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["rootdn_passwd"]));
          }
       }
 
       if (isset($input["_blank_passwd"]) && $input["_blank_passwd"]) {
          $input['rootdn_passwd'] = '';
@@ -1455,11 +1454,11 @@ class AuthLDAP extends CommonDBTM {
          //Test connection to a master ldap server
          $host = $config_ldap->fields['host'];
          $port = $config_ldap->fields['port'];
       }
       $ds = self::connectToServer($host, $port, $config_ldap->fields['rootdn'],
-                                  Toolbox::decrypt($config_ldap->fields['rootdn_passwd'], GLPIKEY),
+                                  Toolbox::sodiumDecrypt($config_ldap->fields['rootdn_passwd']),
                                   $config_ldap->fields['use_tls'],
                                   $config_ldap->fields['deref_option']);
       if ($ds) {
          return true;
       }
@@ -2611,11 +2610,11 @@ class AuthLDAP extends CommonDBTM {
     */
    function connect() {
 
       return $this->connectToServer($this->fields['host'], $this->fields['port'],
                                     $this->fields['rootdn'],
-                                    Toolbox::decrypt($this->fields['rootdn_passwd'], GLPIKEY),
+                                    Toolbox::sodiumDecrypt($this->fields['rootdn_passwd']),
                                     $this->fields['use_tls'],
                                     $this->fields['deref_option']);
    }
 
 
@@ -2672,11 +2671,11 @@ class AuthLDAP extends CommonDBTM {
          Toolbox::logError("ldap_connect function is missing. Did you miss install php-ldap extension?");
          return false;
       }
       $ds = self::connectToServer($ldap_method['host'], $ldap_method['port'],
                                   $ldap_method['rootdn'],
-                                  Toolbox::decrypt($ldap_method['rootdn_passwd'], GLPIKEY),
+                                  Toolbox::sodiumDecrypt($ldap_method['rootdn_passwd']),
                                   $ldap_method['use_tls'], $ldap_method['deref_option']);
 
       // Test with login and password of the user if exists
       if (!$ds
           && !empty($login)) {
@@ -2689,11 +2688,11 @@ class AuthLDAP extends CommonDBTM {
       if (!$ds
           && ($ldap_method['id'] > 0)) {
          foreach (self::getAllReplicateForAMaster($ldap_method['id']) as $replicate) {
             $ds = self::connectToServer($replicate["host"], $replicate["port"],
                                         $ldap_method['rootdn'],
-                                        Toolbox::decrypt($ldap_method['rootdn_passwd'], GLPIKEY),
+                                        Toolbox::sodiumDecrypt($ldap_method['rootdn_passwd']),
                                         $ldap_method['use_tls'], $ldap_method['deref_option']);
 
             // Test with login and password of the user
             if (!$ds
                 && !empty($login)) {
@@ -3443,11 +3442,11 @@ class AuthLDAP extends CommonDBTM {
     */
    static function searchUser(AuthLDAP $authldap) {
 
       if (self::connectToServer($authldap->getField('host'), $authldap->getField('port'),
                                 $authldap->getField('rootdn'),
-                                Toolbox::decrypt($authldap->getField('rootdn_passwd'), GLPIKEY),
+                                Toolbox::sodiumDecrypt($authldap->getField('rootdn_passwd')),
                                 $authldap->getField('use_tls'),
                                 $authldap->getField('deref_option'))) {
          self::showLdapUsers();
 
       } else {
@@ -3501,11 +3500,11 @@ class AuthLDAP extends CommonDBTM {
       if (!self::getNumberOfServers()) {
          $input['is_default'] = 1;
       }
 
       if (isset($input["rootdn_passwd"]) && !empty($input["rootdn_passwd"])) {
-         $input["rootdn_passwd"] = Toolbox::encrypt(stripslashes($input["rootdn_passwd"]), GLPIKEY);
+         $input["rootdn_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["rootdn_passwd"]));
       }
 
       return $input;
    }
 
diff --git a/inc/config.class.php b/inc/config.class.php
index c0b0fbe467..0a144e685c 100644
--- a/inc/config.class.php
+++ b/inc/config.class.php
@@ -154,11 +154,11 @@ class Config extends CommonDBTM {
       }
       if (isset($input["smtp_passwd"])) {
          if (empty($input["smtp_passwd"])) {
             unset($input["smtp_passwd"]);
          } else {
-            $input["smtp_passwd"] = Toolbox::encrypt(stripslashes($input["smtp_passwd"]), GLPIKEY);
+            $input["smtp_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["smtp_passwd"]));
          }
       }
 
       if (isset($input["_blank_smtp_passwd"]) && $input["_blank_smtp_passwd"]) {
          $input['smtp_passwd'] = '';
@@ -166,12 +166,11 @@ class Config extends CommonDBTM {
 
       if (isset($input["proxy_passwd"])) {
          if (empty($input["proxy_passwd"])) {
             unset($input["proxy_passwd"]);
          } else {
-            $input["proxy_passwd"] = Toolbox::encrypt(stripslashes($input["proxy_passwd"]),
-                                                      GLPIKEY);
+            $input["proxy_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["proxy_passwd"]));
          }
       }
 
       if (isset($input["_blank_proxy_passwd"]) && $input["_blank_proxy_passwd"]) {
          $input['proxy_passwd'] = '';
@@ -2058,10 +2057,12 @@ class Config extends CommonDBTM {
                  'check'   => 'Mexitek\\PHPColors\\Color' ],
                [ 'name'    => 'guzzlehttp/guzzle',
                  'check'   => 'GuzzleHttp\\Client' ],
                [ 'name'    => 'wapmorgan/unified-archive',
                  'check'   => 'wapmorgan\\UnifiedArchive\\UnifiedArchive' ],
+               [ 'name'    => 'paragonie/sodium_compat',
+                 'check'   => 'ParagonIE_Sodium_Compat' ],
       ];
       if (Toolbox::canUseCAS()) {
          $deps[] = [
             'name'    => 'phpCas',
             'version' => phpCAS::getVersion(),
@@ -2521,10 +2522,13 @@ class Config extends CommonDBTM {
             'exif' => [
                'required'  => false
             ],
             'intl' => [
                'required' => true
+            ],
+            'sodium' => [
+               'required' => false
             ]
          ];
       } else {
          $extensions_to_check = $list;
       }
diff --git a/inc/console/database/installcommand.class.php b/inc/console/database/installcommand.class.php
index 2136a7c147..0d3eda9cfa 100644
--- a/inc/console/database/installcommand.class.php
+++ b/inc/console/database/installcommand.class.php
@@ -35,10 +35,11 @@ namespace Glpi\Console\Database;
 if (!defined('GLPI_ROOT')) {
    die("Sorry. You can't access this file directly");
 }
 
 use DB;
+use GLPIKey;
 use Toolbox;
 
 use Symfony\Component\Console\Exception\RuntimeException;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
@@ -73,10 +74,17 @@ class InstallCommand extends AbstractConfigureCommand {
     *
     * @var integer
     */
    const ERROR_MISSING_REQUIREMENTS = 8;
 
+   /**
+    * Error code returned when failing to create encryption key file.
+    *
+    * @var integer
+    */
+   const ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE = 9;
+
    protected function configure() {
 
       parent::configure();
 
       $this->setName('glpi:database:install');
@@ -208,10 +216,18 @@ class InstallCommand extends AbstractConfigureCommand {
             );
             return 0;
          }
       }
 
+      // Create security key
+      $glpikey = new GLPIKey();
+      if (!$glpikey->keyExists() && !$glpikey->generate()) {
+         $message = __('Security key cannot be generated!');
+         $output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
+         return self::ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE;
+      }
+
       $mysqli = new \mysqli();
       if (intval($db_port) > 0) {
          // Network port
          @$mysqli->connect($db_host, $db_user, $db_pass, null, $db_port);
       } else {
diff --git a/inc/console/security/changekeycommand.class.php b/inc/console/security/changekeycommand.class.php
new file mode 100644
index 0000000000..be84510844
--- /dev/null
+++ b/inc/console/security/changekeycommand.class.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * ---------------------------------------------------------------------
+ * GLPI - Gestionnaire Libre de Parc Informatique
+ * Copyright (C) 2015-2018 Teclib' and contributors.
+ *
+ * http://glpi-project.org
+ *
+ * based on GLPI - Gestionnaire Libre de Parc Informatique
+ * Copyright (C) 2003-2014 by the INDEPNET Development Team.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * LICENSE
+ *
+ * This file is part of GLPI.
+ *
+ * GLPI is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GLPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GLPI. If not, see <http://www.gnu.org/licenses/>.
+ * ---------------------------------------------------------------------
+ */
+
+namespace Glpi\Console\Security;
+
+if (!defined('GLPI_ROOT')) {
+   die("Sorry. You can't access this file directly");
+}
+
+use Glpi\Console\AbstractCommand;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use GLPIKey;
+
+class ChangekeyCommand extends AbstractCommand {
+   /**
+    * Error code returned when unable to renew key.
+    *
+    * @var integer
+    */
+   const ERROR_UNABLE_TO_RENEW_KEY = 1;
+
+   protected function configure() {
+      parent::configure();
+
+      $this->setName('glpi:security:change_key');
+      $this->setDescription(__('Change password storage key and update values in database.'));
+   }
+
+   protected function execute(InputInterface $input, OutputInterface $output) {
+      $glpikey = new GLPIKey();
+
+      $fields = $glpikey->getFields();
+      $configs = $glpikey->getConfigs();
+      $conf_count = 0;
+      foreach ($configs as $config) {
+         $conf_count += count($config);
+      }
+
+      $output->writeln(
+         sprintf(
+            '<info>' . __('Found %1$s field(s) and %2$s configuration entries requiring migration.') . '</info>',
+            count($fields),
+            $conf_count
+         )
+      );
+
+      if (!$input->getOption('no-interaction')) {
+         // Ask for confirmation (unless --no-interaction)
+         $question_helper = $this->getHelper('question');
+         $run = $question_helper->ask(
+            $input,
+            $output,
+            new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
+         );
+         if (!$run) {
+            $output->writeln(
+               '<comment>' . __('Aborted.') . '</comment>',
+               OutputInterface::VERBOSITY_VERBOSE
+            );
+            return 0;
+         }
+      }
+
+      $created = $glpikey->generate();
+      if (!$created) {
+         $output->writeln(
+            '<error>' . __('Unable to change security key!') . '</error>',
+            OutputInterface::VERBOSITY_QUIET
+         );
+         return self::ERROR_UNABLE_TO_RENEW_KEY;
+      }
+
+      $this->output->write(PHP_EOL);
+
+      $output->writeln('<info>' . __('New security key generated; database updated.') . '</info>');
+
+      return 0; // Success
+   }
+}
diff --git a/inc/dashboard/grid.class.php b/inc/dashboard/grid.class.php
index 8b6647485b..14cbd101b3 100644
--- a/inc/dashboard/grid.class.php
+++ b/inc/dashboard/grid.class.php
@@ -460,11 +460,11 @@ JAVASCRIPT;
       // show embeded dashboard
       $this->show(true);
    }
 
    static function getToken(string $dasboard = "", int $entities_id = 0, int $is_recursive = 0): string {
-      $seed         = $dasboard.$entities_id.$is_recursive.GLPIKEY;
+      $seed         = $dasboard.$entities_id.$is_recursive.Telemetry::getInstanceUuid();
       $uuid         = Uuid::uuid5(Uuid::NAMESPACE_OID, $seed);
       $token        = $uuid->toString();
 
       return $token;
    }
@@ -1364,6 +1364,6 @@ HTML;
          }
       }
 
       return \Dropdown::showFromArray($name, $options_dashboards, $params);
    }
-}
\ No newline at end of file
+}
diff --git a/inc/glpikey.class.php b/inc/glpikey.class.php
new file mode 100644
index 0000000000..5dbce1dbc2
--- /dev/null
+++ b/inc/glpikey.class.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * ---------------------------------------------------------------------
+ * GLPI - Gestionnaire Libre de Parc Informatique
+ * Copyright (C) 2015-2018 Teclib' and contributors.
+ *
+ * http://glpi-project.org
+ *
+ * based on GLPI - Gestionnaire Libre de Parc Informatique
+ * Copyright (C) 2003-2014 by the INDEPNET Development Team.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * LICENSE
+ *
+ * This file is part of GLPI.
+ *
+ * GLPI is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GLPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GLPI. If not, see <http://www.gnu.org/licenses/>.
+ * ---------------------------------------------------------------------
+ */
+
+if (!defined('GLPI_ROOT')) {
+   die("Sorry. You can't access this file directly");
+}
+
+/**
+ *  GLPI security key
+**/
+class GLPIKey {
+   /**
+    * Key file path.
+    *
+    * @var string
+    */
+   private $keyfile;
+
+   /**
+    * Legacy key file path.
+    *
+    * @var string
+    */
+   private $legacykeyfile;
+
+   /**
+    * List of crypted DB fields.
+    *
+    * @var array
+    */
+   protected $fields = [
+      'glpi_mailcollectors.passwd',
+      'glpi_authldaps.rootdn_passwd'
+   ];
+
+   /**
+    * List of crypted configuration values.
+    * Each key corresponds to a configuration context, and contains list of configs names.
+    *
+    * @var array
+    */
+   protected $configs = [
+      'core'   => [
+         'smtp_passwd',
+         'proxy_passwd'
+      ]
+   ];
+
+   public function __construct() {
+      $this->keyfile = GLPI_CONFIG_DIR . '/glpicrypt.key';
+      $this->legacykeyfile = GLPI_CONFIG_DIR . '/glpi.key';
+   }
+
+   /**
+    * Check if GLPI security key used for decryptable passwords exists
+    *
+    * @return string
+    */
+   public function keyExists() {
+      return file_exists($this->keyfile) && !empty($this->get());
+   }
+
+   /**
+    * Get GLPI security key used for decryptable passwords
+    *
+    * @throw \RuntimeException if key file is missing
+    *
+    * @return string
+    */
+   public function get() {
+      if (!file_exists($this->keyfile)) {
+         throw new \RuntimeException('You must create a security key, see glpi:security:change_key command.');
+      }
+      //load key from existing config file
+      $key = file_get_contents($this->keyfile);
+      return $key;
+   }
+
+   /**
+    * Get GLPI security legacy key that was used for decryptable passwords
+    *
+    * @return string
+    *
+    * @deprecated 9.5.0
+    */
+   public function getLegacyKey() {
+      Toolbox::deprecated();
+
+      if (!file_exists($this->legacykeyfile)) {
+         return GLPIKEY;
+      }
+      //load key from existing config file
+      $key = file_get_contents($this->legacykeyfile);
+      return $key;
+   }
+
+   /**
+    * Generate GLPI security key used for decryptable passwords
+    * and update values in DB if necessary.
+    * @return boolean
+    */
+   public function generate() {
+      global $DB;
+
+      $sodium_key = null;
+      $old_key = false;
+
+      try {
+         $sodium_key = $this->get();
+      } catch (\RuntimeException $e) {
+         $sodium_key = null;
+         $old_key = @$this->getLegacyKey();
+      }
+
+      $key = sodium_crypto_aead_chacha20poly1305_ietf_keygen();
+      $success = (bool)file_put_contents($this->keyfile, $key);
+      if (!$success) {
+         return false;
+      }
+
+      if ($DB instanceof DBmysql) {
+         return $this->migrateFieldsInDb($sodium_key, $old_key)
+            && $this->migrateConfigsInDb($sodium_key, $old_key);
+      }
+
+      return true;
+   }
+
+   /**
+    * Get fields
+    *
+    * @return array
+    */
+   public function getFields() :array {
+      global $PLUGIN_HOOKS;
+
+      $fields = $this->fields;
+      if (isset($PLUGIN_HOOKS['secured_fields'])) {
+         foreach ($PLUGIN_HOOKS['secured_fields'] as $plugfields) {
+            $fields = array_merge($fields, $plugfields);
+         }
+      }
+
+      return $fields;
+   }
+
+   /**
+    * Get configs
+    *
+    * @return array
+    */
+   public function getConfigs() :array {
+      global $PLUGIN_HOOKS;
+
+      $configs = $this->configs;
+
+      if (isset($PLUGIN_HOOKS['secured_configs'])) {
+         foreach ($PLUGIN_HOOKS['secured_configs'] as $plugin => $plugconfigs) {
+            $configs['plugin:' . $plugin] = $plugconfigs;
+         }
+      }
+
+      return $configs;
+   }
+
+   /**
+    * Migrate fields in database
+    *
+    * @param string       $sodium_key Current key
+    * @param string|false $old_key     Old key, if any
+    *
+    * @return void
+    */
+   protected function migrateFieldsInDb($sodium_key, $old_key = false) {
+      global $DB;
+
+      $success = true;
+
+      foreach ($this->getFields() as $field) {
+         list($table, $column) = explode('.', $field);
+
+         $iterator = $DB->request([
+            'SELECT' => ['id', $column],
+            'FROM'   => $table
+         ]);
+
+         while ($success && $row = $iterator->next()) {
+            if ($old_key === false) {
+               $pass = Toolbox::sodiumEncrypt(Toolbox::sodiumDecrypt($row[$column], $sodium_key));
+            } else {
+               $pass = Toolbox::sodiumEncrypt(Toolbox::decrypt($row[$column], $old_key));
+            }
+            $success = $DB->update(
+               $table,
+               [$field  => $pass],
+               ['id'    => $row['id']]
+            );
+         }
+      }
+
+      return $success;
+   }
+
+   /**
+    * Migrate configurations in database
+    *
+    * @param string       $sodium_key Current key
+    * @param string|false $old_key    Old key, if any
+    *
+    * @return boolean
+    */
+   protected function migrateConfigsInDb($sodium_key, $old_key = false) {
+      global $DB;
+
+      $success = true;
+
+      foreach ($this->getConfigs() as $context => $names) {
+         $iterator = $DB->request([
+            'FROM'   => Config::getTable(),
+            'WHERE'  => [
+               'context'   => $context,
+               'name'      => $names
+            ]
+         ]);
+
+         while ($success && $row = $iterator->next()) {
+            if ($old_key === false) {
+               $pass = Toolbox::sodiumEncrypt(Toolbox::sodiumDecrypt($row['value'], $sodium_key));
+            } else {
+               $pass = Toolbox::sodiumEncrypt(Toolbox::decrypt($row['value'], $old_key));
+            }
+            $success = $DB->update(
+               Config::getTable(),
+               ['value' => $pass],
+               ['id'    => $row['id']]
+            );
+         }
+      }
+
+      return $success;
+   }
+}
diff --git a/inc/glpimailer.class.php b/inc/glpimailer.class.php
index 6bb85b8cde..bdb85e589e 100644
--- a/inc/glpimailer.class.php
+++ b/inc/glpimailer.class.php
@@ -63,11 +63,11 @@ class GLPIMailer extends PHPMailer {
          $this->Host   = $CFG_GLPI['smtp_host'].':'.$CFG_GLPI['smtp_port'];
 
          if ($CFG_GLPI['smtp_username'] != '') {
             $this->SMTPAuth = true;
             $this->Username = $CFG_GLPI['smtp_username'];
-            $this->Password = Toolbox::decrypt($CFG_GLPI['smtp_passwd'], GLPIKEY);
+            $this->Password = Toolbox::sodiumDecrypt($CFG_GLPI['smtp_passwd']);
          }
 
          if ($CFG_GLPI['smtp_mode'] == MAIL_SMTPSSL) {
             $this->SMTPSecure = "ssl";
          }
diff --git a/inc/mailcollector.class.php b/inc/mailcollector.class.php
index 6c46154efd..6144778d72 100644
--- a/inc/mailcollector.class.php
+++ b/inc/mailcollector.class.php
@@ -136,11 +136,11 @@ class MailCollector  extends CommonDBTM {
 
       if (isset($input["passwd"])) {
          if (empty($input["passwd"])) {
             unset($input["passwd"]);
          } else {
-            $input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]), GLPIKEY);
+            $input["passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["passwd"]));
          }
       }
 
       if (isset($input['mail_server']) && !empty($input['mail_server'])) {
          $input["host"] = Toolbox::constructMailServerConfig($input);
@@ -1247,11 +1247,11 @@ class MailCollector  extends CommonDBTM {
       $config = Toolbox::parseMailServerConnectString($this->fields['host']);
 
       $params = [
          'host'      => $config['address'],
          'user'      => $this->fields['login'],
-         'password'  => Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
+         'password'  => Toolbox::sodiumDecrypt($this->fields['passwd']),
          'port'      => $config['port']
       ];
 
       if ($config['ssl']) {
          $params['ssl'] = 'SSL';
@@ -1289,11 +1289,11 @@ class MailCollector  extends CommonDBTM {
       /** FIXME: find the equivalent for those cases with zend-mail? */
       /*if ($this->fields['use_kerberos']) {
          $this->marubox = imap_open(
             $this->fields['host'],
             $this->fields['login'],
-            Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
+            Toolbox::sodiumDecrypt($this->fields['passwd']),
             CL_EXPUNGE,
             1
          );
       } else {
          $try_options = [
@@ -1302,11 +1302,11 @@ class MailCollector  extends CommonDBTM {
          ];
          foreach ($try_options as $option) {
             $this->marubox = imap_open(
                $this->fields['host'],
                $this->fields['login'],
-               Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
+               Toolbox::sodiumDecrypt($this->fields['passwd']),
                CL_EXPUNGE,
                1,
                $option
             );
             if (false === $this->marubox) {
diff --git a/inc/marketplace/api/plugins.class.php b/inc/marketplace/api/plugins.class.php
index d07c215ad1..62a06f88de 100644
--- a/inc/marketplace/api/plugins.class.php
+++ b/inc/marketplace/api/plugins.class.php
@@ -62,11 +62,11 @@ class Plugins {
       ];
 
       // add proxy string if configured in glpi
       if (!empty($CFG_GLPI["proxy_name"])) {
          $proxy_creds      = !empty($CFG_GLPI["proxy_user"])
-            ? $CFG_GLPI["proxy_user"].":".Toolbox::decrypt($CFG_GLPI["proxy_passwd"], GLPIKEY)."@"
+            ? $CFG_GLPI["proxy_user"].":".Toolbox::sodiumDecrypt($CFG_GLPI["proxy_passwd"])."@"
             : "";
          $proxy_string     = "http://{$proxy_creds}".$CFG_GLPI['proxy_name'].":".$CFG_GLPI['proxy_port'];
          $options['proxy'] = $proxy_string;
       }
 
diff --git a/inc/rssfeed.class.php b/inc/rssfeed.class.php
index e31b8faebe..e380d2e7ec 100644
--- a/inc/rssfeed.class.php
+++ b/inc/rssfeed.class.php
@@ -874,12 +874,11 @@ class RSSFeed extends CommonDBVisible {
          $prx_opt[CURLOPT_PROXY]     = $CFG_GLPI["proxy_name"];
          $prx_opt[CURLOPT_PROXYPORT] = $CFG_GLPI["proxy_port"];
          if (!empty($CFG_GLPI["proxy_user"])) {
             $prx_opt[CURLOPT_HTTPAUTH]     = CURLAUTH_ANYSAFE;
             $prx_opt[CURLOPT_PROXYUSERPWD] = $CFG_GLPI["proxy_user"].":".
-                                             Toolbox::decrypt($CFG_GLPI["proxy_passwd"],
-                                                              GLPIKEY);
+                                             Toolbox::sodiumDecrypt($CFG_GLPI["proxy_passwd"]);
          }
          $feed->set_curl_options($prx_opt);
       }
 
       $feed->enable_cache(true);
diff --git a/inc/system/requirementsmanager.class.php b/inc/system/requirementsmanager.class.php
index 1fb42799b9..44fbdd4afc 100644
--- a/inc/system/requirementsmanager.class.php
+++ b/inc/system/requirementsmanager.class.php
@@ -87,10 +87,11 @@ class RequirementsManager {
       $requirements[] = new Extension('xmlrpc', true); // for XMLRPC API
       $requirements[] = new ExtensionClass('CAS', 'phpCAS', true); // for CAS lib
       $requirements[] = new Extension('exif', true);
       $requirements[] = new Extension('zip', true);
       $requirements[] = new Extension('bz2', true);
+      $requirements[] = new Extension('sodium', true);
 
       if ($db instanceof \DBmysql) {
          $requirements[] = new DbEngine($db);
          $requirements[] = new DbTimezones($db);
       }
diff --git a/inc/toolbox.class.php b/inc/toolbox.class.php
index 1d96d4a25e..522b1e046f 100644
--- a/inc/toolbox.class.php
+++ b/inc/toolbox.class.php
@@ -239,11 +239,17 @@ class Toolbox {
     * @param string $string  string to encrypt
     * @param string $key     key used to encrypt
     *
     * @return string  encrypted string
    **/
-   static function encrypt($string, $key) {
+   static function encrypt($string, $key = null) {
+      self::deprecated('Use sodiumEncrypt');
+
+      if ($key === null) {
+         $glpikey = new GLPIKey();
+         $key = $glpikey->getLegacyKey();
+      }
 
       $result = '';
       for ($i=0; $i<strlen($string); $i++) {
          $char    = substr($string, $i, 1);
          $keychar = substr($key, ($i % strlen($key))-1, 1);
@@ -251,20 +257,60 @@ class Toolbox {
          $result .= $char;
       }
       return base64_encode($result);
    }
 
+   public static function sodiumEncrypt($content, $key = null) {
+      if ($key === null) {
+         $key = self::getGlpiSecKey();
+      }
+
+      $nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES); // NONCE = Number to be used ONCE, for each message
+      $encrypted = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
+         $content,
+         $nonce,
+         $nonce,
+         $key
+      );
+      return base64_encode($nonce . $encrypted);
+   }
+
+   public static function sodiumDecrypt($content, $key = null) {
+      if ($key === null) {
+         $key = self::getGlpiSecKey();
+      }
+
+      $content = base64_decode($content);
+      $nonce = mb_substr($content, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES, '8bit');
+      $ciphertext = mb_substr($content, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES, null, '8bit');
+      $plaintext = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
+         $ciphertext,
+         $nonce,
+         $nonce,
+         $key
+      );
+      if (!is_string($plaintext)) {
+         throw new \RuntimeException('Unable to decrypt content');
+      }
+      return $plaintext;
+   }
 
    /**
     * Decrypt a string
     *
     * @param string $string  string to decrypt
     * @param string $key     key used to decrypt
     *
     * @return string  decrypted string
    **/
-   static function decrypt($string, $key) {
+   static function decrypt($string, $key = null) {
+      self::deprecated('Use sodiumDecrypt');
+
+      if ($key === null) {
+         $glpikey = new GLPIKey();
+         $key = $glpikey->getLegacyKey();
+      }
 
       $result = '';
       $string = base64_decode($string);
 
       for ($i=0; $i<strlen($string); $i++) {
@@ -275,10 +321,22 @@ class Toolbox {
       }
 
       return Toolbox::unclean_cross_side_scripting_deep($result);
    }
 
+   /**
+    * Get GLPI security key used for decryptable passwords from file
+    *
+    * @throw \RuntimeException if key file is missing
+    *
+    * @return string
+    */
+   public static function getGlpiSecKey() {
+      $glpikey = new GLPIKey();
+      return $glpikey->get();
+   }
+
 
    /**
     * Prevent from XSS
     * Clean code
     *
@@ -1612,11 +1670,11 @@ class Toolbox {
          ];
 
          if (!empty($CFG_GLPI["proxy_user"])) {
             $opts += [
                CURLOPT_PROXYAUTH    => CURLAUTH_BASIC,
-               CURLOPT_PROXYUSERPWD => $CFG_GLPI["proxy_user"] . ":" . self::decrypt($CFG_GLPI["proxy_passwd"], GLPIKEY),
+               CURLOPT_PROXYUSERPWD => $CFG_GLPI["proxy_user"] . ":" . self::sodiumDecrypt($CFG_GLPI["proxy_passwd"]),
             ];
          }
 
          if ($defaultport == 443) {
             $opts += [
diff --git a/inc/update.class.php b/inc/update.class.php
index 387e7b33b9..d2f956292c 100644
--- a/inc/update.class.php
+++ b/inc/update.class.php
@@ -161,11 +161,12 @@ class Update extends CommonGLPI {
       ini_set("max_execution_time", "0");
 
       $updir = __DIR__ . "/../install/";
 
       if (isCommandLine() && version_compare($current_version, '0.72.3', 'lt')) {
-         die('Upgrade from command line is not supported before 0.72.3!');
+         echo 'Upgrade from command line is not supported before 0.72.3!';
+         die(1);
       }
 
       // Update process desactivate all plugins
       $plugin = new Plugin();
       $plugin->unactivateAll();
@@ -509,10 +510,16 @@ class Update extends CommonGLPI {
       // reset telemetry
       $crontask_telemetry = new CronTask;
       $crontask_telemetry->getFromDBbyName("Telemetry", "telemetry");
       $crontask_telemetry->resetDate();
       $crontask_telemetry->resetState();
+
+      //generate security key if missing, and update db
+      $glpikey = new GLPIKey();
+      if (!$glpikey->keyExists() && !$glpikey->generate()) {
+         $this->migration->displayWarning(__('Unable to create security key file!'), true);
+      }
    }
 
    /**
     * Declare old items for compatibility
     *
diff --git a/install/install.php b/install/install.php
index b357527dd3..0c939a89fc 100644
--- a/install/install.php
+++ b/install/install.php
@@ -366,10 +366,23 @@ function step4 ($databasename, $newdatabasename) {
       echo "<p class='submit'><input type='submit' name='submit' class='submit' value='".
              __('Continue')."'></p>";
       Html::closeForm();
    }
 
+   //create security key
+   $glpikey = new GLPIKey();
+   $secured = $glpikey->keyExists();
+   if (!$secured) {
+      $secured = $glpikey->generate();
+   }
+
+   if (!$secured) {
+      echo "<p><strong>".__('Security key cannot be generated!')."</strong></p>";
+      prev_form($host, $user, $password);
+      return;
+   }
+
    //Check if the port is in url
    $hostport = explode(":", $host);
    if (count($hostport) < 2) {
       $link = new mysqli($hostport[0], $user, $password);
    } else {
@@ -519,10 +532,11 @@ function step8() {
    );
 
    Session::destroy(); // Remove session data (debug mode for instance) set by web installation
 
    echo "<h2>".__('The installation is finished')."</h2>";
+
    echo "<p>".__('Default logins / passwords are:')."</p>";
    echo "<p><ul><li> ".__('glpi/glpi for the administrator account')."</li>";
    echo "<li>".__('tech/tech for the technician account')."</li>";
    echo "<li>".__('normal/normal for the normal account')."</li>";
    echo "<li>".__('post-only/postonly for the postonly account')."</li></ul></p>";
diff --git a/install/update_0782_080.php b/install/update_0782_080.php
index a65709f609..e9b5e9323c 100644
--- a/install/update_0782_080.php
+++ b/install/update_0782_080.php
@@ -949,11 +949,11 @@ function update0782to080() {
       if ($result=$DB->query($query)) {
          if ($DB->numrows($result)>0) {
             $value = $DB->result($result, 0, 0);
             if (!empty($value)) {
                $query = "UPDATE `glpi_configs`
-                         SET `proxy_passwd` = '".addslashes(Toolbox::encrypt($value, GLPIKEY))."'
+                         SET `proxy_passwd` = '".addslashes(Toolbox::encrypt($value))."'
                          WHERE `id` = '1' ";
                $DB->queryOrDie($query, "0.80 update proxy_passwd in glpi_configs");
             }
          }
       }
@@ -971,11 +971,11 @@ function update0782to080() {
       if ($result=$DB->query($query)) {
          if ($DB->numrows($result)>0) {
             $value = $DB->result($result, 0, 0);
             if (!empty($value)) {
                $query = "UPDATE `glpi_configs`
-                         SET `smtp_passwd` = '".addslashes(Toolbox::encrypt($value, GLPIKEY))."'
+                         SET `smtp_passwd` = '".addslashes(Toolbox::encrypt($value))."'
                          WHERE `id` = '1' ";
                $DB->queryOrDie($query, "0.80 update smtp_passwd in glpi_configs");
             }
          }
       }
@@ -994,12 +994,11 @@ function update0782to080() {
       if ($result = $DB->query($query)) {
          if ($DB->numrows($result)) {
             while ($data = $DB->fetchAssoc($result)) {
                if (!empty($data['rootdn_password'])) {
                   $query = "UPDATE `glpi_authldaps`
-                            SET `rootdn_passwd` = '".addslashes(Toolbox::encrypt($data['rootdn_password'],
-                                                                                 GLPIKEY))."'
+                            SET `rootdn_passwd` = '".addslashes(Toolbox::encrypt($data['rootdn_password']))."'
                             WHERE `id` = '".$data['id']."' ";
                   $DB->queryOrDie($query, "0.80 update rootdn_passwd in glpi_authldaps");
                }
             }
          }
@@ -1121,12 +1120,11 @@ function update0782to080() {
       if ($result = $DB->query($query)) {
          if ($DB->numrows($result)) {
             while ($data = $DB->fetchAssoc($result)) {
                if (!empty($data['password'])) {
                   $query = "UPDATE `glpi_mailcollectors`
-                            SET `passwd` = '".addslashes(Toolbox::encrypt($data['password'],
-                                                                          GLPIKEY))."'
+                            SET `passwd` = '".addslashes(Toolbox::encrypt($data['password']))."'
                             WHERE `id`= '".$data['id']."' ";
                   $DB->queryOrDie($query, "0.80 update passwd in glpi_mailcollectors");
                }
             }
          }
diff --git a/install/update_94_95.php b/install/update_94_95.php
index e8a15d930f..0cc87a4c8e 100644
--- a/install/update_94_95.php
+++ b/install/update_94_95.php
@@ -1782,10 +1782,28 @@ HTML
             'condition' => 'WHERE `id` = 0'
          ]
       );
    }
 
+   /** Appliances & webapps */
+   require __DIR__ . '/update_94_95/appliances.php';
+   /** /Appliances & webapps */
+
+   // ************ Keep it at the end **************
+   foreach ($ADDTODISPLAYPREF as $type => $tab) {
+      $rank = 1;
+      foreach ($tab as $newval) {
+         $DB->updateOrInsert("glpi_displaypreferences", [
+            'rank'      => $rank++
+         ], [
+            'users_id'  => "0",
+            'itemtype'  => $type,
+            'num'       => $newval,
+         ]);
+      }
+   }
+
    // Add new field states in contract
    if (!$DB->fieldExists('glpi_states', 'is_visible_contract')) {
       $migration->addField('glpi_states', 'is_visible_contract', 'bool', [
          'value' => 1,
          'after' => 'is_visible_cluster'
diff --git a/status.php b/status.php
index 2149c44454..46107ef4c2 100644
--- a/status.php
+++ b/status.php
@@ -102,12 +102,11 @@ if (($ok_master || $ok_slave )
       echo "Check LDAP servers:";
 
       foreach ($ldap_methods as $method) {
          echo " ".$method['name'];
          if (AuthLDAP::tryToConnectToServer($method, $method["rootdn"],
-                                            Toolbox::decrypt($method["rootdn_passwd"],
-                                            GLPIKEY))) {
+                                            Toolbox::sodiumDecrypt($method["rootdn_passwd"]))) {
             echo "_OK";
          } else {
             echo "_PROBLEM";
             $ok = false;
          }
diff --git a/tests/functionnal/AuthLdap.php b/tests/functionnal/AuthLdap.php
index a68d4b8211..14cac676c0 100644
--- a/tests/functionnal/AuthLdap.php
+++ b/tests/functionnal/AuthLdap.php
@@ -152,32 +152,10 @@ class AuthLDAP extends DbTestCase {
       //no rootdn_passwd set : should not appear in the response array
       $input  = ['name' => 'ldap'];
       $result = $ldap->prepareInputForUpdate($input);
       $this->array($result)->notHasKey('rootdn_passwd');
 
-      //rootdn_passwd is set with a value (a password, not encrypted)
-      $password = 'toto';
-      $input    = ['name' => 'ldap', 'rootdn_passwd' => $password];
-      $result   = $ldap->prepareInputForUpdate($input);
-
-      //Expected value to be encrypted using GLPIKEY key
-      $expected = \Toolbox::encrypt(stripslashes($password), GLPIKEY);
-      $this->string($result['rootdn_passwd'])->isIdenticalTo($expected);
-
-      $password = 'tot\'o';
-      $input    = ['name' => 'ldap', 'rootdn_passwd' => $password];
-      $result   = $ldap->prepareInputForUpdate($input);
-
-      //Expected value to be encrypted using GLPIKEY key
-      $expected = \Toolbox::encrypt(stripslashes($password), GLPIKEY);
-      $this->string($result['rootdn_passwd'])->isIdenticalTo($expected);
-
-      $input['_blank_passwd'] = 1;
-      $result   = $ldap->prepareInputForUpdate($input);
-      //rootdn_passwd is set but empty
-      $this->string($result['rootdn_passwd'])->isEmpty();
-
       //Field name finishing with _field : set the value in lower case
       $input['_login_field'] = 'TEST';
       $result         = $ldap->prepareInputForUpdate($input);
       $this->string($result['_login_field'])->isIdenticalTo('test');
 
@@ -596,6 +574,50 @@ class AuthLDAP extends DbTestCase {
       $this->string(\AuthLDAP::getFieldValue($infos, 'field'))->isIdenticalTo('value');
 
       $infos = ['objectguid' => 'value'];
       $this->string(\AuthLDAP::getFieldValue($infos, 'objectguid'))->isIdenticalTo('value');
    }
+
+   public function testPassword() {
+      $ldap = new \AuthLDAP();
+      $id = (int)$ldap->add([
+         'name'        => 'LDAPcrypted',
+         'is_active'   => 1,
+         'is_default'  => 0,
+         'basedn'      => 'ou=people,dc=mycompany',
+         'login_field' => 'uid',
+         'phone_field' => 'phonenumber'
+      ]);
+      $this->integer($id)->isGreaterThan(0);
+
+      //rootdn_passwd is set with a value (a password, not encrypted)
+      $password = 'toto';
+      $input    = ['id' => $id, 'name' => 'ldap', 'rootdn_passwd' => $password];
+      $this->boolean($ldap->update($input))->isTrue();
+      $this->boolean($ldap->getFromDB($id))->isTrue();
+
+      //Expected value to be encrypted using current  key
+      $this->string(\Toolbox::sodiumDecrypt($ldap->fields['rootdn_passwd']))->isIdenticalTo($password);
+
+      $password = 'tot\'o';
+      $input    = ['id' => $id, 'name' => 'ldap', 'rootdn_passwd' => $password];
+      $this->boolean($ldap->update($input))->isTrue();
+      $this->boolean($ldap->getFromDB($id))->isTrue();
+
+      //Expected value to be encrypted using current key
+      $this->string(\Toolbox::sodiumDecrypt($ldap->fields['rootdn_passwd']))->isIdenticalTo($password);
+
+      $password = 'tot\'o';
+      $input    = ['id' => $id, 'name' => 'ldap', 'rootdn_passwd' => \Toolbox::addslashes_deep($password)];
+      $this->boolean($ldap->update($input))->isTrue();
+      $this->boolean($ldap->getFromDB($id))->isTrue();
+
+      //Expected value to be encrypted using current key
+      $this->string(\Toolbox::sodiumDecrypt($ldap->fields['rootdn_passwd']))->isIdenticalTo($password);
+
+      $input['_blank_passwd'] = 1;
+      $result   = $ldap->prepareInputForUpdate($input);
+      //rootdn_passwd is set but empty
+      $this->string($result['rootdn_passwd'])->isEmpty();
+
+   }
 }
diff --git a/tests/imap/MailCollector.php b/tests/imap/MailCollector.php
index 1910dfa5e0..4e541a40eb 100644
--- a/tests/imap/MailCollector.php
+++ b/tests/imap/MailCollector.php
@@ -114,11 +114,11 @@ class MailCollector extends DbTestCase {
          'is_active' => true
       ];
 
       $this->array($this->testedInstance->prepareInput($oinput, 'add'))
          ->isIdenticalTo([
-            'passwd'    => \Toolbox::encrypt($oinput["passwd"], GLPIKEY),
+            'passwd'    => \Toolbox::sodiumEncrypt($oinput["passwd"]),
             'is_active' => true
          ]);
 
       //empty password means no password.
       $oinput = [
@@ -367,6 +367,6 @@ class MailCollector extends DbTestCase {
       while ($data = $iterator->next()) {
          print_r($data);
       }
        */
    }
-}
\ No newline at end of file
+}
diff --git a/tests/units/Toolbox.php b/tests/units/Toolbox.php
index 277db83876..de71e34d26 100644
--- a/tests/units/Toolbox.php
+++ b/tests/units/Toolbox.php
@@ -345,22 +345,25 @@ class Toolbox extends \GLPITestCase {
          ['My string', 'mykey', 'xuaZ3tnr1ufS'],
          ['keepmysecret', 'keepmykey', '5NDK1d3m7NDI69DZ']
       ];
    }
 
-   /**
-    * @dataProvider encryptProvider
-    */
-   public function testEncrypt($string, $key, $expected) {
-      $this->string(\Toolbox::encrypt($string, $key))->isIdenticalTo($expected);
+   protected function sodiumEncryptProvider() {
+      return [
+         ['My string'],
+         ['keepmysecret'],
+         ['This is a strng I want to crypt, with some unusual chars like %, \', @, and so on!']
+      ];
    }
 
    /**
-    * @dataProvider encryptProvider
+    * @dataProvider sodiumEncryptProvider
     */
-   public function testDecrypt($expected, $key, $string) {
-      $this->string(\Toolbox::decrypt($string, $key))->isIdenticalTo($expected);
+   public function testSodiumEncrypt($string) {
+      $crypted = \Toolbox::sodiumEncrypt($string);
+      $this->string($crypted)->isNotEmpty();
+      $this->string(\Toolbox::sodiumDecrypt($crypted))->isIdenticalTo($string);
    }
 
    protected function cleanProvider() {
       return [
          ['mystring', 'mystring', null, 15, 0.56, false],

Vulnerable code:                $input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]), GLPIKEY);
        "ext-ldap": "Used to provide LDAP authentication and synchronization"
    "content-hash": "267af5215d3b1148d5a2bd588feec8fa",
    }
                                                  Toolbox::decrypt($ldap_method["rootdn_passwd"],
                                                                   GLPIKEY),
}
            $input["rootdn_passwd"] = Toolbox::encrypt(stripslashes($input["rootdn_passwd"]),
                                                       GLPIKEY);
                                  Toolbox::decrypt($config_ldap->fields['rootdn_passwd'], GLPIKEY),
                                    Toolbox::decrypt($this->fields['rootdn_passwd'], GLPIKEY),
                                  Toolbox::decrypt($ldap_method['rootdn_passwd'], GLPIKEY),
                                        Toolbox::decrypt($ldap_method['rootdn_passwd'], GLPIKEY),
                                Toolbox::decrypt($authldap->getField('rootdn_passwd'), GLPIKEY),
         $input["rootdn_passwd"] = Toolbox::encrypt(stripslashes($input["rootdn_passwd"]), GLPIKEY);
            $input["smtp_passwd"] = Toolbox::encrypt(stripslashes($input["smtp_passwd"]), GLPIKEY);
            $input["proxy_passwd"] = Toolbox::encrypt(stripslashes($input["proxy_passwd"]),
                                                      GLPIKEY);
      $seed         = $dasboard.$entities_id.$is_recursive.GLPIKEY;
}
            $this->Password = Toolbox::decrypt($CFG_GLPI['smtp_passwd'], GLPIKEY);
            $input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]), GLPIKEY);
         'password'  => Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
            Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
               Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
            ? $CFG_GLPI["proxy_user"].":".Toolbox::decrypt($CFG_GLPI["proxy_passwd"], GLPIKEY)."@"
                                             Toolbox::decrypt($CFG_GLPI["proxy_passwd"],
                                                              GLPIKEY);
   static function encrypt($string, $key) {
   static function decrypt($string, $key) {
               CURLOPT_PROXYUSERPWD => $CFG_GLPI["proxy_user"] . ":" . self::decrypt($CFG_GLPI["proxy_passwd"], GLPIKEY),
         die('Upgrade from command line is not supported before 0.72.3!');
                         SET `proxy_passwd` = '".addslashes(Toolbox::encrypt($value, GLPIKEY))."'
                         SET `smtp_passwd` = '".addslashes(Toolbox::encrypt($value, GLPIKEY))."'
                            SET `rootdn_passwd` = '".addslashes(Toolbox::encrypt($data['rootdn_password'],
                                                                                 GLPIKEY))."'
                            SET `passwd` = '".addslashes(Toolbox::encrypt($data['password'],
                                                                          GLPIKEY))."'
                                            Toolbox::decrypt($method["rootdn_passwd"],
                                            GLPIKEY))) {
      //rootdn_passwd is set with a value (a password, not encrypted)
      $password = 'toto';
      $input    = ['name' => 'ldap', 'rootdn_passwd' => $password];
      $result   = $ldap->prepareInputForUpdate($input);

      //Expected value to be encrypted using GLPIKEY key
      $expected = \Toolbox::encrypt(stripslashes($password), GLPIKEY);
      $this->string($result['rootdn_passwd'])->isIdenticalTo($expected);

      $password = 'tot\'o';
      $input    = ['name' => 'ldap', 'rootdn_passwd' => $password];
      $result   = $ldap->prepareInputForUpdate($input);

      //Expected value to be encrypted using GLPIKEY key
      $expected = \Toolbox::encrypt(stripslashes($password), GLPIKEY);
      $this->string($result['rootdn_passwd'])->isIdenticalTo($expected);

      $input['_blank_passwd'] = 1;
      $result   = $ldap->prepareInputForUpdate($input);
      //rootdn_passwd is set but empty
      $this->string($result['rootdn_passwd'])->isEmpty();

            'passwd'    => \Toolbox::encrypt($oinput["passwd"], GLPIKEY),
}
   /**
    * @dataProvider encryptProvider
    */
   public function testEncrypt($string, $key, $expected) {
      $this->string(\Toolbox::encrypt($string, $key))->isIdenticalTo($expected);
    * @dataProvider encryptProvider
   public function testDecrypt($expected, $key, $string) {
      $this->string(\Toolbox::decrypt($string, $key))->isIdenticalTo($expected);
Fixed code: /config/glpi.key
/config/glpicrypt.key
- Encryption alogithm improved using libsodium
- Usage of `GLPIKEY` constant
- `Toolbox::encrypt()` and `Toolbox::decrypt()` because they use the old encryption aglogithm
               $input["passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["passwd"]));
        "paragonie/sodium_compat": "^1.13",
        "paragonie/random_compat": "*",
        "ext-ldap": "Used to provide LDAP authentication and synchronization",
        "ext-sodium": "Used to provide strong encryption for sensitive data in database"
    "content-hash": "889748826910604c38cf291aab5b831e",
            "funding": [
                {
                    "url": "https://github.com/blueimp",
                    "type": "github"
                }
            ],
            "funding": [
                {
                    "url": "https://funding.communitybridge.org/projects/laminas-project",
                    "type": "community_bridge"
                }
            ],
            "funding": [
                {
                    "url": "https://funding.communitybridge.org/projects/laminas-project",
                    "type": "community_bridge"
                }
            ],
        {
            "name": "paragonie/sodium_compat",
            "version": "v1.13.0",
            "source": {
                "type": "git",
                "url": "https://github.com/paragonie/sodium_compat.git",
                "reference": "bbade402cbe84c69b718120911506a3aa2bae653"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bbade402cbe84c69b718120911506a3aa2bae653",
                "reference": "bbade402cbe84c69b718120911506a3aa2bae653",
                "shasum": ""
            },
            "require": {
                "paragonie/random_compat": ">=1",
                "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
            },
            "require-dev": {
                "phpunit/phpunit": "^3|^4|^5|^6|^7"
            },
            "suggest": {
                "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
                "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
            },
            "type": "library",
            "autoload": {
                "files": [
                    "autoload.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "ISC"
            ],
            "authors": [
                {
                    "name": "Paragon Initiative Enterprises",
                    "email": "security@paragonie.com"
                },
                {
                    "name": "Frank Denis",
                    "email": "jedisct1@pureftpd.org"
                }
            ],
            "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
            "keywords": [
                "Authentication",
                "BLAKE2b",
                "ChaCha20",
                "ChaCha20-Poly1305",
                "Chapoly",
                "Curve25519",
                "Ed25519",
                "EdDSA",
                "Edwards-curve Digital Signature Algorithm",
                "Elliptic Curve Diffie-Hellman",
                "Poly1305",
                "Pure-PHP cryptography",
                "RFC 7748",
                "RFC 8032",
                "Salpoly",
                "Salsa20",
                "X25519",
                "XChaCha20-Poly1305",
                "XSalsa20-Poly1305",
                "Xchacha20",
                "Xsalsa20",
                "aead",
                "cryptography",
                "ecdh",
                "elliptic curve",
                "elliptic curve cryptography",
                "encryption",
                "libsodium",
                "php",
                "public-key cryptography",
                "secret-key cryptography",
                "side-channel resistant"
            ],
            "time": "2020-03-20T21:48:09+00:00"
        },
            "funding": [
                {
                    "url": "https://marcus.bointon.com/donations/",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/Synchro",
                    "type": "github"
                },
                {
                    "url": "https://www.patreon.com/marcusbointon",
                    "type": "patreon"
                }
            ],
            "funding": [
                {
                    "url": "https://github.com/ramsey",
                    "type": "github"
                }
            ],
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
    },
    "plugin-api-version": "1.1.0"
                                                  Toolbox::sodiumDecrypt($ldap_method["rootdn_passwd"]),
}
            $input["rootdn_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["rootdn_passwd"]));
                                  Toolbox::sodiumDecrypt($config_ldap->fields['rootdn_passwd']),
                                    Toolbox::sodiumDecrypt($this->fields['rootdn_passwd']),
                                  Toolbox::sodiumDecrypt($ldap_method['rootdn_passwd']),
                                        Toolbox::sodiumDecrypt($ldap_method['rootdn_passwd']),
                                Toolbox::sodiumDecrypt($authldap->getField('rootdn_passwd')),
         $input["rootdn_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["rootdn_passwd"]));
            $input["smtp_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["smtp_passwd"]));
            $input["proxy_passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["proxy_passwd"]));
               [ 'name'    => 'paragonie/sodium_compat',
                 'check'   => 'ParagonIE_Sodium_Compat' ],
            ],
            'sodium' => [
               'required' => false
use GLPIKey;
   /**
    * Error code returned when failing to create encryption key file.
    *
    * @var integer
    */
   const ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE = 9;

      // Create security key
      $glpikey = new GLPIKey();
      if (!$glpikey->keyExists() && !$glpikey->generate()) {
         $message = __('Security key cannot be generated!');
         $output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
         return self::ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE;
      }

<?php
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2015-2018 Teclib' and contributors.
 *
 * http://glpi-project.org
 *
 * based on GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2003-2014 by the INDEPNET Development Team.
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * GLPI is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GLPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GLPI. If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
 */

namespace Glpi\Console\Security;

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}

use Glpi\Console\AbstractCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use GLPIKey;

class ChangekeyCommand extends AbstractCommand {
   /**
    * Error code returned when unable to renew key.
    *
    * @var integer
    */
   const ERROR_UNABLE_TO_RENEW_KEY = 1;

   protected function configure() {
      parent::configure();

      $this->setName('glpi:security:change_key');
      $this->setDescription(__('Change password storage key and update values in database.'));
   }

   protected function execute(InputInterface $input, OutputInterface $output) {
      $glpikey = new GLPIKey();

      $fields = $glpikey->getFields();
      $configs = $glpikey->getConfigs();
      $conf_count = 0;
      foreach ($configs as $config) {
         $conf_count += count($config);
      }

      $output->writeln(
         sprintf(
            '<info>' . __('Found %1$s field(s) and %2$s configuration entries requiring migration.') . '</info>',
            count($fields),
            $conf_count
         )
      );

      if (!$input->getOption('no-interaction')) {
         // Ask for confirmation (unless --no-interaction)
         $question_helper = $this->getHelper('question');
         $run = $question_helper->ask(
            $input,
            $output,
            new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
         );
         if (!$run) {
            $output->writeln(
               '<comment>' . __('Aborted.') . '</comment>',
               OutputInterface::VERBOSITY_VERBOSE
            );
            return 0;
         }
      }

      $created = $glpikey->generate();
      if (!$created) {
         $output->writeln(
            '<error>' . __('Unable to change security key!') . '</error>',
            OutputInterface::VERBOSITY_QUIET
         );
         return self::ERROR_UNABLE_TO_RENEW_KEY;
      }

      $this->output->write(PHP_EOL);

      $output->writeln('<info>' . __('New security key generated; database updated.') . '</info>');

      return 0; // Success
   }
}
      $seed         = $dasboard.$entities_id.$is_recursive.Telemetry::getInstanceUuid();
}
<?php
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2015-2018 Teclib' and contributors.
 *
 * http://glpi-project.org
 *
 * based on GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2003-2014 by the INDEPNET Development Team.
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * GLPI is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GLPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GLPI. If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
 */

if (!defined('GLPI_ROOT')) {
   die("Sorry. You can't access this file directly");
}

/**
 *  GLPI security key
**/
class GLPIKey {
   /**
    * Key file path.
    *
    * @var string
    */
   private $keyfile;

   /**
    * Legacy key file path.
    *
    * @var string
    */
   private $legacykeyfile;

   /**
    * List of crypted DB fields.
    *
    * @var array
    */
   protected $fields = [
      'glpi_mailcollectors.passwd',
      'glpi_authldaps.rootdn_passwd'
   ];

   /**
    * List of crypted configuration values.
    * Each key corresponds to a configuration context, and contains list of configs names.
    *
    * @var array
    */
   protected $configs = [
      'core'   => [
         'smtp_passwd',
         'proxy_passwd'
      ]
   ];

   public function __construct() {
      $this->keyfile = GLPI_CONFIG_DIR . '/glpicrypt.key';
      $this->legacykeyfile = GLPI_CONFIG_DIR . '/glpi.key';
   }

   /**
    * Check if GLPI security key used for decryptable passwords exists
    *
    * @return string
    */
   public function keyExists() {
      return file_exists($this->keyfile) && !empty($this->get());
   }

   /**
    * Get GLPI security key used for decryptable passwords
    *
    * @throw \RuntimeException if key file is missing
    *
    * @return string
    */
   public function get() {
      if (!file_exists($this->keyfile)) {
         throw new \RuntimeException('You must create a security key, see glpi:security:change_key command.');
      }
      //load key from existing config file
      $key = file_get_contents($this->keyfile);
      return $key;
   }

   /**
    * Get GLPI security legacy key that was used for decryptable passwords
    *
    * @return string
    *
    * @deprecated 9.5.0
    */
   public function getLegacyKey() {
      Toolbox::deprecated();

      if (!file_exists($this->legacykeyfile)) {
         return GLPIKEY;
      }
      //load key from existing config file
      $key = file_get_contents($this->legacykeyfile);
      return $key;
   }

   /**
    * Generate GLPI security key used for decryptable passwords
    * and update values in DB if necessary.
    * @return boolean
    */
   public function generate() {
      global $DB;

      $sodium_key = null;
      $old_key = false;

      try {
         $sodium_key = $this->get();
      } catch (\RuntimeException $e) {
         $sodium_key = null;
         $old_key = @$this->getLegacyKey();
      }

      $key = sodium_crypto_aead_chacha20poly1305_ietf_keygen();
      $success = (bool)file_put_contents($this->keyfile, $key);
      if (!$success) {
         return false;
      }

      if ($DB instanceof DBmysql) {
         return $this->migrateFieldsInDb($sodium_key, $old_key)
            && $this->migrateConfigsInDb($sodium_key, $old_key);
      }

      return true;
   }

   /**
    * Get fields
    *
    * @return array
    */
   public function getFields() :array {
      global $PLUGIN_HOOKS;

      $fields = $this->fields;
      if (isset($PLUGIN_HOOKS['secured_fields'])) {
         foreach ($PLUGIN_HOOKS['secured_fields'] as $plugfields) {
            $fields = array_merge($fields, $plugfields);
         }
      }

      return $fields;
   }

   /**
    * Get configs
    *
    * @return array
    */
   public function getConfigs() :array {
      global $PLUGIN_HOOKS;

      $configs = $this->configs;

      if (isset($PLUGIN_HOOKS['secured_configs'])) {
         foreach ($PLUGIN_HOOKS['secured_configs'] as $plugin => $plugconfigs) {
            $configs['plugin:' . $plugin] = $plugconfigs;
         }
      }

      return $configs;
   }

   /**
    * Migrate fields in database
    *
    * @param string       $sodium_key Current key
    * @param string|false $old_key     Old key, if any
    *
    * @return void
    */
   protected function migrateFieldsInDb($sodium_key, $old_key = false) {
      global $DB;

      $success = true;

      foreach ($this->getFields() as $field) {
         list($table, $column) = explode('.', $field);

         $iterator = $DB->request([
            'SELECT' => ['id', $column],
            'FROM'   => $table
         ]);

         while ($success && $row = $iterator->next()) {
            if ($old_key === false) {
               $pass = Toolbox::sodiumEncrypt(Toolbox::sodiumDecrypt($row[$column], $sodium_key));
            } else {
               $pass = Toolbox::sodiumEncrypt(Toolbox::decrypt($row[$column], $old_key));
            }
            $success = $DB->update(
               $table,
               [$field  => $pass],
               ['id'    => $row['id']]
            );
         }
      }

      return $success;
   }

   /**
    * Migrate configurations in database
    *
    * @param string       $sodium_key Current key
    * @param string|false $old_key    Old key, if any
    *
    * @return boolean
    */
   protected function migrateConfigsInDb($sodium_key, $old_key = false) {
      global $DB;

      $success = true;

      foreach ($this->getConfigs() as $context => $names) {
         $iterator = $DB->request([
            'FROM'   => Config::getTable(),
            'WHERE'  => [
               'context'   => $context,
               'name'      => $names
            ]
         ]);

         while ($success && $row = $iterator->next()) {
            if ($old_key === false) {
               $pass = Toolbox::sodiumEncrypt(Toolbox::sodiumDecrypt($row['value'], $sodium_key));
            } else {
               $pass = Toolbox::sodiumEncrypt(Toolbox::decrypt($row['value'], $old_key));
            }
            $success = $DB->update(
               Config::getTable(),
               ['value' => $pass],
               ['id'    => $row['id']]
            );
         }
      }

      return $success;
   }
}
            $this->Password = Toolbox::sodiumDecrypt($CFG_GLPI['smtp_passwd']);
            $input["passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["passwd"]));
         'password'  => Toolbox::sodiumDecrypt($this->fields['passwd']),
            Toolbox::sodiumDecrypt($this->fields['passwd']),
               Toolbox::sodiumDecrypt($this->fields['passwd']),
            ? $CFG_GLPI["proxy_user"].":".Toolbox::sodiumDecrypt($CFG_GLPI["proxy_passwd"])."@"
                                             Toolbox::sodiumDecrypt($CFG_GLPI["proxy_passwd"]);
      $requirements[] = new Extension('sodium', true);
   static function encrypt($string, $key = null) {
      self::deprecated('Use sodiumEncrypt');

      if ($key === null) {
         $glpikey = new GLPIKey();
         $key = $glpikey->getLegacyKey();
      }
   public static function sodiumEncrypt($content, $key = null) {
      if ($key === null) {
         $key = self::getGlpiSecKey();
      }

      $nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES); // NONCE = Number to be used ONCE, for each message
      $encrypted = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
         $content,
         $nonce,
         $nonce,
         $key
      );
      return base64_encode($nonce . $encrypted);
   }

   public static function sodiumDecrypt($content, $key = null) {
      if ($key === null) {
         $key = self::getGlpiSecKey();
      }

      $content = base64_decode($content);
      $nonce = mb_substr($content, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES, '8bit');
      $ciphertext = mb_substr($content, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES, null, '8bit');
      $plaintext = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
         $ciphertext,
         $nonce,
         $nonce,
         $key
      );
      if (!is_string($plaintext)) {
         throw new \RuntimeException('Unable to decrypt content');
      }
      return $plaintext;
   }
   static function decrypt($string, $key = null) {
      self::deprecated('Use sodiumDecrypt');

      if ($key === null) {
         $glpikey = new GLPIKey();
         $key = $glpikey->getLegacyKey();
      }
   /**
    * Get GLPI security key used for decryptable passwords from file
    *
    * @throw \RuntimeException if key file is missing
    *
    * @return string
    */
   public static function getGlpiSecKey() {
      $glpikey = new GLPIKey();
      return $glpikey->get();
   }

               CURLOPT_PROXYUSERPWD => $CFG_GLPI["proxy_user"] . ":" . self::sodiumDecrypt($CFG_GLPI["proxy_passwd"]),
         echo 'Upgrade from command line is not supported before 0.72.3!';
         die(1);

      //generate security key if missing, and update db
      $glpikey = new GLPIKey();
      if (!$glpikey->keyExists() && !$glpikey->generate()) {
         $this->migration->displayWarning(__('Unable to create security key file!'), true);
      }
   //create security key
   $glpikey = new GLPIKey();
   $secured = $glpikey->keyExists();
   if (!$secured) {
      $secured = $glpikey->generate();
   }

   if (!$secured) {
      echo "<p><strong>".__('Security key cannot be generated!')."</strong></p>";
      prev_form($host, $user, $password);
      return;
   }


                         SET `proxy_passwd` = '".addslashes(Toolbox::encrypt($value))."'
                         SET `smtp_passwd` = '".addslashes(Toolbox::encrypt($value))."'
                            SET `rootdn_passwd` = '".addslashes(Toolbox::encrypt($data['rootdn_password']))."'
                            SET `passwd` = '".addslashes(Toolbox::encrypt($data['password']))."'
   /** Appliances & webapps */
   require __DIR__ . '/update_94_95/appliances.php';
   /** /Appliances & webapps */

   // ************ Keep it at the end **************
   foreach ($ADDTODISPLAYPREF as $type => $tab) {
      $rank = 1;
      foreach ($tab as $newval) {
         $DB->updateOrInsert("glpi_displaypreferences", [
            'rank'      => $rank++
         ], [
            'users_id'  => "0",
            'itemtype'  => $type,
            'num'       => $newval,
         ]);
      }
   }

                                            Toolbox::sodiumDecrypt($method["rootdn_passwd"]))) {

   public function testPassword() {
      $ldap = new \AuthLDAP();
      $id = (int)$ldap->add([
         'name'        => 'LDAPcrypted',
         'is_active'   => 1,
         'is_default'  => 0,
         'basedn'      => 'ou=people,dc=mycompany',
         'login_field' => 'uid',
         'phone_field' => 'phonenumber'
      ]);
      $this->integer($id)->isGreaterThan(0);

      //rootdn_passwd is set with a value (a password, not encrypted)
      $password = 'toto';
      $input    = ['id' => $id, 'name' => 'ldap', 'rootdn_passwd' => $password];
      $this->boolean($ldap->update($input))->isTrue();
      $this->boolean($ldap->getFromDB($id))->isTrue();

      //Expected value to be encrypted using current  key
      $this->string(\Toolbox::sodiumDecrypt($ldap->fields['rootdn_passwd']))->isIdenticalTo($password);

      $password = 'tot\'o';
      $input    = ['id' => $id, 'name' => 'ldap', 'rootdn_passwd' => $password];
      $this->boolean($ldap->update($input))->isTrue();
      $this->boolean($ldap->getFromDB($id))->isTrue();

      //Expected value to be encrypted using current key
      $this->string(\Toolbox::sodiumDecrypt($ldap->fields['rootdn_passwd']))->isIdenticalTo($password);

      $password = 'tot\'o';
      $input    = ['id' => $id, 'name' => 'ldap', 'rootdn_passwd' => \Toolbox::addslashes_deep($password)];
      $this->boolean($ldap->update($input))->isTrue();
      $this->boolean($ldap->getFromDB($id))->isTrue();

      //Expected value to be encrypted using current key
      $this->string(\Toolbox::sodiumDecrypt($ldap->fields['rootdn_passwd']))->isIdenticalTo($password);

      $input['_blank_passwd'] = 1;
      $result   = $ldap->prepareInputForUpdate($input);
      //rootdn_passwd is set but empty
      $this->string($result['rootdn_passwd'])->isEmpty();

   }
            'passwd'    => \Toolbox::sodiumEncrypt($oinput["passwd"]),
}
   protected function sodiumEncryptProvider() {
      return [
         ['My string'],
         ['keepmysecret'],
         ['This is a strng I want to crypt, with some unusual chars like %, \', @, and so on!']
      ];
    * @dataProvider sodiumEncryptProvider
   public function testSodiumEncrypt($string) {
      $crypted = \Toolbox::sodiumEncrypt($string);
      $this->string($crypted)->isNotEmpty();
      $this->string(\Toolbox::sodiumDecrypt($crypted))->isIdenticalTo($string);