Unencrypted ldap.adminpassword property in postgres database

Hi everyone,

I’d appreciate some help regarding OpenLdap bind password encryption.
I’m working on a Kubernetes pod-based, touchless deployment of Openfire (using version 4.7.5). I configure OpenLdap via the openfire.xml file, but for some reason, the ldap.adminpassword ends up stored in the database in plain text.

Here’s an overview of what happens during the Openfire deployment process—after the PostgreSQL database pod is up, but before Openfire’s first startup:

  1. The original openfire.xml and security.xml files are deleted, and my custom-configured versions (provided via a ConfigMap) are copied into /etc/openfire.
  2. The database schema is checked and loaded if needed.
  3. Openfire is started using the startup.jar file.

The OpenLDAP bind password is specified via the ldap.adminPassword property, but for some reason it’s not being encrypted when saved to the database.

My openfire.xml includes the following:

<jive>
  <ldap>
    <adminPassword>password</adminPassword>
  </ldap>
</jive>

And in security.xml, I’ve also listed this property for encryption:

<security>
  <encrypt>
    <property>
      <name>database.defaultProvider.username</name>
      <name>database.defaultProvider.password</name>
      <name>ldap.adminpassword</name>
    </property>
  </encrypt>
</security>

Could you please help me figure out how to ensure the ldap.adminpassword gets stored in the database in encrypted form?

Thanks in advance for any help

Hi Miklos!

Going through an entire Kubernetes setup is a bit more effort than what I have available right now, but I do think that I can be of some help.

  1. In your security.xml example, it is important to use the exact casing of the property name.

    You have:

    <name>ldap.adminpassword</name>
    

    This needs to be:

    <name>ldap.adminPassword</name>
    

    Note that difference in the letter P.

  2. When testing this, I found a problem in Openfire (I’m using the latest development version, which is currently a snapshot that will become release 5.1.0). Openfire fails to startup successfully, with the configuration that you’re trying to have.

    I have documented this problem as OF-3175.

I am working on a fix for this problem, which I think is ready to be merged. With this fix, when I use this content for openfire.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jive>
  <adminConsole>
      <port>9090</port>
      <securePort>9091</securePort>
  </adminConsole>
  <connectionProvider>
    <className>org.jivesoftware.database.EmbeddedConnectionProvider</className>
  </connectionProvider>
  <ldap>
      <host>127.0.0.1</host>
      <port>10389</port>
      <baseDN>dc=planetexpress,dc=com</baseDN>
      <adminDN>cn=admin,dc=planetexpress,dc=com</adminDN>
      <adminPassword>GoodNewsEveryone</adminPassword>
      <sslEnabled>false</sslEnabled>
  </ldap>
  <autosetup>
      <run>true</run>
      <locale>en</locale>
      <xmpp>
          <auth>
              <anonymous>true</anonymous>
          </auth>
          <domain>example.org</domain>
          <fqdn>example.org</fqdn>
      </xmpp>
      <database>
          <mode>embedded</mode>
      </database>
      <admin>
          <email>admin@example.com</email>
          <password>admin</password>
      </admin>
  </autosetup>
</jive>

and this content for security.xml:

<?xml version="1.0" encoding="UTF-8"?>
<security>
  <encrypt>
    <algorithm>Blowfish</algorithm>
    <key>
       <current />
    </key>
    <property>
      <name>database.defaultProvider.username</name>
      <name>database.defaultProvider.password</name>
      <name>ldap.adminPassword</name>
    </property>
  </encrypt>  

Then, after startup of Openfire, the openfire.xml file gets modified like this:

<?xml version="1.0" encoding="UTF-8"?>

<jive> 
  <adminConsole> 
    <port>9090</port>  
    <securePort>9091</securePort> 
  </adminConsole>  
  <connectionProvider> 
    <className>org.jivesoftware.database.EmbeddedConnectionProvider</className> 
  </connectionProvider>  
  <ldap> 
    <host>127.0.0.1</host>  
    <port>10389</port>  
    <baseDN>dc=planetexpress,dc=com</baseDN>  
    <adminDN>cn=admin,dc=planetexpress,dc=com</adminDN>  
    <adminPassword encrypted="true" iv="gejFrZByxi1LdgROGMgwQA==">a3604dcda1d170a6db7d317168c960c9c84f48e520eb1f7da3ce1793e7e1149cb6a6167d48d24662cc703917968c6>
    <sslEnabled>false</sslEnabled> 
  </ldap>  
  <setup>true</setup> 
  <locale>en</locale> 
  <fqdn>example.org</fqdn>
