password hashing changed to SHA-256 with 8 byte salt
authorVictor Kirhenshtein <victor@netxms.org>
Sat, 16 May 2015 20:09:46 +0000 (23:09 +0300)
committerVictor Kirhenshtein <victor@netxms.org>
Sat, 16 May 2015 20:09:46 +0000 (23:09 +0300)
ChangeLog
include/netxmsdb.h
include/nms_util.h
sql/schema.in
src/libnetxms/crypto.cpp
src/server/core/userdb.cpp
src/server/core/userdb_objects.cpp
src/server/include/nms_users.h
src/server/libnxsrv/messages.mc
src/server/tools/nxdbmgr/upgrade.cpp

index cdcdd62..22579c7 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,11 +9,12 @@
 - New NXSL function sha256
 - Fixed broken nxagent.sms SMS driver
 - Added support for SNMP traps over IPv6
+- Switched to SHA-256 for password hashing
 - Management console:
        - Fixed broken VPN connectors configuration
        - "Inverted values" option on line charts
        - Filter in predefined graphs tree
-- Fixed issues: #816, #817
+- Fixed issues: #797, #816, #817
 
 
 *
index d320e73..3965759 100644 (file)
@@ -23,6 +23,6 @@
 #ifndef _netxmsdb_h
 #define _netxmsdb_h
 
-#define DB_FORMAT_VERSION   353
+#define DB_FORMAT_VERSION   354
 
 #endif
