diff --git a/source/packages/com_mokosuitebackup/forms/profile.xml b/source/packages/com_mokosuitebackup/forms/profile.xml
index fbbb2f7..274154b 100644
--- a/source/packages/com_mokosuitebackup/forms/profile.xml
+++ b/source/packages/com_mokosuitebackup/forms/profile.xml
@@ -159,7 +159,6 @@
default="none"
>
-
@@ -183,6 +182,7 @@
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST_DESC"
maxlength="255"
+ required="true"
showon="remote_storage:sftp"
/>
+
+
+
+
+
diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini
index 9398fa4..1f2f08d 100644
--- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini
+++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini
@@ -256,7 +256,17 @@ COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC="Username for SSH authentication"
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD="SSH Password"
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC="Password for SSH authentication. Leave blank if using a key file."
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY="SSH Private Key"
-COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Paste the contents of your SSH private key (e.g. id_rsa or id_ed25519). The key is stored securely in the database and written to a temp file with 0600 permissions only during upload, then deleted. Leave blank to use password authentication."
+COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Upload or paste your SSH private key (e.g. id_rsa or id_ed25519). The key is stored securely in the database and written to a temp file with 0600 permissions only during upload, then deleted. Leave blank to use password authentication."
+COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD="Upload Key File"
+COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE="Replace Key"
+COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED="Key loaded"
+COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_NONE="No key file"
+COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_CLEAR="Remove Key"
+COM_MOKOJOOMBACKUP_FIELD_SFTP_AUTH_TYPE="Authentication Type"
+COM_MOKOJOOMBACKUP_FIELD_SFTP_AUTH_TYPE_DESC="Choose how to authenticate with the SFTP server."
+COM_MOKOJOOMBACKUP_SFTP_AUTH_PASSWORD="Password"
+COM_MOKOJOOMBACKUP_SFTP_AUTH_KEY="Key File"
+COM_MOKOJOOMBACKUP_SFTP_AUTH_KEY_PASSPHRASE="Key File + Passphrase"
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE="Key Passphrase"
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE_DESC="Passphrase for the private key, if encrypted. Leave blank for unencrypted keys."
COM_MOKOJOOMBACKUP_FIELD_SFTP_PATH="Remote Path"
diff --git a/source/packages/com_mokosuitebackup/sql/install.mysql.sql b/source/packages/com_mokosuitebackup/sql/install.mysql.sql
index 67020ec..a87895c 100644
--- a/source/packages/com_mokosuitebackup/sql/install.mysql.sql
+++ b/source/packages/com_mokosuitebackup/sql/install.mysql.sql
@@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_profiles` (
`sftp_host` VARCHAR(255) NOT NULL DEFAULT '',
`sftp_port` INT(5) UNSIGNED NOT NULL DEFAULT 22,
`sftp_username` VARCHAR(255) NOT NULL DEFAULT '',
+ `sftp_auth_type` VARCHAR(20) NOT NULL DEFAULT 'key',
`sftp_password` VARCHAR(255) NOT NULL DEFAULT '',
`sftp_key_data` MEDIUMTEXT,
`sftp_passphrase` VARCHAR(255) NOT NULL DEFAULT '',
diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/01.36.00.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/01.36.00.sql
new file mode 100644
index 0000000..b8b6565
--- /dev/null
+++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/01.36.00.sql
@@ -0,0 +1,4 @@
+-- MokoSuiteBackup 01.36.00 — SFTP auth type column
+
+ALTER TABLE `#__mokosuitebackup_profiles`
+ ADD COLUMN `sftp_auth_type` VARCHAR(20) NOT NULL DEFAULT 'key' AFTER `sftp_username`;
diff --git a/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php b/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php
index 5911c06..980ea4a 100644
--- a/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php
+++ b/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php
@@ -141,7 +141,15 @@ class SftpUploader implements RemoteUploaderInterface
$tmpDir = sys_get_temp_dir();
$keyFile = $tmpDir . '/mokobackup-sftp-' . bin2hex(random_bytes(8)) . '.key';
- if (file_put_contents($keyFile, $this->keyData) === false) {
+ /* Key is stored base64-encoded in the database — decode before writing */
+ $keyContent = base64_decode($this->keyData, true);
+
+ if ($keyContent === false) {
+ /* Fallback: might be raw PEM (legacy or paste) */
+ $keyContent = $this->keyData;
+ }
+
+ if (file_put_contents($keyFile, $keyContent) === false) {
throw new \RuntimeException('Cannot write temporary SSH key file');
}
diff --git a/source/packages/com_mokosuitebackup/src/Field/SshKeyField.php b/source/packages/com_mokosuitebackup/src/Field/SshKeyField.php
new file mode 100644
index 0000000..176ce22
--- /dev/null
+++ b/source/packages/com_mokosuitebackup/src/Field/SshKeyField.php
@@ -0,0 +1,109 @@
+
+ * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
+ * @license GNU General Public License version 3 or later; see LICENSE
+ *
+ * Custom field for SSH private key input.
+ * Supports both file upload (via FileReader JS) and paste-in textarea.
+ * The key content is stored in the database, not as a file on disk.
+ */
+
+namespace Joomla\Component\MokoSuiteBackup\Administrator\Field;
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Form\FormField;
+use Joomla\CMS\Language\Text;
+
+class SshKeyField extends FormField
+{
+ protected $type = 'SshKey';
+
+ protected function getInput(): string
+ {
+ $value = $this->value ?? '';
+ $id = $this->id;
+ $name = $this->name;
+
+ $hasKey = !empty($value) && str_contains($value, 'PRIVATE KEY');
+
+ $html = '
';
+
+ /* Status badge */
+ if ($hasKey) {
+ $html .= ''
+ . ' '
+ . Text::_('COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED')
+ . '';
+ }
+
+ /* File upload button */
+ $html .= '';
+ $html .= '';
+
+ $html .= '';
+
+ if ($hasKey) {
+ $html .= ' ';
+ }
+
+ /* Hidden field — key data is NEVER rendered as visible text.
+ On existing keys, we submit a sentinel value to preserve the DB value
+ unless a new file is uploaded or clear is clicked. */
+ if ($hasKey) {
+ $html .= '';
+ } else {
+ $html .= '';
+ }
+
+ $html .= '
';
+ $html .= $this->getScript();
+
+ return $html;
+ }
+
+ private function getScript(): string
+ {
+ return <<<'JS'
+
+JS;
+ }
+}
diff --git a/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php b/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php
index fef06ca..5102e91 100644
--- a/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php
+++ b/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php
@@ -25,6 +25,23 @@ class ProfileTable extends Table
public function store($updateNulls = true): bool
{
+ /* Handle SSH key sentinel — when __KEEP_EXISTING__ is submitted,
+ preserve the current DB value instead of overwriting with the sentinel.
+ This prevents the key from being exposed in the form HTML. */
+ if (isset($this->sftp_key_data) && $this->sftp_key_data === '__KEEP_EXISTING__') {
+ if ($this->id) {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('sftp_key_data'))
+ ->from($db->quoteName($this->_tbl))
+ ->where($db->quoteName('id') . ' = ' . (int) $this->id);
+ $db->setQuery($query);
+ $this->sftp_key_data = $db->loadResult() ?: '';
+ } else {
+ $this->sftp_key_data = '';
+ }
+ }
+
$result = parent::store($updateNulls);
if ($result && !empty($this->backup_dir)) {