Skip to content

Add encrypted TOTP secret field to accounts#135

Open
RPGMais wants to merge 1 commit intoInfotelGLPI:masterfrom
RPGMais:feature/totp-secret-field
Open

Add encrypted TOTP secret field to accounts#135
RPGMais wants to merge 1 commit intoInfotelGLPI:masterfrom
RPGMais:feature/totp-secret-field

Conversation

@RPGMais
Copy link
Copy Markdown
Contributor

@RPGMais RPGMais commented Mar 24, 2026

Summary

  • Adds a dedicated encrypted_totp_secret column to store TOTP (Time-based One-Time Password) seeds alongside account credentials
  • TOTP secret is encrypted server-side using the same AES-256-CTR fingerprint as the password
  • Show/hide toggle in the UI follows the same UX pattern as the password field
  • Re-encryption is handled automatically when changing the fingerprint key

This is a highly requested feature (3 separate issues) that allows users to securely store 2FA seeds used by authenticators like Google Authenticator, Authy, and Bitwarden, instead of storing them as plaintext in the "Others" or "Comments" fields.

Changes

File Change
install/sql/update-3.3.0.sql Migration: adds encrypted_totp_secret column
install/sql/empty-3.2.1.sql Updated for fresh installations
hook.php Migration block with $DB->fieldExists() guard
src/Account.php encryptTotpSecret() helper, rawSearchOptions, prepareInputForAdd/Update, showForm
src/Hash.php updateHash() now re-encrypts TOTP secret alongside password
templates/account.html.twig TOTP input field with show/hide toggle and "Clear" checkbox

Design Decisions

  • Server-side encryption (not client-side like the password): TOTP seeds are short strings (16-32 chars base32) submitted via standard form POST. Client-side encryption overhead is unnecessary and would add JS complexity.
  • No live TOTP code generator: Kept out of this PR to minimize scope. Can be added as a follow-up with a lightweight JS library.
  • nosearch + nodisplay on the search option: encrypted content should never appear in search results or list views.

Test Plan

  • Fresh install: verify encrypted_totp_secret column exists in glpi_plugin_accounts_accounts
  • Upgrade from 3.2.x: verify migration adds the column without errors
  • Create account with TOTP secret: verify it's stored encrypted in DB
  • Edit account: verify "Leave blank to keep current" placeholder, existing secret preserved
  • Clear TOTP: check "Clear" checkbox, save, verify secret is removed
  • Change fingerprint key: verify TOTP secret is re-encrypted alongside password
  • Show/hide toggle: verify eye icon toggles input visibility

Fixes #127, closes #96, closes #102

Add support for storing TOTP (Time-based One-Time Password) secrets
alongside account credentials. The TOTP secret is encrypted server-side
using the same fingerprint as the password, providing secure storage
for 2FA seeds used by authenticators like Google Authenticator, Authy,
and Bitwarden.

Changes:
- New `encrypted_totp_secret` column (TEXT) in accounts table
- Server-side encryption via AccountCrypto (same AES-256-CTR as passwords)
- Show/hide toggle in the UI (same UX pattern as the password field)
- "Clear" checkbox to remove stored TOTP secret
- Re-encryption support when changing the fingerprint key (Hash::updateHash)
- Migration file for existing installations (update-3.3.0.sql)

The TOTP secret is encrypted server-side (unlike the password which is
encrypted client-side in JS). This is intentional as TOTP seeds are
short strings submitted via standard form POST and don't benefit from
the client-side encryption overhead.

Fixes InfotelGLPI#127
Closes InfotelGLPI#96
Closes InfotelGLPI#102
@RPGMais RPGMais force-pushed the feature/totp-secret-field branch from 3f9dca0 to 3ca19e0 Compare March 24, 2026 17:42
Comment thread src/Account.php
];

$tab[] = [
'id' => '30',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id 30 already used (please change to 31)

Comment thread src/Account.php
$aeskey = new AesKey();
if ($hash_id
&& $aeskey->getFromDBByCrit(['plugin_accounts_hashes_id' => $hash_id])
&& !empty($aeskey->fields['name'])) {
Copy link
Copy Markdown
Contributor

@tsmr tsmr Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if i haven't stored aeskey into database, i cannot encrypt it ?

class="form-control"
autocomplete="off"
placeholder="{{ item.isNewItem() ? '' : __('Leave blank to keep current', 'accounts') }}"
value="">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can i see the uncrypted value of TOTP secret ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Add encrypted TOTP secret field to accounts OTP TOTP Support

2 participants