</jive>

and the database gets modified like this (the password is added as a plaintext value, then removed, then re-added as an encrypted value):

COMMIT
INSERT INTO OFPROPERTY VALUES('ldap.host','127.0.0.1',0,NULL)
COMMIT
INSERT INTO OFPROPERTY VALUES('ldap.port','10389',0,NULL)
COMMIT
INSERT INTO OFPROPERTY VALUES('ldap.baseDN','dc=planetexpress,dc=com',0,NULL)
COMMIT
INSERT INTO OFPROPERTY VALUES('ldap.adminDN','cn=admin,dc=planetexpress,dc=com',0,NULL)
COMMIT
INSERT INTO OFPROPERTY VALUES('ldap.adminPassword','GoodNewsEveryone',0,NULL)
COMMIT
DELETE FROM OFPROPERTY WHERE NAME='ldap.adminPassword'
INSERT INTO OFPROPERTY VALUES('ldap.adminPassword','f5a477a06c079be474f57626b1064b3872d6815ef9fb5924ae0d8b4b12a63f46bc077a4edc15327a3a01f882bcc80c2e',1,'+XDIoI+ClAIFNFBESIexqA==')
COMMIT
INSERT INTO OFPROPERTY VALUES('ldap.sslEnabled','false',0,NULL)
COMMIT

I believe that this shows that your problem is addressed.

Hello Guus,
Thank you very much for the quick response.
I will test what you suggested in a few days and report back on the results.

Hello Guus,
I tested the configuration you recommended and came to the following:If I use embedded database, the encryption works fine, but if I use a Postgres database, the ldap admin password is still saved as plain text at the end of the deployment. The only change I made to the openfire.xml configuration was that I had to remove the database part from the autosetup part because the deployment could not find the database provider only if the database part was used outside of the autosetup.

<?xml version="1.0" encoding="UTF-8"?>
<jive>
  <adminConsole>
	<port>{{ .Values.openfire.adminConsole.httpPort }}</port>
	<securePort>{{ .Values.openfire.adminConsole.httpsPort }}</securePort>
  </adminConsole>
  <connectionProvider>
	<className>org.jivesoftware.database.DefaultConnectionProvider</className>
  </connectionProvider>

  <database>
	<defaultProvider>
	  <driver>org.postgresql.Driver</driver>
	  <serverURL>jdbc:postgresql://{{ include "openfire.fullname" . }}-postgres:5432/openfire</serverURL>
	  <username>openfire</username>
	  <password>${DB_PASSWORD}</password>
	  <testSQL>select 1</testSQL>
	  <testBeforeUse>false</testBeforeUse>
	  <testAfterUse>false</testAfterUse>
	  <minConnections>5</minConnections>
	  <maxConnections>25</maxConnections>
	  <connectionTimeout>1.0</connectionTimeout>
	</defaultProvider>
  </database>

  <autosetup>
	  <run>true</run>
	  <locale>en</locale>
	  <xmpp>
		  <auth>
			  <anonymous>true</anonymous>
		  </auth>
		  <domain>testopenfire.services</domain>
		  <fqdn>chat.testopenfire.services</fqdn>
	  </xmpp>
	  <admin>
		  <email>admin@example.com</email>
		  <password>admin</password>
	  </admin>
  </autosetup>

  <provider>
	<user>
	  <className>org.jivesoftware.openfire.ldap.LdapUserProvider</className>
	</user>
	<group>
	  <className>org.jivesoftware.openfire.ldap.LdapGroupProvider</className>
	  <groupBasedAdminProvider>
		<groupName>{{ .Values.ldap.adminGroup }}</groupName>
	  </groupBasedAdminProvider>
	</group>
	<auth>
	  <className>org.jivesoftware.openfire.ldap.LdapAuthProvider</className>
	</auth>
	<admin>
	  <className>org.jivesoftware.openfire.admin.GroupBasedAdminProvider</className>
	</admin>
  </provider>
  <ldap>
	<host>{{ .Values.ldap.host }}</host>
	<port>{{ .Values.ldap.port }}</port>
	<usernameField>cn</usernameField>
	<baseDN>{{ .Values.ldap.baseDN }}</baseDN>
	<nameField>cn</nameField>
	<searchFilter>(&amp;(objectClass=inetOrgPerson)(cn={0}))</searchFilter>
	<groupNameField>cn</groupNameField>
	<adminPassword>${LDAP_PASSWORD}</adminPassword>
	<groupMemberField>member</groupMemberField>
	<groupDescriptionField>description</groupDescriptionField>
	<posixMode>false</posixMode>
	<groupSearchFilter>(objectClass=groupOfNames)</groupSearchFilter>
	<adminDN>{{ .Values.ldap.adminDN }}</adminDN>
	<connectionProtocol>{{ .Values.ldap.connectionProtocol }}</connectionProtocol>
	<connectionPoolEnabled>{{ .Values.ldap.connectionPoolEnabled }}</connectionPoolEnabled>
	<sslEnabled>{{ .Values.ldap.sslEnabled }}</sslEnabled>
	<startTlsEnabled>{{ .Values.ldap.startTlsEnabled }}</startTlsEnabled>
  </ldap>