index 3b89c12..9c6082b 100644 (file)
@@ -1411,6 +1411,8 @@ BOOL LIBNETXMS_EXPORTABLE CalculateFileMD5Hash(const TCHAR *pszFileName, BYTE *p
 BOOL LIBNETXMS_EXPORTABLE CalculateFileSHA1Hash(const TCHAR *pszFileName, BYTE *pHash);
 BOOL LIBNETXMS_EXPORTABLE CalculateFileCRC32(const TCHAR *pszFileName, UINT32 *pResult);
 
+void LIBNETXMS_EXPORTABLE GenerateRandomBytes(BYTE *buffer, size_t size);
+
 void LIBNETXMS_EXPORTABLE ICEEncryptData(const BYTE *in, int inLen, BYTE *out, const BYTE *key);
 void LIBNETXMS_EXPORTABLE ICEDecryptData(const BYTE *in, int inLen, BYTE *out, const BYTE *key);
 
index 76f1589..88f97fb 100644 (file)
@@ -57,7 +57,7 @@ CREATE TABLE users
        id integer not null,
        guid varchar(36) not null,
        name varchar(63) not null,
-       password varchar(48) not null,
+       password varchar(127) not null,
        system_access SQL_INT64 not null,
        flags integer not null,
        full_name varchar(127) null,
index f399bbc..d3dd5db 100644 (file)
@@ -804,3 +804,17 @@ bool NXCPEncryptionContext::decryptMessage(NXCP_ENCRYPTED_MESSAGE *msg, BYTE *de
    return false;
 #endif
 }
+
+/**
+ * Generate random bytes
+ */
+void LIBNETXMS_EXPORTABLE GenerateRandomBytes(BYTE *buffer, size_t size)
+{
+#ifdef _WITH_ENCRYPTION
+   RAND_bytes(buffer, (int)size);
+#else
+   srand((unsigned int)time(NULL));
+   for(size_t i = 0; i < size; i++)
+      buffer[i] = (BYTE)(rand() % 256);
+#endif
+}
index 34a35f9..2bee5a0 100644 (file)
@@ -234,7 +234,6 @@ UINT32 AuthenticateUser(const TCHAR *login, const TCHAR *password, UINT32 dwSigL
                        User *user = (User *)m_users[i];
          *pdwId = user->getId(); // always set user ID for caller so audit log will contain correct user ID on failures as well
 
-
          if (user->isLDAPUser())
          {
             if (user->isDisabled() || user->hasSyncException())
index 831e0dd..1691046 100644 (file)
 
 #include "nxcore.h"
 
+/**
+ * Generate hash for password
+ */
+static void CalculatePasswordHash(const TCHAR *password, PasswordHashType type, PasswordHash *ph, const BYTE *salt = NULL)
+{
+#ifdef UNICODE
+       char mbPassword[1024];
+       WideCharToMultiByte(CP_UTF8, 0, password, -1, mbPassword, 1024, NULL, NULL);
+       mbPassword[1023] = 0;
+#else
+   const char *mbPassword = password;
+#endif
+
+   BYTE buffer[1024];
+
+   memset(ph, 0, sizeof(PasswordHash));
+   ph->hashType = type;
+   switch(type)
+   {
+      case PWD_HASH_SHA1:
+       CalculateSHA1Hash((BYTE *)mbPassword, strlen(mbPassword), ph->hash);
+         break;
+      case PWD_HASH_SHA256:
+         if (salt != NULL)
+            memcpy(buffer, salt, PASSWORD_SALT_LENGTH);
+         else
+            GenerateRandomBytes(buffer, PASSWORD_SALT_LENGTH);
+         strcpy((char *)&buffer[PASSWORD_SALT_LENGTH], mbPassword);
+       CalculateSHA256Hash(buffer, strlen(mbPassword) + PASSWORD_SALT_LENGTH, ph->hash);
+         memcpy(ph->salt, buffer, PASSWORD_SALT_LENGTH);
+         break;
+      default:
+         break;
+   }
+}
 
 /*****************************************************************************
  **  UserDatabaseObject
@@ -308,12 +343,35 @@ User::User(DB_RESULT hResult, int row) : UserDatabaseObject(hResult, row)
 {
        TCHAR buffer[256];
 
-       if (StrToBin(DBGetField(hResult, row, 7, buffer, 256), m_passwordHash, SHA1_DIGEST_SIZE) != SHA1_DIGEST_SIZE)
-       {
-               nxlog_write(MSG_INVALID_SHA1_HASH, EVENTLOG_WARNING_TYPE, "s", m_name);
-               CalculateSHA1Hash((BYTE *)"netxms", 6, m_passwordHash);
+   bool validHash = false;
+   DBGetField(hResult, row, 7, buffer, 256);
+   if (buffer[0] == _T('$'))
+   {
+      // new format - with hash type indicator
+      if (buffer[1] == 'A')
+      {
+         m_password.hashType = PWD_HASH_SHA256;
+         if (_tcslen(buffer) >= 82)
+         {
+            if ((StrToBin(&buffer[2], m_password.salt, PASSWORD_SALT_LENGTH) == PASSWORD_SALT_LENGTH) && 
+                (StrToBin(&buffer[18], m_password.hash, SHA256_DIGEST_SIZE) == SHA256_DIGEST_SIZE))
+               validHash = true;
+         }
+      }
+   }
+   else
+   {
+      // old format - SHA1 hash without salt
+      m_password.hashType = PWD_HASH_SHA1;
+      if (StrToBin(buffer, m_password.hash, SHA1_DIGEST_SIZE) == SHA1_DIGEST_SIZE)
+         validHash = true;
+   }
+   if (!validHash)
+   {
+          nxlog_write(MSG_INVALID_PASSWORD_HASH, NXLOG_WARNING, "s", m_name);
+          CalculatePasswordHash(_T("netxms"), PWD_HASH_SHA256, &m_password);
       m_flags |= UF_MODIFIED | UF_CHANGE_PASSWORD;
-       }
+   }
 
        DBGetField(hResult, row, 8, m_fullName, MAX_USER_FULLNAME);
        m_graceLogins = DBGetFieldLong(hResult, row, 9);
@@ -345,7 +403,7 @@ User::User()
        m_systemRights = SYSTEM_ACCESS_FULL;
        m_fullName[0] = 0;
        _tcscpy(m_description, _T("Built-in system administrator account"));
-       CalculateSHA1Hash((BYTE *)"netxms", 6, m_passwordHash);
+       CalculatePasswordHash(_T("netxms"), PWD_HASH_SHA256, &m_password);
        m_graceLogins = MAX_GRACE_LOGINS;
        m_authMethod = AUTH_NETXMS_PASSWORD;
        uuid_generate(m_guid);
@@ -370,7 +428,7 @@ User::User(UINT32 id, const TCHAR *name) : UserDatabaseObject(id, name)
        m_authMethod = AUTH_NETXMS_PASSWORD;
        m_certMappingMethod = USER_MAP_CERT_BY_CN;
        m_certMappingData = NULL;
-       CalculateSHA1Hash((BYTE *)"", 0, m_passwordHash);
+       CalculatePasswordHash(_T(""), PWD_HASH_SHA256, &m_password);
        m_authFailures = 0;
        m_lastPasswordChange = 0;
        m_minPasswordLength = -1;       // Use system-wide default
@@ -392,13 +450,26 @@ User::~User()
  */
 bool User::saveToDatabase(DB_HANDLE hdb)
 {
-       TCHAR password[SHA1_DIGEST_SIZE * 2 + 1], guidText[64];
+       TCHAR password[128], guidText[64];
 
    // Clear modification flag
    m_flags &= ~UF_MODIFIED;
 
    // Create or update record in database
-   BinToStr(m_passwordHash, SHA1_DIGEST_SIZE, password);
+   switch(m_password.hashType)
+   {
+      case PWD_HASH_SHA1:
+         BinToStr(m_password.hash, SHA1_DIGEST_SIZE, password);
+         break;
+      case PWD_HASH_SHA256:
+         _tcscpy(password, _T("$A"));
+         BinToStr(m_password.salt, PASSWORD_SALT_LENGTH, &password[2]);
+         BinToStr(m_password.hash, SHA256_DIGEST_SIZE, &password[18]);
+         break;
+      default:
+         _tcscpy(password, _T("$$"));
+         break;
+   }
    DB_STATEMENT hStmt;
    if (IsDatabaseRecordExist(hdb, _T("users"), _T("id"), m_id))
    {
@@ -487,29 +558,19 @@ bool User::deleteFromDatabase(DB_HANDLE hdb)
 
 /**
  * Validate user's password
- * For non-UNICODE build, password must be UTF-8 encoded
  */
 bool User::validatePassword(const TCHAR *password)
 {
-   BYTE hash[SHA1_DIGEST_SIZE];
-
-#ifdef UNICODE
-       char mbPassword[1024];
-       WideCharToMultiByte(CP_UTF8, 0, password, -1, mbPassword, 1024, NULL, NULL);
-       mbPassword[1023] = 0;
-       CalculateSHA1Hash((BYTE *)mbPassword, strlen(mbPassword), hash);
-#else
-       CalculateSHA1Hash((BYTE *)password, strlen(password), hash);
-#endif
-       return !memcmp(hash, m_passwordHash, SHA1_DIGEST_SIZE);
-}
-
-/**
- * Validate user's password in hashed form
- */
-bool User::validateHashedPassword(const BYTE *password)
-{
-       return !memcmp(password, m_passwordHash, SHA1_DIGEST_SIZE);
+   PasswordHash ph;
+   CalculatePasswordHash(password, m_password.hashType, &ph, m_password.salt);
+   bool success = !memcmp(ph.hash, m_password.hash, PWD_HASH_SIZE(m_password.hashType));
+   if (success && m_password.hashType == PWD_HASH_SHA1)
+   {
+      // regenerate password hash if old format is used
+      CalculatePasswordHash(password, PWD_HASH_SHA256, &m_password);
+          m_flags |= UF_MODIFIED;
+   }
+   return success;
 }
 
 /**
@@ -518,14 +579,7 @@ bool User::validateHashedPassword(const BYTE *password)
  */
 void User::setPassword(const TCHAR *password, bool clearChangePasswdFlag)
 {
-#ifdef UNICODE
-       char mbPassword[1024];
-       WideCharToMultiByte(CP_UTF8, 0, password, -1, mbPassword, 1024, NULL, NULL);
-       mbPassword[1023] = 0;
-       CalculateSHA1Hash((BYTE *)mbPassword, strlen(mbPassword), m_passwordHash);
-#else
-       CalculateSHA1Hash((BYTE *)password, strlen(password), m_passwordHash);
-#endif
+   CalculatePasswordHash(password, PWD_HASH_SHA256, &m_password);
        m_graceLogins = MAX_GRACE_LOGINS;
        m_flags |= UF_MODIFIED;
        if (clearChangePasswdFlag)
index f9885e0..df26dbd 100644 (file)
@@ -203,6 +203,35 @@ public:
        void setDn(const TCHAR *dn);
 };
 
+/**
+ * Hash types
+ */
+enum PasswordHashType
+{
+   PWD_HASH_SHA1 = 0,
+   PWD_HASH_SHA256 = 1
+};
+
+/**
+ * Password salt length
+ */
+#define PASSWORD_SALT_LENGTH  8
+
+/**
+ * Password hash size
+ */
+#define PWD_HASH_SIZE(t) ((t == PWD_HASH_SHA256) ? SHA256_DIGEST_SIZE : ((t == PWD_HASH_SHA1) ? SHA1_DIGEST_SIZE : 0))
+
+/**
+ * Hashed password
+ */
+struct PasswordHash
+{
+   PasswordHashType hashType;
+   BYTE hash[SHA256_DIGEST_SIZE];
+   BYTE salt[PASSWORD_SALT_LENGTH];
+};
+
 /**
  * User object
  */
@@ -210,7 +239,7 @@ class NXCORE_EXPORTABLE User : public UserDatabaseObject
 {
 protected:
        TCHAR m_fullName[MAX_USER_FULLNAME];
-   BYTE m_passwordHash[SHA1_DIGEST_SIZE];
+   PasswordHash m_password;
    int m_graceLogins;
    int m_authMethod;
        int m_certMappingMethod;
@@ -248,7 +277,6 @@ public:
    const TCHAR *getXmppId() { return m_xmppId; }
 
        bool validatePassword(const TCHAR *password);
-       bool validateHashedPassword(const BYTE *password);
        void decreaseGraceLogins() { if (m_graceLogins > 0) m_graceLogins--; m_flags |= UF_MODIFIED; }
        void setPassword(const TCHAR *password, bool clearChangePasswdFlag);
        void increaseAuthFailures();
index a2bc8f0..65af01a 100644 (file)
@@ -267,7 +267,7 @@ SQL query failed (Query = "%1"): %2
 .
 
 MessageId=
-SymbolicName=MSG_INVALID_SHA1_HASH
+SymbolicName=MSG_INVALID_PASSWORD_HASH
 Language=English
 Invalid password hash for user %1: password reset to default
 .
index e8a6ff3..24214ec 100644 (file)
@@ -466,6 +466,16 @@ static BOOL ConvertNetMasks(const TCHAR *table, const TCHAR *column, const TCHAR
    return success;
 }
 
+/**
+ * Upgrade from V353 to V354
+ */
+static BOOL H_UpgradeFromV353(int currVersion, int newVersion)
+{
+   CHK_EXEC(ResizeColumn(_T("users"), _T("password"), 127, false));
+   CHK_EXEC(SQLQuery(_T("UPDATE metadata SET var_value='354' WHERE var_name='SchemaVersion'")));
+   return TRUE;
+}
+
 /**
  * Upgrade from V352 to V353
  */
@@ -8575,6 +8585,7 @@ static struct
    { 350, 351, H_UpgradeFromV350 },
    { 351, 352, H_UpgradeFromV351 },
    { 352, 353, H_UpgradeFromV352 },
+   { 353, 354, H_UpgradeFromV353 },
    { 0, 0, NULL }
 };