</jive>

and the security.xml

<?xml version="1.0" encoding="UTF-8"?>
<security>
  <encrypt>
	<algorithm>Blowfish</algorithm>
	<key>
	  <current></current>
	</key>
	<property>
	  <name>database.defaultProvider.username</name>
	  <name>database.defaultProvider.password</name>
	  <name>ldap.adminPassword</name>
	</property>
  </encrypt>
</security>

Plain text ldap admin password is: 0GnNNNMJ6pQTxsIW
The following Postgres log shows that Openfire encrypts the ldap.adminPassword, but for some reason it updates it again with the plain text password after encryption, so it ends up remaining in the database in plain text format.

2026-02-16 19:20:51.736 UTC [110] DETAIL:  parameters: $1 = 'ldap.adminPassword', $2 = '0GnNNNMJ6pQTxsIW', $3 = '0', $4 = NULL
2026-02-16 19:20:51.736 UTC [110] LOG:  execute S_1: INSERT INTO ofProperty(name, propValue, encrypted, iv) VALUES($1,$2,$3,$4)
2026-02-16 19:20:51.736 UTC [110] DETAIL:  parameters: $1 = 'ldap.adminPassword', $2 = '0GnNNNMJ6pQTxsIW', $3 = '0', $4 = NULL
2026-02-16 19:20:51.736 UTC [110] LOG:  duration: 0.092 ms
2026-02-16 19:20:51.742 UTC [110] LOG:  duration: 3.043 ms  parse <unnamed>: UPDATE ofProperty SET propValue=$1, encrypted=$2, iv=$3 WHERE name=$4
2026-02-16 19:20:51.742 UTC [110] LOG:  duration: 0.114 ms  bind <unnamed>: UPDATE ofProperty SET propValue=$1, encrypted=$2, iv=$3 WHERE name=$4
2026-02-16 19:20:51.742 UTC [110] DETAIL:  parameters: $1 = 'f9639af92b2f895acc4e4ec8dad33cf455b10d3ee9eaeeddfd0cca8acf862c4159f1c73933530d716d6e409a5ad8184b', $2 = '1', $3 = 'gno1Vz5i3TGB3UrUxcAdkA==', $4 = 'ldap.adminPassword'
2026-02-16 19:20:51.742 UTC [110] LOG:  execute <unnamed>: UPDATE ofProperty SET propValue=$1, encrypted=$2, iv=$3 WHERE name=$4
2026-02-16 19:20:51.742 UTC [110] DETAIL:  parameters: $1 = 'f9639af92b2f895acc4e4ec8dad33cf455b10d3ee9eaeeddfd0cca8acf862c4159f1c73933530d716d6e409a5ad8184b', $2 = '1', $3 = 'gno1Vz5i3TGB3UrUxcAdkA==', $4 = 'ldap.adminPassword'
2026-02-16 19:20:51.745 UTC [110] LOG:  duration: 2.195 ms
2026-02-16 19:20:51.764 UTC [110] LOG:  duration: 0.100 ms  parse <unnamed>: UPDATE ofProperty SET propValue=$1, encrypted=$2, iv=$3 WHERE name=$4
2026-02-16 19:20:51.764 UTC [110] LOG:  duration: 0.105 ms  bind <unnamed>: UPDATE ofProperty SET propValue=$1, encrypted=$2, iv=$3 WHERE name=$4
2026-02-16 19:20:51.764 UTC [110] DETAIL:  parameters: $1 = '0GnNNNMJ6pQTxsIW', $2 = '0', $3 = NULL, $4 = 'ldap.adminPassword'
2026-02-16 19:20:51.764 UTC [110] LOG:  execute <unnamed>: UPDATE ofProperty SET propValue=$1, encrypted=$2, iv=$3 WHERE name=$4
2026-02-16 19:20:51.764 UTC [110] DETAIL:  parameters: $1 = '0GnNNNMJ6pQTxsIW', $2 = '0', $3 = NULL, $4 = 'ldap.adminPassword'
2026-02-16 19:20:51.764 UTC [110] LOG:  duration: 0.082 ms

My code does not contain any sql commands, Openfire is only configured via xml files.

Based on what I wrote, do you have any ideas on where I should go next?
Thank you in advance for your help.

Miklos

I have tried reproducing this with Mysql (which is what I happened to have available), but I could not. The value in the database was encrypted, as expected. I’m not quite sure yet what differs from my setup compared to yours.

I used this security.xml and this openfire.xml content:

<?xml version="1.0" encoding="UTF-8"?>
<security>
  <encrypt>
    <algorithm>Blowfish</algorithm>
    <key>
       <current />
    </key>
    <property>
      <name>database.defaultProvider.username</name>
      <name>database.defaultProvider.password</name>
      <name>ldap.adminPassword</name>
    </property>
  </encrypt>
</security>
<?xml version="1.0" encoding="UTF-8"?>
<jive>
  <adminConsole>
      <port>9090</port>
      <securePort>9091</securePort>
  </adminConsole>
  <connectionProvider>
    <className>org.jivesoftware.database.DefaultConnectionProvider</className>
  </connectionProvider>
  <database>
    <defaultProvider>
      <driver>com.mysql.cj.jdbc.Driver</driver> 
      <serverURL>jdbc:mysql://example.org:3306/openfire?rewriteBatchedStatements=true&amp;characterEncoding=UTF-8&amp;characterSetResults=UTF-8&amp;serverTimezone=UTC</serverURL>
      <username>openfire</username> 
      <password>openfire</password> 
      <testSQL>select 1</testSQL> 
      <testBeforeUse>false</testBeforeUse> 
      <testAfterUse>false</testAfterUse> 
      <testTimeout>PT0.5S</testTimeout> 
      <timeBetweenEvictionRuns>PT30S</timeBetweenEvictionRuns> 
      <minIdleTime>PT15M</minIdleTime> 
      <maxWaitTime>PT0.5S</maxWaitTime> 
      <minConnections>5</minConnections> 
      <maxConnections>25</maxConnections> 
      <connectionTimeout>1.0</connectionTimeout>
    </defaultProvider>
  </database>
  <ldap>
      <host>127.0.0.1</host>
      <port>10389</port>
      <baseDN>dc=planetexpress,dc=com</baseDN>
      <adminDN>cn=admin,dc=planetexpress,dc=com</adminDN>
      <adminPassword>GoodNewsEveryone</adminPassword>
      <sslEnabled>false</sslEnabled>
  </ldap>
  <autosetup>
      <run>true</run>
      <locale>en</locale>
      <xmpp>
          <auth>
              <anonymous>true</anonymous>
          </auth>
          <domain>example.org</domain>
          <fqdn>example.org</fqdn>
      </xmpp>
      <database>
          <mode>embedded</mode>
      </database>
      <admin>
          <email>admin@example.com</email>
          <password>admin</password>
      </admin>
  </autosetup>
</jive>

After starting openfire, the openfire.xml file was modified toreplace all passwords with encryption, as expected:

<?xml version="1.0" encoding="UTF-8"?>

<jive> 
  <adminConsole> 
    <port>9090</port>  
    <securePort>9091</securePort> 
  </adminConsole>  
  <connectionProvider> 
    <className>org.jivesoftware.database.DefaultConnectionProvider</className> 
  </connectionProvider>  
  <database> 
    <defaultProvider> 
      <driver>com.mysql.cj.jdbc.Driver</driver>  
      <serverURL>jdbc:mysql://example.org:3306/openfire?rewriteBatchedStatements=true&amp;characterEncoding=UTF-8&amp;characterSetResults=UTF-8&amp;serverTimezone=UTC</s>
      <username encrypted="true" iv="AHXxJvNG38lsY5iIkpf88g==">f1c32a7f420068e7b84d6d552e92652e011118d24484dec4fc7a98c7316697ef</username>  
      <password encrypted="true" iv="8gzO8d6rTpztid4FxKuHBw==">0bc766e234c2cbf6b7aca8a9c2ac736ae9cc1994b376f8c66a486fa62620711d</password>  
      <testSQL>select 1</testSQL>  
      <testBeforeUse>false</testBeforeUse>  
      <testAfterUse>false</testAfterUse>  
      <testTimeout>PT0.5S</testTimeout>  
      <timeBetweenEvictionRuns>PT30S</timeBetweenEvictionRuns>  
      <minIdleTime>PT15M</minIdleTime>  
      <maxWaitTime>PT0.5S</maxWaitTime>  
      <minConnections>5</minConnections>  
      <maxConnections>25</maxConnections>  
      <connectionTimeout>1.0</connectionTimeout> 
    </defaultProvider> 
  </database>  
  <ldap> 
    <host>127.0.0.1</host>  
    <port>10389</port>  
    <baseDN>dc=planetexpress,dc=com</baseDN>  
    <adminDN>cn=admin,dc=planetexpress,dc=com</adminDN>  
    <adminPassword encrypted="true" iv="fBKV52w5xJ9TQAX8Z2q7RA==">58761cc6aefb1713dc841aa13139e3c559bd6d795b205b5b67903258d2918925db19e7e6da946559aebe830e8ac27d58</admin>
    <sslEnabled>false</sslEnabled> 
  </ldap>  
  <setup>true</setup> 
  <locale>en</locale> 
  <fqdn>example.org</fqdn> 
  <clustering>
    <nodeid>a173e85d-40b9-4d46-afe7-f6063a600e4e</nodeid>
  </clustering>
</jive>

The database also stores an encrypted value, as expected:

mysql> SELECT * FROM openfire.ofProperty WHERE name = 'ldap.adminPassword'\G
*************************** 1. row ***************************
     name: ldap.adminPassword
propValue: 122c47e3fee4e0b5c9902475a3172a543a0042b4752bf6738811627f453b970d7ac711a34783e523e66ea968ecdeae72
encrypted: 1
       iv: OgTwde3oUG+1hd29MTxWMw==
1 row in set (0,00 sec)

I wonder what causes the different behavior in your setup.

I wonder if this is a(nother) outing of upper- and lowercase letters used in the value. MySQL is using a case insensitive coalation, I think, which may hide the problem.

Can you execute this on your database?

SELECT * FROM openfire.ofProperty WHERE name = 'ldap.adminPassword'

As a workaround, try adding both casings to security.xml (this is a hack, not a fix).

Hello Guus.
I am using the same XML configuration as you (although with a postgres database) and yet the ldap.adminPassword is in the database unencrypted.
The difference is that Openfire is running in a container in Kubernetes (k3s) environment. The XML configuration is used from the configmap.yaml template, and my deployment.yaml template contains the following steps:

Openfire kubernetes pod startup steps:

1. XML configuration from templates

  • Log: generation starts.

  • Remove any existing files: /etc/openfire/openfire.xml, /etc/openfire/security.xml.

  • envsubst: /templates/openfire.xml.template/etc/openfire/openfire.xml, and security.xml.templatesecurity.xml (substituted from environment variables).

2. Keystore / truststore setup

  • Remove existing keystore, truststore, and client.truststore.

  • Concatenate TLS certificate and CA, run openssl pkcs12, then keytool importkeystore into the main keystore.

  • Import CA into truststores; optionally LDAP CA (ldap.importCaFromSecret in the chart);

3. Database preparation

  • Wait in a loop until pg_isready succeeds.

  • Check whether the ofuser table exists; on first run, load the PostgreSQL schema (openfire_postgresql.sql).

5. Log4j and JVM startup

  • Copy log4j2.xml from the template to /etc/openfire/.

  • Run java -jar .../startup.jar


The Java launch command: what does it do?

After preparation, the embedded shell in openfire-startup-configmap.yaml starts the Openfire server as a background process with this java invocation:

java \
  -server \
  -DopenfireHome="/usr/share/openfire" \
  -Dopenfire.lib.dir="/usr/share/openfire/lib" \
  -Dlog4j2.configurationFile=file:/etc/openfire/log4j2.xml \
  -Dlog4j2.formatMsgNoLookups=true \
  -classpath "$/usr/share/openfire/lib/startup.jar" \
  -jar "/usr/share/openfire/lib/startup.jar" &

  • -server: HotSpot “server” JVM mode, tuned for long-running service workloads.

  • -DopenfireHome / -Dopenfire.lib.dir: Openfire install root (typically /usr/share/openfire) and the lib directory; Openfire loads its classes and resources from there.

  • -Dlog4j2.configurationFile: force Log4j2 to use the config file copied earlier to /etc/openfire/log4j2.xml.

  • -Dlog4j2.formatMsgNoLookups=true: disable message lookup interpolation in Log4j (common hardening setting).

  • -classpathstartup.jar + -jarstartup.jar: startup.jar is Openfire’s bootstrap entry point; the JVM starts from this JAR and loads the actual Openfire application using the given /usr/share/openfire.

    My personal solution:

    I have made a patch in the Openfire 4.8.3 source code (LdapManager.java) that solves the encryption problem.:

Hi Miklos,

Thanks for the detailed follow-up and for sharing the PostgreSQL logs. They’re genuinely useful for narrowing down what’s happening. The double-write behaviour you’ve captured (encrypt, then overwrite with plain text) is clearly a real bug, and your logs make a strong case that it’s specific to the PostgreSQL code path.

I do want to be transparent about one thing: the setup you’re describing (Kubernetes, Helm chart templating, envsubst preprocessing, custom startup sequencing, a patched Openfire build) is a fairly specialised combination. Reproducing and debugging issues that may arise at the intersection of all those layers is difficult to do reliably through open community support, and it’s probably not fair to you to have it handled that way either, given how much is riding on the deployment.

For issues of this complexity, you may get better results working with one of the professional service providers listed on our website, who can engage with your full environment directly (full disclosure: my own company is listed there).

That said, I’ll keep the ticket updated as I learn more about the PostgreSQL-specific behaviour. If you’re able to reduce the reproduction case to something outside of Kubernetes (even a plain Docker Compose setup with Postgres) that would help a lot in getting this fixed properly upstream.

Hi @guus,

We will review our options and contact the appropriate team.

Many thanks for your feedback and opinion

I had another look at this, and found the cause. It does not have anything to do with case sensitivity.

The problem with LDAP-specific encrypted attributes (them being stored in plain text, rather than as encrypted values), occurs with versions of Openfire prior to the 5.0.0 release. In 5.0.0 and later, this problem no longer occurs. This is a side-effect of a change (commit #a5a942fbd) in which the instantiation of the LDAPManager was switched from ‘eager’ to ‘lazy’. This has adjusted the order in which XML properties get invoked, which works around the problem.

The root cause of the problem is a bug in the code in the ‘migration’ of XML properties to the database. It correctly sets the encrypted flag, but in the ‘delete’ routine of the XML property (which has been moved to the database), the encrypted flag gets set to false, unconditionally.

Although the LDAP properties no longer seem to suffer from the problem in and after Openfire 5.0.0, other properties may.

I have raised a ticket for this issue: https://igniterealtime.atlassian.net/browse/OF-3296

A fix for this issue has been proposed here: https://github.com/igniterealtime/Openfire/pull/3353

@miklosmezei I suggest that you consider replacing your custom patch with this one. This will make the patch that you made redundant after you upgrade to Openfire 5.1.0 (which means that you’ll not run with a fork of Openfire until the end of times), but it also does a better job of addressing the root cause of the issue.